4637 lines
		
	
	
		
			140 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4637 lines
		
	
	
		
			140 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2016 MariaDB Corporation Ab
 | |
|  *
 | |
|  * Use of this software is governed by the Business Source License included
 | |
|  * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | |
|  *
 | |
|  * Change Date: 2020-01-01
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file config.c Configuration file processing
 | |
|  */
 | |
| 
 | |
| #include <maxscale/config.h>
 | |
| 
 | |
| #include <ctype.h>
 | |
| #include <ftw.h>
 | |
| #include <fcntl.h>
 | |
| #include <glob.h>
 | |
| #include <net/if.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <ini.h>
 | |
| #include <set>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| 
 | |
| #include <maxscale/adminusers.h>
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/housekeeper.h>
 | |
| #include <maxscale/limits.h>
 | |
| #include <maxscale/log_manager.h>
 | |
| #include <maxscale/pcre2.h>
 | |
| #include <maxscale/spinlock.h>
 | |
| #include <maxscale/utils.h>
 | |
| #include <maxscale/paths.h>
 | |
| #include <maxscale/router.h>
 | |
| #include <maxscale/json_api.h>
 | |
| #include <maxscale/http.hh>
 | |
| #include <maxscale/version.h>
 | |
| #include <maxscale/maxscale.h>
 | |
| #include <maxscale/clock.h>
 | |
| 
 | |
| #include "internal/config.h"
 | |
| #include "internal/filter.h"
 | |
| #include "internal/service.h"
 | |
| #include "internal/monitor.h"
 | |
| #include "internal/modules.h"
 | |
| 
 | |
| using std::set;
 | |
| using std::string;
 | |
| 
 | |
| const char CN_ACCOUNT[]                       = "account";
 | |
| const char CN_ADDRESS[]                       = "address";
 | |
| const char CN_ARG_MAX[]                       = "arg_max";
 | |
| const char CN_ARG_MIN[]                       = "arg_min";
 | |
| const char CN_ADMIN_AUTH[]                    = "admin_auth";
 | |
| const char CN_ADMIN_ENABLED[]                 = "admin_enabled";
 | |
| const char CN_ADMIN_LOG_AUTH_FAILURES[]       = "admin_log_auth_failures";
 | |
| const char CN_ADMIN_HOST[]                    = "admin_host";
 | |
| const char CN_ADMIN_PORT[]                    = "admin_port";
 | |
| const char CN_ADMIN_SSL_KEY[]                 = "admin_ssl_key";
 | |
| const char CN_ADMIN_SSL_CERT[]                = "admin_ssl_cert";
 | |
| const char CN_ADMIN_SSL_CA_CERT[]             = "admin_ssl_ca_cert";
 | |
| const char CN_ATTRIBUTES[]                    = "attributes";
 | |
| const char CN_AUTHENTICATOR[]                 = "authenticator";
 | |
| const char CN_AUTHENTICATOR_DIAGNOSTICS[]     = "authenticator_diagnostics";
 | |
| const char CN_AUTHENTICATOR_OPTIONS[]         = "authenticator_options";
 | |
| const char CN_AUTH_ALL_SERVERS[]              = "auth_all_servers";
 | |
| const char CN_AUTH_CONNECT_TIMEOUT[]          = "auth_connect_timeout";
 | |
| const char CN_AUTH_READ_TIMEOUT[]             = "auth_read_timeout";
 | |
| const char CN_AUTH_WRITE_TIMEOUT[]            = "auth_write_timeout";
 | |
| const char CN_AUTO[]                          = "auto";
 | |
| const char CN_CONNECTION_TIMEOUT[]            = "connection_timeout";
 | |
| const char CN_DATA[]                          = "data";
 | |
| const char CN_DEFAULT[]                       = "default";
 | |
| const char CN_DESCRIPTION[]                   = "description";
 | |
| const char CN_DUMP_LAST_STATEMENTS[]          = "dump_last_statements";
 | |
| const char CN_ENABLE_ROOT_USER[]              = "enable_root_user";
 | |
| const char CN_FILTERS[]                       = "filters";
 | |
| const char CN_FILTER[]                        = "filter";
 | |
| const char CN_FILTER_DIAGNOSTICS[]            = "filter_diagnostics";
 | |
| const char CN_GATEWAY[]                       = "gateway";
 | |
| const char CN_ID[]                            = "id";
 | |
| const char CN_INET[]                          = "inet";
 | |
| const char CN_LISTENER[]                      = "listener";
 | |
| const char CN_LISTENERS[]                     = "listeners";
 | |
| const char CN_LOCALHOST_MATCH_WILDCARD_HOST[] = "localhost_match_wildcard_host";
 | |
| const char CN_LOG_AUTH_WARNINGS[]             = "log_auth_warnings";
 | |
| const char CN_LOG_THROTTLING[]                = "log_throttling";
 | |
| const char CN_MAXSCALE[]                      = "maxscale";
 | |
| const char CN_MAX_CONNECTIONS[]               = "max_connections";
 | |
| const char CN_MAX_RETRY_INTERVAL[]            = "max_retry_interval";
 | |
| const char CN_META[]                          = "meta";
 | |
| const char CN_METHOD[]                        = "method";
 | |
| const char CN_MODULE[]                        = "module";
 | |
| const char CN_MODULES[]                       = "modules";
 | |
| const char CN_MODULE_COMMAND[]                = "module_command";
 | |
| const char CN_MONITORS[]                      = "monitors";
 | |
| const char CN_MONITOR[]                       = "monitor";
 | |
| const char CN_MONITOR_DIAGNOSTICS[]           = "monitor_diagnostics";
 | |
| const char CN_MS_TIMESTAMP[]                  = "ms_timestamp";
 | |
| const char CN_NAME[]                          = "name";
 | |
| const char CN_NON_BLOCKING_POLLS[]            = "non_blocking_polls";
 | |
| const char CN_OPTIONS[]                       = "options";
 | |
| const char CN_PARAMETERS[]                    = "parameters";
 | |
| const char CN_PASSIVE[]                       = "passive";
 | |
| const char CN_PASSWORD[]                      = "password";
 | |
| const char CN_POLL_SLEEP[]                    = "poll_sleep";
 | |
| const char CN_PORT[]                          = "port";
 | |
| const char CN_PROTOCOL[]                      = "protocol";
 | |
| const char CN_QUERY_CLASSIFIER[]              = "query_classifier";
 | |
| const char CN_QUERY_CLASSIFIER_ARGS[]         = "query_classifier_args";
 | |
| const char CN_QUERY_RETRIES[]                 = "query_retries";
 | |
| const char CN_QUERY_RETRY_TIMEOUT[]           = "query_retry_timeout";
 | |
| const char CN_RELATIONSHIPS[]                 = "relationships";
 | |
| const char CN_LINKS[]                         = "links";
 | |
| const char CN_LOCAL_ADDRESS[]                 = "local_address";
 | |
| const char CN_REQUIRED[]                      = "required";
 | |
| const char CN_RETAIN_LAST_STATEMENTS[]        = "retain_last_statements";
 | |
| const char CN_RETRY_ON_FAILURE[]              = "retry_on_failure";
 | |
| const char CN_ROUTER[]                        = "router";
 | |
| const char CN_ROUTER_DIAGNOSTICS[]            = "router_diagnostics";
 | |
| const char CN_ROUTER_OPTIONS[]                = "router_options";
 | |
| const char CN_SELF[]                          = "self";
 | |
| const char CN_SERVERS[]                       = "servers";
 | |
| const char CN_SERVER[]                        = "server";
 | |
| const char CN_SERVICES[]                      = "services";
 | |
| const char CN_SERVICE[]                       = "service";
 | |
| const char CN_SESSIONS[]                      = "sessions";
 | |
| const char CN_SKIP_PERMISSION_CHECKS[]        = "skip_permission_checks";
 | |
| const char CN_SOCKET[]                        = "socket";
 | |
| const char CN_SQL_MODE[]                      = "sql_mode";
 | |
| const char CN_STATE[]                         = "state";
 | |
| const char CN_SSL[]                           = "ssl";
 | |
| const char CN_SSL_CA_CERT[]                   = "ssl_ca_cert";
 | |
| const char CN_SSL_CERT[]                      = "ssl_cert";
 | |
| const char CN_SSL_CERT_VERIFY_DEPTH[]         = "ssl_cert_verify_depth";
 | |
| const char CN_SSL_VERIFY_PEER_CERTIFICATE[]   = "ssl_verify_peer_certificate";
 | |
| const char CN_SSL_KEY[]                       = "ssl_key";
 | |
| const char CN_SSL_VERSION[]                   = "ssl_version";
 | |
| const char CN_STRIP_DB_ESC[]                  = "strip_db_esc";
 | |
| const char CN_SUBSTITUTE_VARIABLES[]          = "substitute_variables";
 | |
| const char CN_THREADS[]                       = "threads";
 | |
| const char CN_THREAD_STACK_SIZE[]             = "thread_stack_size";
 | |
| const char CN_TYPE[]                          = "type";
 | |
| const char CN_UNIX[]                          = "unix";
 | |
| const char CN_USER[]                          = "user";
 | |
| const char CN_USERS[]                         = "users";
 | |
| const char CN_USERS_REFRESH_TIME[]            = "users_refresh_time";
 | |
| const char CN_VERSION_STRING[]                = "version_string";
 | |
| const char CN_WEIGHTBY[]                      = "weightby";
 | |
| const char CN_SESSION_TRACK_TRX_STATE[]       = "session_track_trx_state";
 | |
| const char CN_WRITEQ_HIGH_WATER[]             = "writeq_high_water";
 | |
| const char CN_WRITEQ_LOW_WATER[]              = "writeq_low_water";
 | |
| 
 | |
| 
 | |
| extern const char CN_LOGDIR[] = "logdir";
 | |
| extern const char CN_LIBDIR[] = "libdir";
 | |
| extern const char CN_PIDDIR[] = "piddir";
 | |
| extern const char CN_DATADIR[] = "datadir";
 | |
| extern const char CN_CACHEDIR[] = "cachedir";
 | |
| extern const char CN_LANGUAGE[] = "language";
 | |
| extern const char CN_EXECDIR[] = "execdir";
 | |
| extern const char CN_CONNECTOR_PLUGINDIR[] = "connector_plugindir";
 | |
| extern const char CN_PERSISTDIR[] = "persistdir";
 | |
| extern const char CN_MODULE_CONFIGDIR[] = "module_configdir";
 | |
| extern const char CN_SYSLOG[] = "syslog";
 | |
| extern const char CN_MAXLOG[] = "maxlog";
 | |
| extern const char CN_LOG_AUGMENTATION[] = "log_augmentation";
 | |
| extern const char CN_LOG_TO_SHM[] = "log_to_shm";
 | |
| 
 | |
| typedef struct duplicate_context
 | |
| {
 | |
|     HASHTABLE        *hash;
 | |
|     pcre2_code       *re;
 | |
|     pcre2_match_data *mdata;
 | |
| } DUPLICATE_CONTEXT;
 | |
| 
 | |
| static bool duplicate_context_init(DUPLICATE_CONTEXT* context);
 | |
| static void duplicate_context_finish(DUPLICATE_CONTEXT* context);
 | |
| 
 | |
| static bool process_config_context(CONFIG_CONTEXT *);
 | |
| static bool process_config_update(CONFIG_CONTEXT *);
 | |
| static char *config_get_value(MXS_CONFIG_PARAMETER *, const char *);
 | |
| static char *config_get_password(MXS_CONFIG_PARAMETER *);
 | |
| static const char* config_get_value_string(const MXS_CONFIG_PARAMETER *params, const char *name);
 | |
| static int handle_global_item(const char *, const char *);
 | |
| static bool check_config_objects(CONFIG_CONTEXT *context);
 | |
| static int maxscale_getline(char** dest, int* size, FILE* file);
 | |
| static bool check_first_last_char(const char* string, char expected);
 | |
| static void remove_first_last_char(char* value);
 | |
| static bool test_regex_string_validity(const char* regex_string, const char* key);
 | |
| static pcre2_code* compile_regex_string(const char* regex_string, bool jit_enabled,
 | |
|                                         uint32_t options, uint32_t* output_ovector_size);
 | |
| static uint64_t get_suffixed_size(const char* value);
 | |
| 
 | |
| int config_get_ifaddr(unsigned char *output);
 | |
| static int config_get_release_string(char* release);
 | |
| bool config_has_duplicate_sections(const char* config, DUPLICATE_CONTEXT* context);
 | |
| int create_new_service(CONFIG_CONTEXT *obj);
 | |
| int create_new_server(CONFIG_CONTEXT *obj);
 | |
| int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash);
 | |
| int create_new_listener(CONFIG_CONTEXT *obj);
 | |
| int create_new_filter(CONFIG_CONTEXT *obj);
 | |
| int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj);
 | |
| void config_fix_param(const MXS_MODULE_PARAM *params, MXS_CONFIG_PARAMETER *p);
 | |
| 
 | |
| static const char *config_file = NULL;
 | |
| static MXS_CONFIG gateway;
 | |
| char *version_string = NULL;
 | |
| static bool is_persisted_config = false; /**< True if a persisted configuration file is being parsed */
 | |
| 
 | |
| const char *config_service_params[] =
 | |
| {
 | |
|     CN_TYPE,
 | |
|     CN_ROUTER,
 | |
|     CN_ROUTER_OPTIONS,
 | |
|     CN_SERVERS,
 | |
|     CN_MONITOR,
 | |
|     CN_USER,
 | |
|     "passwd", // DEPRECATE: See config_get_password.
 | |
|     CN_PASSWORD,
 | |
|     CN_ENABLE_ROOT_USER,
 | |
|     CN_MAX_RETRY_INTERVAL,
 | |
|     CN_MAX_CONNECTIONS,
 | |
|     "max_queued_connections", //TODO: Fix this
 | |
|     "queued_connection_timeout", // TODO: Fix this
 | |
|     CN_CONNECTION_TIMEOUT,
 | |
|     CN_AUTH_ALL_SERVERS,
 | |
|     CN_STRIP_DB_ESC,
 | |
|     CN_LOCALHOST_MATCH_WILDCARD_HOST,
 | |
|     CN_VERSION_STRING,
 | |
|     CN_FILTERS,
 | |
|     CN_WEIGHTBY,
 | |
|     CN_LOG_AUTH_WARNINGS,
 | |
|     CN_RETRY_ON_FAILURE,
 | |
|     CN_SESSION_TRACK_TRX_STATE,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| const char *config_listener_params[] =
 | |
| {
 | |
|     CN_AUTHENTICATOR_OPTIONS,
 | |
|     CN_TYPE,
 | |
|     CN_SERVICE,
 | |
|     CN_PROTOCOL,
 | |
|     CN_PORT,
 | |
|     CN_ADDRESS,
 | |
|     CN_SOCKET,
 | |
|     CN_AUTHENTICATOR,
 | |
|     CN_SSL_CERT,
 | |
|     CN_SSL_CA_CERT,
 | |
|     CN_SSL,
 | |
|     CN_SSL_KEY,
 | |
|     CN_SSL_VERSION,
 | |
|     CN_SSL_CERT_VERIFY_DEPTH,
 | |
|     CN_SSL_VERIFY_PEER_CERTIFICATE,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| const char *config_monitor_params[] =
 | |
| {
 | |
|     CN_TYPE,
 | |
|     CN_MODULE,
 | |
|     CN_SERVERS,
 | |
|     CN_USER,
 | |
|     "passwd",   // DEPRECATE: See config_get_password.
 | |
|     CN_PASSWORD,
 | |
|     CN_SCRIPT,
 | |
|     CN_EVENTS,
 | |
|     CN_MONITOR_INTERVAL,
 | |
|     CN_JOURNAL_MAX_AGE,
 | |
|     CN_SCRIPT_TIMEOUT,
 | |
|     CN_BACKEND_CONNECT_TIMEOUT,
 | |
|     CN_BACKEND_READ_TIMEOUT,
 | |
|     CN_BACKEND_WRITE_TIMEOUT,
 | |
|     CN_BACKEND_CONNECT_ATTEMPTS,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| const char *config_filter_params[] =
 | |
| {
 | |
|     CN_TYPE,
 | |
|     CN_MODULE,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| const char *server_params[] =
 | |
| {
 | |
|     CN_TYPE,
 | |
|     CN_PROTOCOL,
 | |
|     CN_PORT,
 | |
|     CN_ADDRESS,
 | |
|     CN_AUTHENTICATOR,
 | |
|     CN_MONITORUSER,
 | |
|     CN_MONITORPW,
 | |
|     CN_PERSISTPOOLMAX,
 | |
|     CN_PERSISTMAXTIME,
 | |
|     CN_SSL_CERT,
 | |
|     CN_SSL_CA_CERT,
 | |
|     CN_SSL,
 | |
|     CN_SSL_KEY,
 | |
|     CN_SSL_VERSION,
 | |
|     CN_SSL_CERT_VERIFY_DEPTH,
 | |
|     CN_SSL_VERIFY_PEER_CERTIFICATE,
 | |
|     CN_PROXY_PROTOCOL,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * This is currently only used in handle_global_item() to verify that
 | |
|  * all global configuration item names are valid.
 | |
|  */
 | |
| const char *config_pre_parse_global_params[] =
 | |
| {
 | |
|     CN_LOGDIR,
 | |
|     CN_LIBDIR,
 | |
|     CN_PIDDIR,
 | |
|     CN_DATADIR,
 | |
|     CN_CACHEDIR,
 | |
|     CN_LANGUAGE,
 | |
|     CN_EXECDIR,
 | |
|     CN_CONNECTOR_PLUGINDIR,
 | |
|     CN_PERSISTDIR,
 | |
|     CN_MODULE_CONFIGDIR,
 | |
|     CN_SYSLOG,
 | |
|     CN_MAXLOG,
 | |
|     CN_LOG_AUGMENTATION,
 | |
|     CN_LOG_TO_SHM,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| const char *deprecated_server_params[] =
 | |
| {
 | |
|     CN_AUTHENTICATOR_OPTIONS,
 | |
|     NULL
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Initialize the context object used for tracking duplicate sections.
 | |
|  *
 | |
|  * @param context The context object to be initialized.
 | |
|  *
 | |
|  * @return True, if the object could be initialized.
 | |
|  */
 | |
| static bool duplicate_context_init(DUPLICATE_CONTEXT* context)
 | |
| {
 | |
|     bool rv = false;
 | |
| 
 | |
|     const int table_size = 10;
 | |
|     HASHTABLE *hash = hashtable_alloc(table_size, hashtable_item_strhash, hashtable_item_strcmp);
 | |
|     int errcode;
 | |
|     PCRE2_SIZE erroffset;
 | |
|     pcre2_code *re = pcre2_compile((PCRE2_SPTR) "^\\s*\\[(.+)\\]\\s*$", PCRE2_ZERO_TERMINATED,
 | |
|                                    0, &errcode, &erroffset, NULL);
 | |
|     pcre2_match_data *mdata = NULL;
 | |
| 
 | |
|     if (hash && re && (mdata = pcre2_match_data_create_from_pattern(re, NULL)))
 | |
|     {
 | |
|         hashtable_memory_fns(hash, hashtable_item_strdup, NULL, hashtable_item_free, NULL);
 | |
| 
 | |
|         context->hash = hash;
 | |
|         context->re = re;
 | |
|         context->mdata = mdata;
 | |
|         rv = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         pcre2_match_data_free(mdata);
 | |
|         pcre2_code_free(re);
 | |
|         hashtable_free(hash);
 | |
|     }
 | |
| 
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Finalize the context object used for tracking duplicate sections.
 | |
|  *
 | |
|  * @param context The context object to be initialized.
 | |
|  */
 | |
| static void duplicate_context_finish(DUPLICATE_CONTEXT* context)
 | |
| {
 | |
|     pcre2_match_data_free(context->mdata);
 | |
|     pcre2_code_free(context->re);
 | |
|     hashtable_free(context->hash);
 | |
| 
 | |
|     context->mdata = NULL;
 | |
|     context->re = NULL;
 | |
|     context->hash = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Remove extra commas and whitespace from a string. This string is interpreted
 | |
|  * as a list of string values separated by commas.
 | |
|  * @param strptr String to clean
 | |
|  * @return pointer to a new string or NULL if an error occurred
 | |
|  */
 | |
| char* config_clean_string_list(const char* str)
 | |
| {
 | |
|     size_t destsize = strlen(str) + 1;
 | |
|     char *dest = (char*)MXS_MALLOC(destsize);
 | |
| 
 | |
|     if (dest)
 | |
|     {
 | |
|         pcre2_code* re;
 | |
|         pcre2_match_data* data;
 | |
|         int re_err;
 | |
|         size_t err_offset;
 | |
| 
 | |
|         if ((re = pcre2_compile((PCRE2_SPTR) "[[:space:],]*([^,]*[^[:space:],])[[:space:],]*",
 | |
|                                 PCRE2_ZERO_TERMINATED, 0, &re_err, &err_offset, NULL)) == NULL ||
 | |
|             (data = pcre2_match_data_create_from_pattern(re, NULL)) == NULL)
 | |
|         {
 | |
|             PCRE2_UCHAR errbuf[MXS_STRERROR_BUFLEN];
 | |
|             pcre2_get_error_message(re_err, errbuf, sizeof(errbuf));
 | |
|             MXS_ERROR("[%s] Regular expression compilation failed at %d: %s",
 | |
|                       __FUNCTION__, (int)err_offset, errbuf);
 | |
|             pcre2_code_free(re);
 | |
|             MXS_FREE(dest);
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         const char *replace = "$1,";
 | |
|         int rval = 0;
 | |
|         size_t destsize_tmp = destsize;
 | |
|         while ((rval = pcre2_substitute(re, (PCRE2_SPTR) str, PCRE2_ZERO_TERMINATED, 0,
 | |
|                                         PCRE2_SUBSTITUTE_GLOBAL, data, NULL,
 | |
|                                         (PCRE2_SPTR) replace, PCRE2_ZERO_TERMINATED,
 | |
|                                         (PCRE2_UCHAR*) dest, &destsize_tmp)) == PCRE2_ERROR_NOMEMORY)
 | |
|         {
 | |
|             destsize_tmp = 2 * destsize;
 | |
|             char* tmp = (char*)MXS_REALLOC(dest, destsize_tmp);
 | |
|             if (tmp == NULL)
 | |
|             {
 | |
|                 MXS_FREE(dest);
 | |
|                 dest = NULL;
 | |
|                 break;
 | |
|             }
 | |
|             dest = tmp;
 | |
|             destsize = destsize_tmp;
 | |
|         }
 | |
| 
 | |
|         /** Remove the trailing comma */
 | |
|         if (dest && dest[strlen(dest) - 1] == ',')
 | |
|         {
 | |
|             dest[strlen(dest) - 1] = '\0';
 | |
|         }
 | |
| 
 | |
|         pcre2_code_free(re);
 | |
|         pcre2_match_data_free(data);
 | |
|     }
 | |
| 
 | |
|     return dest;
 | |
| }
 | |
| 
 | |
| CONFIG_CONTEXT* config_context_create(const char *section)
 | |
| {
 | |
|     CONFIG_CONTEXT* ctx = (CONFIG_CONTEXT *)MXS_MALLOC(sizeof(CONFIG_CONTEXT));
 | |
|     if (ctx)
 | |
|     {
 | |
|         ctx->object = MXS_STRDUP_A(section);
 | |
|         ctx->was_persisted = is_persisted_config;
 | |
|         ctx->parameters = NULL;
 | |
|         ctx->next = NULL;
 | |
|         ctx->element = NULL;
 | |
|     }
 | |
| 
 | |
|     return ctx;
 | |
| }
 | |
| 
 | |
| /** A set that holds all the section names that contain whitespace */
 | |
| static std::set<string> warned_whitespace;
 | |
| 
 | |
| /**
 | |
|  * @brief Fix section names
 | |
|  *
 | |
|  * Check that section names contain no whitespace. If the name contains
 | |
|  * whitespace, trim it, squeeze it and replace the remaining whitespace with
 | |
|  * hyphens. If a replacement was made, a warning is logged.
 | |
|  *
 | |
|  * @param section Section name
 | |
|  */
 | |
| void fix_section_name(char *section)
 | |
| {
 | |
|     for (char* s = section; *s; s++)
 | |
|     {
 | |
|         if (isspace(*s))
 | |
|         {
 | |
|             if (warned_whitespace.find(section) == warned_whitespace.end())
 | |
|             {
 | |
|                 warned_whitespace.insert(section);
 | |
|                 MXS_WARNING("Whitespace in object names is deprecated, "
 | |
|                             "converting to hyphens: %s", section);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     squeeze_whitespace(section);
 | |
|     trim(section);
 | |
|     replace_whitespace(section);
 | |
| }
 | |
| 
 | |
| static bool is_empty_string(const char* str)
 | |
| {
 | |
|     for (const char* p = str; *p; p++)
 | |
|     {
 | |
|         if (!isspace(*p))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Config item handler for the ini file reader
 | |
|  *
 | |
|  * @param userdata      The config context element
 | |
|  * @param section       The config file section
 | |
|  * @param name          The Parameter name
 | |
|  * @param value         The Parameter value
 | |
|  * @return zero on error
 | |
|  */
 | |
| static int ini_handler(void *userdata, const char *section, const char *name, const char *value)
 | |
| {
 | |
|     CONFIG_CONTEXT *cntxt = (CONFIG_CONTEXT *)userdata;
 | |
|     CONFIG_CONTEXT *ptr = cntxt;
 | |
| 
 | |
|     if (is_empty_string(value))
 | |
|     {
 | |
|         if (is_persisted_config)
 | |
|         {
 | |
|             /**
 | |
|              * Found old-style persisted configuration. These will be automatically
 | |
|              * upgraded on the next modification so we can safely ignore it.
 | |
|              */
 | |
|             return 1;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Empty value given to parameter '%s'", name);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (config_get_global_options()->substitute_variables)
 | |
|     {
 | |
|         if (*value == '$')
 | |
|         {
 | |
|             char* env_value = getenv(value + 1);
 | |
| 
 | |
|             if (!env_value)
 | |
|             {
 | |
|                 MXS_ERROR("The environment variable %s, used as value for parameter %s "
 | |
|                           "in section %s, does not exist.", value + 1, name, section);
 | |
|                 return 0;
 | |
|             }
 | |
| 
 | |
|             value = env_value;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (strcmp(section, CN_GATEWAY) == 0 || strcasecmp(section, CN_MAXSCALE) == 0)
 | |
|     {
 | |
|         return handle_global_item(name, value);
 | |
|     }
 | |
|     else if (strlen(section) == 0)
 | |
|     {
 | |
|         MXS_ERROR("Parameter '%s=%s' declared outside a section.", name, value);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     char fixed_section[strlen(section) + 1];
 | |
|     strcpy(fixed_section, section);
 | |
|     fix_section_name(fixed_section);
 | |
| 
 | |
|     /*
 | |
|      * If we already have some parameters for the object
 | |
|      * add the parameters to that object. If not create
 | |
|      * a new object.
 | |
|      */
 | |
|     while (ptr && strcmp(ptr->object, fixed_section) != 0)
 | |
|     {
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
| 
 | |
|     if (!ptr)
 | |
|     {
 | |
|         if ((ptr = config_context_create(fixed_section)) == NULL)
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         ptr->next = cntxt->next;
 | |
|         cntxt->next = ptr;
 | |
|     }
 | |
| 
 | |
|     if (config_get_param(ptr->parameters, name))
 | |
|     {
 | |
|         /** The values in the persisted configurations are updated versions of
 | |
|          * the ones in the main configuration file.  */
 | |
|         if (is_persisted_config)
 | |
|         {
 | |
|             if (!config_replace_param(ptr, name, value))
 | |
|             {
 | |
|                 return 0;
 | |
|             }
 | |
|         }
 | |
|         /** Multi-line parameter */
 | |
|         else if (!config_append_param(ptr, name, value))
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     else if (!config_add_param(ptr, name, value))
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Load single configuration file.
 | |
|  *
 | |
|  * @param file     The file to load.
 | |
|  * @param dcontext The context object used when tracking duplicate sections.
 | |
|  * @param ccontext The context object used when parsing.
 | |
|  *
 | |
|  * @return True if the file could be parsed, false otherwise.
 | |
|  */
 | |
| static bool config_load_single_file(const char* file,
 | |
|                                     DUPLICATE_CONTEXT* dcontext,
 | |
|                                     CONFIG_CONTEXT* ccontext)
 | |
| {
 | |
|     int rval = -1;
 | |
| 
 | |
|     // With multiple configuration files being loaded, we need to log the file
 | |
|     // currently being loaded so that the context is clear in case of errors.
 | |
|     MXS_NOTICE("Loading %s.", file);
 | |
| 
 | |
|     if (!config_has_duplicate_sections(file, dcontext))
 | |
|     {
 | |
|         if ((rval = ini_parse(file, ini_handler, ccontext)) != 0)
 | |
|         {
 | |
|             char errorbuffer[1024 + 1];
 | |
| 
 | |
|             if (rval > 0)
 | |
|             {
 | |
|                 snprintf(errorbuffer, sizeof(errorbuffer),
 | |
|                          "Failed to parse configuration file %s. Error on line %d.", file, rval);
 | |
|             }
 | |
|             else if (rval == -1)
 | |
|             {
 | |
|                 snprintf(errorbuffer, sizeof(errorbuffer),
 | |
|                          "Failed to parse configuration file %s. Could not open file.", file);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 snprintf(errorbuffer, sizeof(errorbuffer),
 | |
|                          "Failed to parse configuration file %s. Memory allocation failed.", file);
 | |
|             }
 | |
| 
 | |
|             MXS_ERROR("%s", errorbuffer);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Check this after reading config is finished */
 | |
|     if ((gateway.writeq_high_water || gateway.writeq_low_water) &&
 | |
|         gateway.writeq_high_water <= gateway.writeq_low_water)
 | |
|     {
 | |
|         rval = -1;
 | |
|         MXS_ERROR("Invaild configuration, writeq_high_water should be greater than writeq_low_water");
 | |
|     }
 | |
| 
 | |
|     return rval == 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The current parsing contexts must be managed explicitly since the ftw callback
 | |
|  * can not have user data.
 | |
|  */
 | |
| static CONFIG_CONTEXT *current_ccontext;
 | |
| static DUPLICATE_CONTEXT *current_dcontext;
 | |
| 
 | |
| /**
 | |
|  * The nftw callback.
 | |
|  *
 | |
|  * @see man ftw
 | |
|  */
 | |
| int config_cb(const char* fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
 | |
| {
 | |
|     int rval = 0;
 | |
| 
 | |
|     if (typeflag == FTW_SL) // A symbolic link; let's see what it points to.
 | |
|     {
 | |
|         struct stat sb;
 | |
| 
 | |
|         if (stat(fpath, &sb) == 0)
 | |
|         {
 | |
|             int file_type = (sb.st_mode & S_IFMT);
 | |
| 
 | |
|             switch (file_type)
 | |
|             {
 | |
|             case S_IFREG:
 | |
|                 // Points to a file; we'll handle that regardless of where the file resides.
 | |
|                 typeflag = FTW_F;
 | |
|                 break;
 | |
| 
 | |
|             case S_IFDIR:
 | |
|                 // Points to a directory; we'll ignore that.
 | |
|                 MXS_WARNING("Symbolic link %s in configuration directory points to a "
 | |
|                             "directory; it will be ignored.", fpath);
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 // Points to something else; we'll silently ignore.
 | |
|                 ;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("Could not get information about the symbolic link %s; "
 | |
|                         "it will be ignored.", fpath);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (typeflag == FTW_F) // We are only interested in files,
 | |
|     {
 | |
|         const char* filename = fpath + ftwbuf->base;
 | |
|         const char* dot = strrchr(filename, '.');
 | |
| 
 | |
|         if (dot) // that must have a suffix,
 | |
|         {
 | |
|             const char* suffix = dot + 1;
 | |
| 
 | |
|             if (strcmp(suffix, "cnf") == 0) // that is ".cnf".
 | |
|             {
 | |
|                 ss_dassert(current_dcontext);
 | |
|                 ss_dassert(current_ccontext);
 | |
| 
 | |
|                 if (!config_load_single_file(fpath, current_dcontext, current_ccontext))
 | |
|                 {
 | |
|                     rval = -1;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Loads all configuration files in a directory hierarchy.
 | |
|  *
 | |
|  * Only files with the suffix ".cnf" are considered to be configuration files.
 | |
|  *
 | |
|  * @param dir      The directory.
 | |
|  * @param dcontext The duplicate section context.
 | |
|  * @param ccontext The configuration context.
 | |
|  *
 | |
|  * @return True, if all configuration files in the directory hierarchy could be loaded,
 | |
|  *         otherwise false.
 | |
|  */
 | |
| static bool config_load_dir(const char *dir, DUPLICATE_CONTEXT *dcontext, CONFIG_CONTEXT *ccontext)
 | |
| {
 | |
|     // Since there is no way to pass userdata to the callback, we need to store
 | |
|     // the current context into a static variable. Consequently, we need lock.
 | |
|     // Should not matter since config_load() is called once at startup.
 | |
|     static SPINLOCK lock = SPINLOCK_INIT;
 | |
| 
 | |
|     int nopenfd = 5; // Maximum concurrently opened directory descriptors
 | |
| 
 | |
|     spinlock_acquire(&lock);
 | |
|     current_dcontext = dcontext;
 | |
|     current_ccontext = ccontext;
 | |
|     int rv = nftw(dir, config_cb, nopenfd, FTW_PHYS);
 | |
|     current_ccontext = NULL;
 | |
|     current_dcontext = NULL;
 | |
|     spinlock_release(&lock);
 | |
| 
 | |
|     return rv == 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if a directory exists
 | |
|  *
 | |
|  * This function also logs warnings if the directory cannot be accessed or if
 | |
|  * the file is not a directory.
 | |
|  * @param dir Directory to check
 | |
|  * @return True if the file is an existing directory
 | |
|  */
 | |
| static bool is_directory(const char *dir)
 | |
| {
 | |
|     bool rval = false;
 | |
|     struct stat st;
 | |
|     if (stat(dir, &st) == -1)
 | |
|     {
 | |
|         if (errno == ENOENT)
 | |
|         {
 | |
|             MXS_NOTICE("%s does not exist, not reading.", dir);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("Could not access %s, not reading: %s",
 | |
|                         dir, mxs_strerror(errno));
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (S_ISDIR(st.st_mode))
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("%s exists, but it is not a directory. Ignoring.", dir);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Check if a directory contains .cnf files
 | |
|  *
 | |
|  * @param path Path to a directory
 | |
|  * @return True if the directory contained one or more .cnf files
 | |
|  */
 | |
| static bool contains_cnf_files(const char *path)
 | |
| {
 | |
|     bool rval = false;
 | |
|     glob_t matches;
 | |
|     const char suffix[] = "/*.cnf";
 | |
|     char pattern[strlen(path) + sizeof(suffix)];
 | |
| 
 | |
|     strcpy(pattern, path);
 | |
|     strcat(pattern, suffix);
 | |
|     int rc = glob(pattern, GLOB_NOSORT, NULL, &matches);
 | |
| 
 | |
|     switch (rc)
 | |
|     {
 | |
|     case 0:
 | |
|         rval = true;
 | |
|         break;
 | |
| 
 | |
|     case GLOB_NOSPACE:
 | |
|         MXS_OOM();
 | |
|         break;
 | |
| 
 | |
|     case GLOB_ABORTED:
 | |
|         MXS_ERROR("Failed to read directory '%s'", path);
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         ss_dassert(rc == GLOB_NOMATCH);
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     globfree(&matches);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Load the specified configuration file for MaxScale
 | |
|  *
 | |
|  * This function will parse the configuration file, check for duplicate sections,
 | |
|  * validate the module parameters and finally turn it into a set of objects.
 | |
|  *
 | |
|  * @param filename        The filename of the configuration file
 | |
|  * @param process_config  The function using which the successfully loaded
 | |
|  *                        configuration should be processed.
 | |
|  *
 | |
|  * @return True on success, false on fatal error
 | |
|  */
 | |
| static bool
 | |
| config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONTEXT*))
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     DUPLICATE_CONTEXT dcontext;
 | |
| 
 | |
|     if (duplicate_context_init(&dcontext))
 | |
|     {
 | |
|         CONFIG_CONTEXT ccontext = {};
 | |
|         ccontext.object = (char*)"";
 | |
| 
 | |
|         if (config_load_single_file(filename, &dcontext, &ccontext))
 | |
|         {
 | |
|             const char DIR_SUFFIX[] = ".d";
 | |
| 
 | |
|             char dir[strlen(filename) + sizeof(DIR_SUFFIX)];
 | |
|             strcpy(dir, filename);
 | |
|             strcat(dir, DIR_SUFFIX);
 | |
| 
 | |
|             rval = true;
 | |
| 
 | |
|             if (is_directory(dir))
 | |
|             {
 | |
|                 rval = config_load_dir(dir, &dcontext, &ccontext);
 | |
|             }
 | |
| 
 | |
|             /** Create the persisted configuration directory if it doesn't exist */
 | |
|             const char* persist_cnf = get_config_persistdir();
 | |
|             mxs_mkdir_all(persist_cnf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
 | |
| 
 | |
|             if (is_directory(persist_cnf) && contains_cnf_files(persist_cnf))
 | |
|             {
 | |
|                 /**
 | |
|                  * Set the global flag that we are processing a persisted configuration.
 | |
|                  * This will tell the modules whether it is OK to completely overwrite
 | |
|                  * the persisted configuration when changes are made.
 | |
|                  *
 | |
|                  * TODO: Figure out a cleaner way to do this
 | |
|                  */
 | |
|                 is_persisted_config = true;
 | |
| 
 | |
|                 MXS_NOTICE("Loading generated configuration files from '%s'", persist_cnf);
 | |
|                 DUPLICATE_CONTEXT p_dcontext;
 | |
|                 /**
 | |
|                  * We need to initialize a second duplicate context for the
 | |
|                  * generated configuration files as the monitors and services will
 | |
|                  * have duplicate sections. The duplicate sections are used to
 | |
|                  * store changes to the list of servers the services and monitors
 | |
|                  * use, and thus should not be treated as errors.
 | |
|                  */
 | |
|                 if (duplicate_context_init(&p_dcontext))
 | |
|                 {
 | |
|                     rval = config_load_dir(persist_cnf, &p_dcontext, &ccontext);
 | |
|                     duplicate_context_finish(&p_dcontext);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     rval = false;
 | |
|                 }
 | |
|                 is_persisted_config = false;
 | |
|             }
 | |
| 
 | |
|             if (rval)
 | |
|             {
 | |
|                 if (!check_config_objects(ccontext.next) || !process_config(ccontext.next))
 | |
|                 {
 | |
|                     rval = false;
 | |
|                     if (contains_cnf_files(persist_cnf))
 | |
|                     {
 | |
|                         MXS_WARNING("One or more generated configurations were found at '%s'. "
 | |
|                                     "If the error relates to any of the files located there, "
 | |
|                                     "remove the offending configurations from this directory.",
 | |
|                                     persist_cnf);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         config_context_free(ccontext.next);
 | |
| 
 | |
|         duplicate_context_finish(&dcontext);
 | |
|     }
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Load the configuration file for the MaxScale
 | |
|  *
 | |
|  * @param filename The filename of the configuration file
 | |
|  * @return True on success, false on fatal error
 | |
|  */
 | |
| bool
 | |
| config_load(const char *filename)
 | |
| {
 | |
|     ss_dassert(!config_file);
 | |
| 
 | |
|     config_file = filename;
 | |
|     bool rval = config_load_and_process(filename, process_config_context);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reload the configuration file for the MaxScale
 | |
|  *
 | |
|  * @return True on success, false on fatal error.
 | |
|  */
 | |
| bool config_reload()
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (config_file)
 | |
|     {
 | |
|         if (gateway.version_string)
 | |
|         {
 | |
|             MXS_FREE(gateway.version_string);
 | |
|         }
 | |
| 
 | |
|         rval = config_load_and_process(config_file, process_config_update);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("config_reload() called without the configuration having "
 | |
|                   "been loaded first.");
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Process a configuration context and turn it into the set of objects
 | |
|  *
 | |
|  * @param context The parsed configuration context
 | |
|  * @return False on fatal error, true on success
 | |
|  */
 | |
| static bool
 | |
| process_config_context(CONFIG_CONTEXT *context)
 | |
| {
 | |
|     CONFIG_CONTEXT  *obj;
 | |
|     int             error_count = 0;
 | |
|     HASHTABLE*      monitorhash;
 | |
| 
 | |
|     if ((monitorhash = hashtable_alloc(5, hashtable_item_strhash, hashtable_item_strcmp)) == NULL)
 | |
|     {
 | |
|         MXS_ERROR("Failed to allocate, monitor configuration check hashtable.");
 | |
|         return 0;
 | |
|     }
 | |
|     hashtable_memory_fns(monitorhash, hashtable_item_strdup, NULL, hashtable_item_free, NULL);
 | |
| 
 | |
|     /**
 | |
|      * Process the data and create the services and servers defined
 | |
|      * in the data.
 | |
|      */
 | |
|     obj = context;
 | |
|     while (obj)
 | |
|     {
 | |
|         char *type = config_get_value(obj->parameters, CN_TYPE);
 | |
|         if (type)
 | |
|         {
 | |
|             if (!strcmp(type, CN_SERVICE))
 | |
|             {
 | |
|                 error_count += create_new_service(obj);
 | |
|             }
 | |
|             else if (!strcmp(type, "server"))
 | |
|             {
 | |
|                 error_count += create_new_server(obj);
 | |
|             }
 | |
|             else if (!strcmp(type, "filter"))
 | |
|             {
 | |
|                 error_count += create_new_filter(obj);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Configuration object '%s' has no type.", obj->object);
 | |
|             error_count++;
 | |
|         }
 | |
|         obj = obj->next;
 | |
|     }
 | |
| 
 | |
|     if (error_count == 0)
 | |
|     {
 | |
|         /*
 | |
|          * Now we have created the services, servers and filters and we can add the
 | |
|          * servers and filters to the services. Monitors are also created at this point
 | |
|          * because they require a set of servers to monitor.
 | |
|          */
 | |
|         obj = context;
 | |
|         while (obj)
 | |
|         {
 | |
|             char *type = config_get_value(obj->parameters, CN_TYPE);
 | |
|             if (type)
 | |
|             {
 | |
|                 if (!strcmp(type, CN_SERVICE))
 | |
|                 {
 | |
|                     error_count += configure_new_service(context, obj);
 | |
|                 }
 | |
|                 else if (!strcmp(type, CN_LISTENER))
 | |
|                 {
 | |
|                     error_count += create_new_listener(obj);
 | |
|                 }
 | |
|                 else if (!strcmp(type, CN_MONITOR))
 | |
|                 {
 | |
|                     error_count += create_new_monitor(context, obj, monitorhash);
 | |
|                 }
 | |
|                 else if (strcmp(type, CN_SERVER) != 0 && strcmp(type, CN_FILTER) != 0)
 | |
|                 {
 | |
|                     MXS_ERROR("Configuration object '%s' has an invalid type specified.",
 | |
|                               obj->object);
 | |
|                     error_count++;
 | |
|                 }
 | |
|             }
 | |
|             obj = obj->next;
 | |
|         }
 | |
|     }
 | |
|     /** TODO: consistency check function */
 | |
| 
 | |
|     hashtable_free(monitorhash);
 | |
|     /**
 | |
|      * error_count += consistency_checks();
 | |
|      */
 | |
| 
 | |
| #ifdef REQUIRE_LISTENERS
 | |
|     if (!service_all_services_have_listeners())
 | |
|     {
 | |
|         error_count++;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (error_count)
 | |
|     {
 | |
|         MXS_ERROR("%d errors were encountered while processing the configuration "
 | |
|                   "file '%s'.", error_count, config_file);
 | |
|     }
 | |
| 
 | |
|     return error_count == 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the value of a config parameter
 | |
|  *
 | |
|  * @param params        The linked list of config parameters
 | |
|  * @param name          The parameter to return
 | |
|  * @return the parameter value or NULL if not found
 | |
|  */
 | |
| static char *
 | |
| config_get_value(MXS_CONFIG_PARAMETER *params, const char *name)
 | |
| {
 | |
|     while (params)
 | |
|     {
 | |
|         if (!strcmp(params->name, name))
 | |
|         {
 | |
|             return params->value;
 | |
|         }
 | |
| 
 | |
|         params = params->next;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| // DEPRECATE: In 2.1 complain but accept if "passwd" is provided, in 2.2
 | |
| // DEPRECATE: drop support for "passwd".
 | |
| /**
 | |
|  * Get the value of the password parameter
 | |
|  *
 | |
|  * The words looked for are "password" and "passwd".
 | |
|  *
 | |
|  * @param params        The linked list of config parameters
 | |
|  * @return the parameter value or NULL if not found
 | |
|  */
 | |
| static char *
 | |
| config_get_password(MXS_CONFIG_PARAMETER *params)
 | |
| {
 | |
|     char *password = config_get_value(params, CN_PASSWORD);
 | |
|     char *passwd = config_get_value(params, "passwd");
 | |
| 
 | |
|     if (password && passwd)
 | |
|     {
 | |
|         MXS_WARNING("Both 'password' and 'passwd' specified. Using value of 'password'.");
 | |
|     }
 | |
| 
 | |
|     return passwd ? passwd : password;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the value of a config parameter as a string
 | |
|  *
 | |
|  * @param params        The linked list of config parameters
 | |
|  * @param name          The parameter to return
 | |
|  * @return the parameter value or null string if not found
 | |
|  */
 | |
| static const char* config_get_value_string(const MXS_CONFIG_PARAMETER *params, const char *name)
 | |
| {
 | |
|     while (params)
 | |
|     {
 | |
|         if (!strcmp(params->name, name))
 | |
|         {
 | |
|             return (const char *)params->value;
 | |
|         }
 | |
| 
 | |
|         params = params->next;
 | |
|     }
 | |
|     return "";
 | |
| }
 | |
| 
 | |
| MXS_CONFIG_PARAMETER* config_get_param(MXS_CONFIG_PARAMETER* params, const char* name)
 | |
| {
 | |
|     while (params)
 | |
|     {
 | |
|         if (!strcmp(params->name, name))
 | |
|         {
 | |
|             return params;
 | |
|         }
 | |
| 
 | |
|         params = params->next;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| bool config_get_bool(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     return *value ? config_truth_value(value) : false;
 | |
| }
 | |
| 
 | |
| int config_get_integer(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     return *value ? strtol(value, NULL, 10) : 0;
 | |
| }
 | |
| 
 | |
| uint64_t config_get_size(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
| 
 | |
|     return get_suffixed_size(value);
 | |
| }
 | |
| 
 | |
| const char* config_get_string(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     return config_get_value_string(params, key);
 | |
| }
 | |
| 
 | |
| int config_get_enum(const MXS_CONFIG_PARAMETER *params, const char *key, const MXS_ENUM_VALUE *enum_values)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     char tmp_val[strlen(value) + 1];
 | |
|     strcpy(tmp_val, value);
 | |
| 
 | |
|     int rv = 0;
 | |
|     bool found = false;
 | |
|     char *endptr;
 | |
|     const char *delim = ", \t";
 | |
|     char *tok = strtok_r(tmp_val, delim, &endptr);
 | |
| 
 | |
|     while (tok)
 | |
|     {
 | |
|         for (int i = 0; enum_values[i].name; i++)
 | |
|         {
 | |
|             if (strcmp(enum_values[i].name, tok) == 0)
 | |
|             {
 | |
|                 found = true;
 | |
|                 rv |= enum_values[i].enum_value;
 | |
|             }
 | |
|         }
 | |
|         tok = strtok_r(NULL, delim, &endptr);
 | |
|     }
 | |
| 
 | |
|     return found ? rv : -1;
 | |
| }
 | |
| 
 | |
| SERVICE* config_get_service(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     return service_find(value);
 | |
| }
 | |
| 
 | |
| SERVER* config_get_server(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     return server_find_by_unique_name(value);
 | |
| }
 | |
| 
 | |
| int config_get_server_list(const MXS_CONFIG_PARAMETER *params, const char *key,
 | |
|                            SERVER*** output)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
|     char **server_names = NULL;
 | |
|     int found = 0;
 | |
|     const int n_names = config_parse_server_list(value, &server_names);
 | |
|     if (n_names > 0)
 | |
|     {
 | |
|         SERVER** servers;
 | |
|         found = server_find_by_unique_names(server_names, n_names, &servers);
 | |
|         for (int i = 0; i < n_names; i++)
 | |
|         {
 | |
|             MXS_FREE(server_names[i]);
 | |
|         }
 | |
|         MXS_FREE(server_names);
 | |
| 
 | |
|         if (found)
 | |
|         {
 | |
|             /* Fill in the result array */
 | |
|             SERVER** result = (SERVER**)MXS_CALLOC(found, sizeof(SERVER*));
 | |
|             if (result)
 | |
|             {
 | |
|                 int res_ind = 0;
 | |
|                 for (int i = 0; i < n_names; i++)
 | |
|                 {
 | |
|                     if (servers[i])
 | |
|                     {
 | |
|                         result[res_ind] = servers[i];
 | |
|                         res_ind++;
 | |
|                     }
 | |
|                 }
 | |
|                 *output = result;
 | |
|                 ss_dassert(found == res_ind);
 | |
|             }
 | |
|             MXS_FREE(servers);
 | |
|         }
 | |
|     }
 | |
|     return found;
 | |
| }
 | |
| 
 | |
| char* config_copy_string(const MXS_CONFIG_PARAMETER *params, const char *key)
 | |
| {
 | |
|     const char *value = config_get_value_string(params, key);
 | |
| 
 | |
|     char *rval = NULL;
 | |
| 
 | |
|     if (*value)
 | |
|     {
 | |
|         rval = MXS_STRDUP_A(value);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params,
 | |
|                                       const char *key, uint32_t options,
 | |
|                                       uint32_t* output_ovec_size)
 | |
| {
 | |
|     const char* regex_string = config_get_string(params, key);
 | |
|     pcre2_code* code = NULL;
 | |
| 
 | |
|     if (*regex_string)
 | |
|     {
 | |
|         uint32_t jit_available = 0;
 | |
|         pcre2_config(PCRE2_CONFIG_JIT, &jit_available);
 | |
|         code = compile_regex_string(regex_string, jit_available, options, output_ovec_size);
 | |
|     }
 | |
| 
 | |
|     return code;
 | |
| }
 | |
| 
 | |
| bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params,
 | |
|                                  const char* keys[], int keys_size,
 | |
|                                  uint32_t options, uint32_t* out_ovec_size,
 | |
|                                  pcre2_code** out_codes[])
 | |
| {
 | |
|     bool rval = true;
 | |
|     uint32_t max_ovec_size = 0;
 | |
|     uint32_t ovec_size_temp = 0;
 | |
|     for (int i = 0; i < keys_size; i++)
 | |
|     {
 | |
|         ss_dassert(out_codes[i]);
 | |
|         *out_codes[i] = config_get_compiled_regex(params, keys[i], options,
 | |
|                                                   &ovec_size_temp);
 | |
|         if (*out_codes[i])
 | |
|         {
 | |
|             if (ovec_size_temp > max_ovec_size)
 | |
|             {
 | |
|                 max_ovec_size = ovec_size_temp;
 | |
|             }
 | |
|         }
 | |
|         /* config_get_compiled_regex() returns null also if the config setting
 | |
|          * didn't exist. Check that before setting error state. */
 | |
|         else if (*(config_get_value_string(params, keys[i])))
 | |
|         {
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
|     if (out_ovec_size)
 | |
|     {
 | |
|         *out_ovec_size = max_ovec_size;
 | |
|     }
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| MXS_CONFIG_PARAMETER* config_clone_param(const MXS_CONFIG_PARAMETER* param)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER *p2 = (MXS_CONFIG_PARAMETER*)MXS_MALLOC(sizeof(MXS_CONFIG_PARAMETER));
 | |
| 
 | |
|     if (p2)
 | |
|     {
 | |
|         p2->name = MXS_STRDUP_A(param->name);
 | |
|         p2->value = MXS_STRDUP_A(param->value);
 | |
|         p2->next = NULL;
 | |
|     }
 | |
| 
 | |
|     return p2;
 | |
| }
 | |
| 
 | |
| void config_free_one_param(MXS_CONFIG_PARAMETER* p1)
 | |
| {
 | |
|     if (p1)
 | |
|     {
 | |
|         MXS_FREE(p1->name);
 | |
|         MXS_FREE(p1->value);
 | |
|         MXS_FREE(p1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free a configuration parameter
 | |
|  * @param p1 Parameter to free
 | |
|  */
 | |
| void config_parameter_free(MXS_CONFIG_PARAMETER* p1)
 | |
| {
 | |
|     while (p1)
 | |
|     {
 | |
|         MXS_CONFIG_PARAMETER* tmp = p1;
 | |
|         p1 = p1->next;
 | |
|         config_free_one_param(tmp);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void config_context_free(CONFIG_CONTEXT *context)
 | |
| {
 | |
|     CONFIG_CONTEXT   *obj;
 | |
| 
 | |
|     while (context)
 | |
|     {
 | |
|         obj = context->next;
 | |
|         config_parameter_free(context->parameters);
 | |
|         MXS_FREE(context->object);
 | |
|         MXS_FREE(context);
 | |
|         context = obj;
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool config_add_param(CONFIG_CONTEXT* obj, const char* key, const char* value)
 | |
| {
 | |
|     ss_dassert(config_get_param(obj->parameters, key) == NULL);
 | |
|     bool rval = false;
 | |
|     char *my_key = MXS_STRDUP(key);
 | |
|     char *my_value = MXS_STRDUP(value);
 | |
|     MXS_CONFIG_PARAMETER* param = (MXS_CONFIG_PARAMETER *)MXS_MALLOC(sizeof(*param));
 | |
| 
 | |
|     if (my_key && my_value && param)
 | |
|     {
 | |
|         param->name = my_key;
 | |
|         param->value = my_value;
 | |
|         param->next = obj->parameters;
 | |
|         obj->parameters = param;
 | |
|         rval = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_FREE(my_key);
 | |
|         MXS_FREE(my_value);
 | |
|         MXS_FREE(param);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool config_append_param(CONFIG_CONTEXT* obj, const char* key, const char* value)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER *param = config_get_param(obj->parameters, key);
 | |
|     ss_dassert(param);
 | |
|     int paramlen = strlen(param->value) + strlen(value) + 2;
 | |
|     char tmp[paramlen];
 | |
|     bool rval = false;
 | |
| 
 | |
|     strcpy(tmp, param->value);
 | |
|     strcat(tmp, ",");
 | |
|     strcat(tmp, value);
 | |
| 
 | |
|     char *new_value = config_clean_string_list(tmp);
 | |
| 
 | |
|     if (new_value)
 | |
|     {
 | |
|         MXS_FREE(param->value);
 | |
|         param->value = new_value;
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool config_replace_param(CONFIG_CONTEXT* obj, const char* key, const char* value)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER *param = config_get_param(obj->parameters, key);
 | |
|     ss_dassert(param);
 | |
|     char *new_value = MXS_STRDUP(value);
 | |
|     bool rval;
 | |
| 
 | |
|     if (new_value)
 | |
|     {
 | |
|         MXS_FREE(param->value);
 | |
|         param->value = new_value;
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| void config_remove_param(CONFIG_CONTEXT* obj, const char* name)
 | |
| {
 | |
|     if (obj->parameters && strcmp(obj->parameters->name, name) == 0)
 | |
|     {
 | |
|         MXS_CONFIG_PARAMETER* tmp = obj->parameters;
 | |
|         obj->parameters = obj->parameters->next;
 | |
|         config_free_one_param(tmp);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         for (MXS_CONFIG_PARAMETER* p = obj->parameters; p && p->next; p = p->next)
 | |
|         {
 | |
|             if (strcmp(p->next->name, name) == 0)
 | |
|             {
 | |
|                 MXS_CONFIG_PARAMETER* tmp = p->next;
 | |
|                 p->next = tmp->next;
 | |
|                 config_free_one_param(tmp);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the number of configured threads
 | |
|  *
 | |
|  * @return The number of threads configured in the config file
 | |
|  */
 | |
| int
 | |
| config_threadcount()
 | |
| {
 | |
|     return gateway.n_threads;
 | |
| }
 | |
| 
 | |
| size_t config_thread_stack_size()
 | |
| {
 | |
|     return gateway.thread_stack_size;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the number of non-blocking polls to be done before a blocking poll
 | |
|  * is issued.
 | |
|  *
 | |
|  * @return The number of blocking poll calls to make before a blocking call
 | |
|  */
 | |
| unsigned int
 | |
| config_nbpolls()
 | |
| {
 | |
|     return gateway.n_nbpoll;
 | |
| }
 | |
| 
 | |
| uint32_t config_writeq_high_water()
 | |
| {
 | |
|     return gateway.writeq_high_water;
 | |
| }
 | |
| 
 | |
| uint32_t config_writeq_low_water()
 | |
| {
 | |
|     return gateway.writeq_low_water;
 | |
| }
 | |
| /**
 | |
|  * Return the configured number of milliseconds for which we wait when we do
 | |
|  * a blocking poll call.
 | |
|  *
 | |
|  * @return The number of milliseconds to sleep in a blocking poll call
 | |
|  */
 | |
| unsigned int
 | |
| config_pollsleep()
 | |
| {
 | |
|     return gateway.pollsleep;
 | |
| }
 | |
| 
 | |
| static struct
 | |
| {
 | |
|     const char* name;
 | |
|     int         priority;
 | |
|     const char* replacement;
 | |
| } lognames[] =
 | |
| {
 | |
|     { "log_messages", LOG_NOTICE,  "log_notice" }, // Deprecated
 | |
|     { "log_trace",    LOG_INFO,    "log_info" },   // Deprecated
 | |
|     { "log_debug",    LOG_DEBUG,   NULL },
 | |
|     { "log_warning",  LOG_WARNING, NULL },
 | |
|     { "log_notice",   LOG_NOTICE,  NULL },
 | |
|     { "log_info",     LOG_INFO,    NULL },
 | |
|     { NULL, 0 }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Configuration handler for items in the global [MaxScale] section
 | |
|  *
 | |
|  * @param name  The item name
 | |
|  * @param value The item value
 | |
|  * @return 0 on error
 | |
|  */
 | |
| static  int
 | |
| handle_global_item(const char *name, const char *value)
 | |
| {
 | |
|     bool processed = true; // assume 'name' is valid
 | |
| 
 | |
|     int i;
 | |
|     if (strcmp(name, CN_THREADS) == 0)
 | |
|     {
 | |
|         if (strcmp(value, CN_AUTO) == 0)
 | |
|         {
 | |
|             gateway.n_threads = get_processor_count();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             int thrcount = atoi(value);
 | |
|             if (thrcount > 0)
 | |
|             {
 | |
|                 gateway.n_threads = thrcount;
 | |
| 
 | |
|                 int processor_count = get_processor_count();
 | |
|                 if (thrcount > processor_count)
 | |
|                 {
 | |
|                     MXS_WARNING("Number of threads set to %d, which is greater than "
 | |
|                                 "the number of processors available: %d",
 | |
|                                 thrcount, processor_count);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_WARNING("Invalid value for 'threads': %s.", value);
 | |
|                 return 0;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (gateway.n_threads > MXS_MAX_ROUTING_THREADS)
 | |
|         {
 | |
|             MXS_WARNING("Number of threads set to %d, which is greater than the "
 | |
|                         "hard maximum of %d. Number of threads adjusted down "
 | |
|                         "accordingly.", gateway.n_threads, MXS_MAX_ROUTING_THREADS);
 | |
|             gateway.n_threads = MXS_MAX_ROUTING_THREADS;
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_THREAD_STACK_SIZE) == 0)
 | |
|     {
 | |
|         gateway.thread_stack_size = get_suffixed_size(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_NON_BLOCKING_POLLS) == 0)
 | |
|     {
 | |
|         // DEPRECATED in 2.3, remove in 2.4
 | |
|         MXS_WARNING("The configuration option '%s' has no meaning and has been deprecated.",
 | |
|                     CN_NON_BLOCKING_POLLS);
 | |
|         gateway.n_nbpoll = atoi(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_POLL_SLEEP) == 0)
 | |
|     {
 | |
|         // DEPRECATED in 2.3, remove in 2.4
 | |
|         MXS_WARNING("The configuration option '%s' has no meaning and has been deprecated.",
 | |
|                     CN_POLL_SLEEP);
 | |
|         gateway.pollsleep = atoi(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_MS_TIMESTAMP) == 0)
 | |
|     {
 | |
|         mxs_log_set_highprecision_enabled(config_truth_value((char*)value));
 | |
|     }
 | |
|     else if (strcmp(name, CN_SKIP_PERMISSION_CHECKS) == 0)
 | |
|     {
 | |
|         gateway.skip_permission_checks = config_truth_value((char*)value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_AUTH_CONNECT_TIMEOUT) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval > 0)
 | |
|         {
 | |
|             gateway.auth_conn_timeout = intval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("Invalid timeout value for 'auth_connect_timeout': %s", value);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_AUTH_READ_TIMEOUT) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval > 0)
 | |
|         {
 | |
|             gateway.auth_read_timeout = intval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid timeout value for 'auth_read_timeout': %s", value);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_AUTH_WRITE_TIMEOUT) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval > 0)
 | |
|         {
 | |
|             gateway.auth_write_timeout = intval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid timeout value for 'auth_write_timeout': %s", value);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_QUERY_CLASSIFIER) == 0)
 | |
|     {
 | |
|         int len = strlen(value);
 | |
|         int max_len = sizeof(gateway.qc_name) - 1;
 | |
| 
 | |
|         if (len <= max_len)
 | |
|         {
 | |
|             strcpy(gateway.qc_name, value);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("The length of '%s' is %d, while the maximum length is %d.",
 | |
|                       value, len, max_len);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_QUERY_CLASSIFIER_ARGS) == 0)
 | |
|     {
 | |
|         gateway.qc_args = MXS_STRDUP_A(value);
 | |
|     }
 | |
|     else if (strcmp(name, "sql_mode") == 0)
 | |
|     {
 | |
|         if (strcasecmp(value, "default") == 0)
 | |
|         {
 | |
|             gateway.qc_sql_mode = QC_SQL_MODE_DEFAULT;
 | |
|         }
 | |
|         else if (strcasecmp(value, "oracle") == 0)
 | |
|         {
 | |
|             gateway.qc_sql_mode = QC_SQL_MODE_ORACLE;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("'%s' is not a valid value for '%s'. Allowed values are 'DEFAULT' and "
 | |
|                       "'ORACLE'. Using 'DEFAULT' as default.", value, name);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_QUERY_RETRIES) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval >= 0)
 | |
|         {
 | |
|             gateway.query_retries = intval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid timeout value for '%s': %s", CN_QUERY_RETRIES, value);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_QUERY_RETRY_TIMEOUT) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval > 0)
 | |
|         {
 | |
|             gateway.query_retries = intval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid timeout value for '%s': %s", CN_QUERY_RETRY_TIMEOUT, value);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_LOG_THROTTLING) == 0)
 | |
|     {
 | |
|         if (*value == 0)
 | |
|         {
 | |
|             MXS_LOG_THROTTLING throttling = { 0, 0, 0 };
 | |
| 
 | |
|             mxs_log_set_throttling(&throttling);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             char *v = MXS_STRDUP_A(value);
 | |
| 
 | |
|             char *count = v;
 | |
|             char *window_ms = NULL;
 | |
|             char *suppress_ms = NULL;
 | |
| 
 | |
|             window_ms = strchr(count, ',');
 | |
|             if (window_ms)
 | |
|             {
 | |
|                 *window_ms = 0;
 | |
|                 ++window_ms;
 | |
| 
 | |
|                 suppress_ms = strchr(window_ms, ',');
 | |
|                 if (suppress_ms)
 | |
|                 {
 | |
|                     *suppress_ms = 0;
 | |
|                     ++suppress_ms;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (!count || !window_ms || !suppress_ms)
 | |
|             {
 | |
|                 MXS_ERROR("Invalid value for the `log_throttling` configuration entry: \"%s\". "
 | |
|                           "No throttling will now be performed.", value);
 | |
|                 MXS_NOTICE("The format of the value for 'log_throttling' is \"X, Y, Z\", where "
 | |
|                            "X is the maximum number of times a particular error can be logged "
 | |
|                            "in the time window of Y milliseconds, before the logging is suppressed "
 | |
|                            "for Z milliseconds.");
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 int c = atoi(count);
 | |
|                 int w = atoi(window_ms);
 | |
|                 int s = atoi(suppress_ms);
 | |
| 
 | |
|                 if ((c >= 0) && (w >= 0) && (s >= 0))
 | |
|                 {
 | |
|                     MXS_LOG_THROTTLING throttling;
 | |
|                     throttling.count = c;
 | |
|                     throttling.window_ms = w;
 | |
|                     throttling.suppress_ms = s;
 | |
| 
 | |
|                     mxs_log_set_throttling(&throttling);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_ERROR("Invalid value for the `log_throttling` configuration entry: \"%s\". "
 | |
|                               "No throttling will now be performed.", value);
 | |
|                     MXS_NOTICE("The configuration entry 'log_throttling' requires as value three positive "
 | |
|                                "integers (or 0).");
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             MXS_FREE(v);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_PORT) == 0)
 | |
|     {
 | |
|         gateway.admin_port = atoi(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_HOST) == 0)
 | |
|     {
 | |
|         strcpy(gateway.admin_host, value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_SSL_KEY) == 0)
 | |
|     {
 | |
|         strcpy(gateway.admin_ssl_key, value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_SSL_CERT) == 0)
 | |
|     {
 | |
|         strcpy(gateway.admin_ssl_cert, value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_SSL_CA_CERT) == 0)
 | |
|     {
 | |
|         strcpy(gateway.admin_ssl_ca_cert, value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_AUTH) == 0)
 | |
|     {
 | |
|         gateway.admin_auth = config_truth_value(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_ENABLED) == 0)
 | |
|     {
 | |
|         gateway.admin_enabled = config_truth_value(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_ADMIN_LOG_AUTH_FAILURES) == 0)
 | |
|     {
 | |
|         gateway.admin_log_auth_failures = config_truth_value(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_PASSIVE) == 0)
 | |
|     {
 | |
|         gateway.passive = config_truth_value((char*)value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_LOCAL_ADDRESS) == 0)
 | |
|     {
 | |
|         gateway.local_address = MXS_STRDUP_A(value);
 | |
|     }
 | |
|     else if (strcmp(name, CN_USERS_REFRESH_TIME) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         long users_refresh_time = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0')
 | |
|         {
 | |
|             if (users_refresh_time < 0)
 | |
|             {
 | |
|                 MXS_NOTICE("Value of '%s' is less than 0, users will "
 | |
|                            "not be automatically refreshed.", CN_USERS_REFRESH_TIME);
 | |
|                 // Strictly speaking they will be refreshed once every 68 years,
 | |
|                 // but I just don't beleave the uptime will be that long.
 | |
|                 users_refresh_time = INT32_MAX;
 | |
|             }
 | |
|             else if (users_refresh_time < USERS_REFRESH_TIME_MIN)
 | |
|             {
 | |
|                 MXS_WARNING("%s is less than the allowed minimum value of %d for the "
 | |
|                             "configuration option '%s', using the minimum value.",
 | |
|                             value, USERS_REFRESH_TIME_MIN, CN_USERS_REFRESH_TIME);
 | |
|                 users_refresh_time = USERS_REFRESH_TIME_MIN;
 | |
|             }
 | |
| 
 | |
|             if (users_refresh_time > INT32_MAX)
 | |
|             {
 | |
|                 // To ensure that there will be no overflows when
 | |
|                 // we later do arithmetic.
 | |
|                 users_refresh_time = INT32_MAX;
 | |
|             }
 | |
| 
 | |
|             gateway.users_refresh_time = users_refresh_time;
 | |
|         }
 | |
|         else if (strcmp(name, CN_WRITEQ_HIGH_WATER) == 0)
 | |
|         {
 | |
|             gateway.writeq_high_water = get_suffixed_size(value);
 | |
|             if (gateway.writeq_high_water < MIN_WRITEQ_HIGH_WATER)
 | |
|             {
 | |
|                 MXS_WARNING("The specified writeq high water mark %d, is smaller than the minimum allowed size %d. Changing to minimum.",
 | |
|                             gateway.writeq_high_water, MIN_WRITEQ_HIGH_WATER);
 | |
|                 gateway.writeq_high_water = MIN_WRITEQ_HIGH_WATER;
 | |
|             }
 | |
|             MXS_NOTICE("Writeq high water mark set to: %d", gateway.writeq_high_water);
 | |
|         }
 | |
|         else if (strcmp(name, CN_WRITEQ_LOW_WATER) == 0)
 | |
|         {
 | |
|             gateway.writeq_low_water = get_suffixed_size(value);
 | |
|             if (gateway.writeq_low_water < MIN_WRITEQ_LOW_WATER)
 | |
|             {
 | |
|                 MXS_WARNING("The specified writeq low water mark %d, is smaller than the minimum allowed size %d. Changing to minimum.",
 | |
|                             gateway.writeq_low_water, MIN_WRITEQ_LOW_WATER);
 | |
|                 gateway.writeq_low_water = MIN_WRITEQ_LOW_WATER;
 | |
|             }
 | |
|             MXS_NOTICE("Writeq low water mark set to: %d", gateway.writeq_low_water);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("%s is an invalid value for '%s', using default %d instead.",
 | |
|                       value, CN_USERS_REFRESH_TIME, USERS_REFRESH_TIME_DEFAULT);
 | |
|             gateway.users_refresh_time = USERS_REFRESH_TIME_DEFAULT;
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_RETAIN_LAST_STATEMENTS) == 0)
 | |
|     {
 | |
|         char* endptr;
 | |
|         int intval = strtol(value, &endptr, 0);
 | |
|         if (*endptr == '\0' && intval >= 0)
 | |
|         {
 | |
|             session_set_retain_last_statements(intval);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid value for '%s': %s", CN_RETAIN_LAST_STATEMENTS, value);
 | |
|         }
 | |
|     }
 | |
|     else if (strcmp(name, CN_DUMP_LAST_STATEMENTS) == 0)
 | |
|     {
 | |
|         if (strcmp(value, "on_close") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_CLOSE);
 | |
|         }
 | |
|         else if (strcmp(value, "on_error") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_ERROR);
 | |
|         }
 | |
|         else if (strcmp(value, "never") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_NEVER);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("%s can have the values 'never', 'on_close' or 'on_error'.",
 | |
|                       CN_DUMP_LAST_STATEMENTS);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| #ifndef SS_DEBUG
 | |
|         if (strcmp(name, "log_debug") == 0)
 | |
|         {
 | |
|             MXS_WARNING("The 'log_debug' option has no effect in release mode.");
 | |
|         }
 | |
| #endif
 | |
|         bool found = false;
 | |
|         for (i = 0; lognames[i].name; i++)
 | |
|         {
 | |
|             if (strcasecmp(name, lognames[i].name) == 0)
 | |
|             {
 | |
|                 found = true;
 | |
|                 if (lognames[i].replacement)
 | |
|                 {
 | |
|                     MXS_WARNING("In the configuration file the use of '%s' is deprecated, "
 | |
|                                 "use '%s' instead.",
 | |
|                                 lognames[i].name, lognames[i].replacement);
 | |
|                 }
 | |
| 
 | |
|                 mxs_log_set_priority_enabled(lognames[i].priority, config_truth_value(value));
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         if (!found)
 | |
|         {
 | |
|             for (int i = 0; !found && config_pre_parse_global_params[i]; ++i)
 | |
|             {
 | |
|                 found = strcmp(name, config_pre_parse_global_params[i]) == 0;
 | |
|             }
 | |
|         }
 | |
|         processed = found;
 | |
|     }
 | |
| 
 | |
|     if (!processed)
 | |
|     {
 | |
|         MXS_WARNING("Config entry '%s' is unknown."
 | |
|                     " Please remove it from the configuration file.", name);
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free an SSL structure
 | |
|  *
 | |
|  * @param ssl SSL structure to free
 | |
|  */
 | |
| static void
 | |
| free_ssl_structure(SSL_LISTENER *ssl)
 | |
| {
 | |
|     if (ssl)
 | |
|     {
 | |
|         SSL_CTX_free(ssl->ctx);
 | |
|         MXS_FREE(ssl->ssl_key);
 | |
|         MXS_FREE(ssl->ssl_cert);
 | |
|         MXS_FREE(ssl->ssl_ca_cert);
 | |
|         MXS_FREE(ssl);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Form an SSL structure from listener section parameters
 | |
|  *
 | |
|  * @param obj The configuration object for the item being created
 | |
|  * @param require_cert  Whether a certificate and key are required
 | |
|  * @param *error_count  An error count which may be incremented
 | |
|  * @return SSL_LISTENER structure or NULL
 | |
|  */
 | |
| SSL_LISTENER* make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count)
 | |
| {
 | |
|     char *ssl, *ssl_version, *ssl_cert, *ssl_key, *ssl_ca_cert, *ssl_cert_verify_depth;
 | |
|     int local_errors = 0;
 | |
|     SSL_LISTENER *new_ssl;
 | |
| 
 | |
|     ssl = config_get_value(obj->parameters, CN_SSL);
 | |
| 
 | |
|     if (ssl)
 | |
|     {
 | |
|         if (!strcmp(ssl, CN_REQUIRED))
 | |
|         {
 | |
|             if ((new_ssl = (SSL_LISTENER*)MXS_CALLOC(1, sizeof(SSL_LISTENER))) == NULL)
 | |
|             {
 | |
|                 return NULL;
 | |
|             }
 | |
|             new_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX;
 | |
|             ssl_cert = config_get_value(obj->parameters, CN_SSL_CERT);
 | |
|             ssl_key = config_get_value(obj->parameters, CN_SSL_KEY);
 | |
|             ssl_ca_cert = config_get_value(obj->parameters, CN_SSL_CA_CERT);
 | |
|             ssl_version = config_get_value(obj->parameters, CN_SSL_VERSION);
 | |
|             ssl_cert_verify_depth = config_get_value(obj->parameters, CN_SSL_CERT_VERIFY_DEPTH);
 | |
|             const char* ssl_verify_peer_certificate = config_get_value(obj->parameters, CN_SSL_VERIFY_PEER_CERTIFICATE);
 | |
|             new_ssl->ssl_init_done = false;
 | |
|             new_ssl->ssl_cert_verify_depth = 9; // Default of 9 as per Linux man page
 | |
|             new_ssl->ssl_verify_peer_certificate = true;
 | |
| 
 | |
|             if (ssl_version)
 | |
|             {
 | |
|                 if (listener_set_ssl_version(new_ssl, ssl_version) != 0)
 | |
|                 {
 | |
|                     MXS_ERROR("Unknown parameter value for 'ssl_version' for"
 | |
|                               " service '%s': %s", obj->object, ssl_version);
 | |
|                     local_errors++;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (ssl_cert_verify_depth)
 | |
|             {
 | |
|                 new_ssl->ssl_cert_verify_depth = atoi(ssl_cert_verify_depth);
 | |
|                 if (new_ssl->ssl_cert_verify_depth < 0)
 | |
|                 {
 | |
|                     MXS_ERROR("Invalid parameter value for 'ssl_cert_verify_depth"
 | |
|                               " for service '%s': %s", obj->object, ssl_cert_verify_depth);
 | |
|                     new_ssl->ssl_cert_verify_depth = 0;
 | |
|                     local_errors++;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (ssl_verify_peer_certificate)
 | |
|             {
 | |
|                 int rv = config_truth_value(ssl_verify_peer_certificate);
 | |
|                 if (rv == -1)
 | |
|                 {
 | |
|                     MXS_ERROR("Invalid parameter value for 'ssl_verify_peer_certificate"
 | |
|                               " for service '%s': %s", obj->object, ssl_verify_peer_certificate);
 | |
|                     local_errors++;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     new_ssl->ssl_verify_peer_certificate = rv;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert);
 | |
| 
 | |
|             if (require_cert && new_ssl->ssl_cert == NULL)
 | |
|             {
 | |
|                 local_errors++;
 | |
|                 MXS_ERROR("Server certificate missing for service '%s'."
 | |
|                           "Please provide the path to the server certificate by adding "
 | |
|                           "the ssl_cert=<path> parameter", obj->object);
 | |
|             }
 | |
| 
 | |
|             if (require_cert && new_ssl->ssl_ca_cert == NULL)
 | |
|             {
 | |
|                 local_errors++;
 | |
|                 MXS_ERROR("CA Certificate missing for service '%s'."
 | |
|                           "Please provide the path to the certificate authority "
 | |
|                           "certificate by adding the ssl_ca_cert=<path> parameter",
 | |
|                           obj->object);
 | |
|             }
 | |
| 
 | |
|             if (require_cert && new_ssl->ssl_key == NULL)
 | |
|             {
 | |
|                 local_errors++;
 | |
|                 MXS_ERROR("Server private key missing for service '%s'. "
 | |
|                           "Please provide the path to the server certificate key by "
 | |
|                           "adding the ssl_key=<path> parameter",
 | |
|                           obj->object);
 | |
|             }
 | |
| 
 | |
|             if (require_cert && access(new_ssl->ssl_ca_cert, F_OK) != 0)
 | |
|             {
 | |
|                 MXS_ERROR("Certificate authority file for service '%s' not found: %s",
 | |
|                           obj->object,
 | |
|                           new_ssl->ssl_ca_cert);
 | |
|                 local_errors++;
 | |
|             }
 | |
| 
 | |
|             if (require_cert && access(new_ssl->ssl_cert, F_OK) != 0)
 | |
|             {
 | |
|                 MXS_ERROR("Server certificate file for service '%s' not found: %s",
 | |
|                           obj->object,
 | |
|                           new_ssl->ssl_cert);
 | |
|                 local_errors++;
 | |
|             }
 | |
| 
 | |
|             if (require_cert && access(new_ssl->ssl_key, F_OK) != 0)
 | |
|             {
 | |
|                 MXS_ERROR("Server private key file for service '%s' not found: %s",
 | |
|                           obj->object,
 | |
|                           new_ssl->ssl_key);
 | |
|                 local_errors++;
 | |
|             }
 | |
| 
 | |
|             if (0 == local_errors)
 | |
|             {
 | |
|                 return new_ssl;
 | |
|             }
 | |
|             *error_count += local_errors;
 | |
|             MXS_FREE(new_ssl);
 | |
|         }
 | |
|         else if (strcmp(ssl, "disabled") != 0)
 | |
|         {
 | |
|             MXS_ERROR("Unknown value for 'ssl': %s. Service will not use SSL.", ssl);
 | |
|         }
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| void config_set_global_defaults()
 | |
| {
 | |
|     uint8_t mac_addr[6] = "";
 | |
|     struct utsname uname_data;
 | |
|     gateway.config_check = false;
 | |
|     gateway.n_threads = DEFAULT_NTHREADS;
 | |
|     gateway.n_nbpoll = DEFAULT_NBPOLLS;
 | |
|     gateway.pollsleep = DEFAULT_POLLSLEEP;
 | |
|     gateway.auth_conn_timeout = DEFAULT_AUTH_CONNECT_TIMEOUT;
 | |
|     gateway.auth_read_timeout = DEFAULT_AUTH_READ_TIMEOUT;
 | |
|     gateway.auth_write_timeout = DEFAULT_AUTH_WRITE_TIMEOUT;
 | |
|     gateway.skip_permission_checks = false;
 | |
|     gateway.syslog = 1;
 | |
|     gateway.maxlog = 1;
 | |
|     gateway.log_to_shm = 0;
 | |
|     gateway.admin_port = DEFAULT_ADMIN_HTTP_PORT;
 | |
|     gateway.admin_auth = true;
 | |
|     gateway.admin_log_auth_failures = true;
 | |
|     gateway.admin_enabled = true;
 | |
|     strcpy(gateway.admin_host, DEFAULT_ADMIN_HOST);
 | |
|     gateway.admin_ssl_key[0] = '\0';
 | |
|     gateway.admin_ssl_cert[0] = '\0';
 | |
|     gateway.admin_ssl_ca_cert[0] = '\0';
 | |
|     gateway.query_retries = DEFAULT_QUERY_RETRIES;
 | |
|     gateway.query_retry_timeout = DEFAULT_QUERY_RETRY_TIMEOUT;
 | |
|     gateway.passive = false;
 | |
|     gateway.promoted_at = 0;
 | |
| 
 | |
|     gateway.thread_stack_size = 0;
 | |
|     gateway.writeq_high_water = 0;
 | |
|     gateway.writeq_low_water = 0;
 | |
|     pthread_attr_t attr;
 | |
|     if (pthread_attr_init(&attr) == 0)
 | |
|     {
 | |
|         size_t thread_stack_size;
 | |
|         if (pthread_attr_getstacksize(&attr, &thread_stack_size) == 0)
 | |
|         {
 | |
|             gateway.thread_stack_size = thread_stack_size;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (version_string != NULL)
 | |
|     {
 | |
|         gateway.version_string = MXS_STRDUP_A(version_string);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         gateway.version_string = NULL;
 | |
|     }
 | |
|     gateway.id = 0;
 | |
| 
 | |
|     /* get release string */
 | |
|     if (!config_get_release_string(gateway.release_string))
 | |
|     {
 | |
|         sprintf(gateway.release_string, "undefined");
 | |
|     }
 | |
| 
 | |
|     /* get first mac_address in SHA1 */
 | |
|     if (config_get_ifaddr(mac_addr))
 | |
|     {
 | |
|         gw_sha1_str(mac_addr, 6, gateway.mac_sha1);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         memset(gateway.mac_sha1, '\0', sizeof(gateway.mac_sha1));
 | |
|         memcpy(gateway.mac_sha1, "MAC-undef", 9);
 | |
|     }
 | |
| 
 | |
|     /* get uname info */
 | |
|     if (uname(&uname_data))
 | |
|     {
 | |
|         strcpy(gateway.sysname, "undefined");
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         strcpy(gateway.sysname, uname_data.sysname);
 | |
|     }
 | |
| 
 | |
|     /* query_classifier */
 | |
|     memset(gateway.qc_name, 0, sizeof(gateway.qc_name));
 | |
|     gateway.qc_args = NULL;
 | |
|     gateway.qc_sql_mode = QC_SQL_MODE_DEFAULT;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Process a configuration context update and turn it into the set of object
 | |
|  * we need.
 | |
|  *
 | |
|  * @param context       The configuration data
 | |
|  */
 | |
| static bool
 | |
| process_config_update(CONFIG_CONTEXT *context)
 | |
| {
 | |
|     CONFIG_CONTEXT *obj;
 | |
|     SERVICE        *service;
 | |
|     SERVER         *server;
 | |
| 
 | |
|     /**
 | |
|      * Process the data and create the services and servers defined
 | |
|      * in the data.
 | |
|      */
 | |
|     obj = context;
 | |
|     while (obj)
 | |
|     {
 | |
|         char *type = config_get_value(obj->parameters, CN_TYPE);
 | |
|         if (type == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Configuration object %s has no type.", obj->object);
 | |
|         }
 | |
|         else if (!strcmp(type, CN_SERVICE))
 | |
|         {
 | |
|             char *router = config_get_value(obj->parameters, CN_ROUTER);
 | |
|             if (router)
 | |
|             {
 | |
|                 if ((service = service_find(obj->object)) != NULL)
 | |
|                 {
 | |
|                     char *user;
 | |
|                     char *auth;
 | |
|                     char *enable_root_user;
 | |
| 
 | |
|                     const char *max_connections;
 | |
|                     const char *max_queued_connections;
 | |
|                     const char *queued_connection_timeout;
 | |
|                     char *connection_timeout;
 | |
| 
 | |
|                     char* auth_all_servers;
 | |
|                     char* strip_db_esc;
 | |
|                     char* max_slave_conn_str;
 | |
|                     char* max_slave_rlag_str;
 | |
|                     char *version_string;
 | |
|                     char *allow_localhost_match_wildcard_host;
 | |
| 
 | |
|                     enable_root_user = config_get_value(obj->parameters, CN_ENABLE_ROOT_USER);
 | |
| 
 | |
|                     connection_timeout = config_get_value(obj->parameters, CN_CONNECTION_TIMEOUT);
 | |
|                     max_connections = config_get_value_string(obj->parameters, CN_MAX_CONNECTIONS);
 | |
|                     max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections");
 | |
|                     queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout");
 | |
|                     user = config_get_value(obj->parameters, CN_USER);
 | |
|                     auth = config_get_password(obj->parameters);
 | |
| 
 | |
|                     auth_all_servers = config_get_value(obj->parameters, CN_AUTH_ALL_SERVERS);
 | |
|                     strip_db_esc = config_get_value(obj->parameters, CN_STRIP_DB_ESC);
 | |
|                     version_string = config_get_value(obj->parameters, CN_VERSION_STRING);
 | |
|                     allow_localhost_match_wildcard_host =
 | |
|                         config_get_value(obj->parameters, CN_LOCALHOST_MATCH_WILDCARD_HOST);
 | |
| 
 | |
|                     char *log_auth_warnings = config_get_value(obj->parameters, CN_LOG_AUTH_WARNINGS);
 | |
|                     int truthval;
 | |
|                     if (log_auth_warnings && (truthval = config_truth_value(log_auth_warnings)) != -1)
 | |
|                     {
 | |
|                         service->log_auth_warnings = (bool)truthval;
 | |
|                     }
 | |
| 
 | |
|                     if (version_string)
 | |
|                     {
 | |
|                         serviceSetVersionString(service, version_string);
 | |
|                     }
 | |
| 
 | |
|                     if (user && auth)
 | |
|                     {
 | |
|                         service_update(service, router, user, auth);
 | |
|                         if (enable_root_user)
 | |
|                         {
 | |
|                             serviceEnableRootUser(service, config_truth_value(enable_root_user));
 | |
|                         }
 | |
| 
 | |
|                         if (connection_timeout)
 | |
|                         {
 | |
|                             serviceSetTimeout(service, atoi(connection_timeout));
 | |
|                         }
 | |
| 
 | |
|                         if (strlen(max_connections))
 | |
|                         {
 | |
|                             serviceSetConnectionLimits(service,
 | |
|                                                        atoi(max_connections),
 | |
|                                                        atoi(max_queued_connections),
 | |
|                                                        atoi(queued_connection_timeout));
 | |
|                         }
 | |
| 
 | |
|                         if (auth_all_servers)
 | |
|                         {
 | |
|                             serviceAuthAllServers(service, config_truth_value(auth_all_servers));
 | |
|                         }
 | |
| 
 | |
|                         if (strip_db_esc)
 | |
|                         {
 | |
|                             serviceStripDbEsc(service, config_truth_value(strip_db_esc));
 | |
|                         }
 | |
| 
 | |
|                         if (allow_localhost_match_wildcard_host)
 | |
|                         {
 | |
|                             serviceEnableLocalhostMatchWildcardHost(
 | |
|                                 service,
 | |
|                                 config_truth_value(allow_localhost_match_wildcard_host));
 | |
|                         }
 | |
| 
 | |
|                     }
 | |
| 
 | |
|                     obj->element = service;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 obj->element = NULL;
 | |
|                 MXS_ERROR("No router defined for service '%s'.", obj->object);
 | |
|             }
 | |
|         }
 | |
|         else if (!strcmp(type, "server"))
 | |
|         {
 | |
|             char *address = config_get_value(obj->parameters, CN_ADDRESS);
 | |
|             char *port = config_get_value(obj->parameters, CN_PORT);
 | |
| 
 | |
|             if (address && port &&
 | |
|                 (server = server_find(address, atoi(port))) != NULL)
 | |
|             {
 | |
|                 char *monuser = config_get_value(obj->parameters, CN_MONITORUSER);
 | |
|                 char *monpw = config_get_value(obj->parameters, CN_MONITORPW);
 | |
|                 server_update_credentials(server, monuser, monpw);
 | |
|                 obj->element = server;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 create_new_server(obj);
 | |
|             }
 | |
|         }
 | |
|         obj = obj->next;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Check if required parameters are missing
 | |
|  *
 | |
|  * @param name Module name
 | |
|  * @param type Module type
 | |
|  * @param params List of parameters for the object
 | |
|  * @return True if at least one of the required parameters is missing
 | |
|  */
 | |
| static bool missing_required_parameters(const MXS_MODULE_PARAM *mod_params,
 | |
|                                         MXS_CONFIG_PARAMETER *params)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (mod_params)
 | |
|     {
 | |
|         for (int i = 0; mod_params[i].name; i++)
 | |
|         {
 | |
|             if ((mod_params[i].options & MXS_MODULE_OPT_REQUIRED) &&
 | |
|                 config_get_param(params, mod_params[i].name) == NULL)
 | |
|             {
 | |
|                 MXS_ERROR("Mandatory parameter '%s' is not defined.", mod_params[i].name);
 | |
|                 rval = true;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool is_path_parameter(const MXS_MODULE_PARAM *params, const char *name)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (params)
 | |
|     {
 | |
|         for (int i = 0; params[i].name; i++)
 | |
|         {
 | |
|             if (strcmp(params[i].name, name) == 0 && params[i].type == MXS_MODULE_PARAM_PATH)
 | |
|             {
 | |
|                 rval = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static void process_path_parameter(MXS_CONFIG_PARAMETER *param)
 | |
| {
 | |
|     if (*param->value != '/')
 | |
|     {
 | |
|         const char *mod_dir = get_module_configdir();
 | |
|         size_t size = strlen(param->value) + strlen(mod_dir) + 3;
 | |
|         char *value = (char*)MXS_MALLOC(size);
 | |
|         MXS_ABORT_IF_NULL(value);
 | |
| 
 | |
|         sprintf(value, "/%s/%s", mod_dir, param->value);
 | |
|         clean_up_pathname(value);
 | |
|         MXS_FREE(param->value);
 | |
|         param->value = value;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static bool param_is_deprecated(const MXS_MODULE_PARAM* params, const char* name, const char* modname)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     for (int i = 0; params[i].name; i++)
 | |
|     {
 | |
|         if (strcmp(params[i].name, name) == 0)
 | |
|         {
 | |
|             if (params[i].options & MXS_MODULE_OPT_DEPRECATED)
 | |
|             {
 | |
|                 MXS_WARNING("Parameter '%s' for module '%s' is deprecated and "
 | |
|                             "will be ignored.", name, modname);
 | |
|                 rval = true;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Check that the configuration objects have valid parameters
 | |
|  *
 | |
|  * @param context Configuration context
 | |
|  * @return True if the configuration is OK, false if errors were detected
 | |
|  */
 | |
| static bool
 | |
| check_config_objects(CONFIG_CONTEXT *context)
 | |
| {
 | |
|     bool rval = true;
 | |
|     CONFIG_CONTEXT *obj = context;
 | |
| 
 | |
|     while (obj)
 | |
|     {
 | |
|         const char **param_set = NULL;
 | |
|         const char *module = NULL;
 | |
|         const char *type;
 | |
|         const char *module_type = NULL;
 | |
| 
 | |
|         if (obj->parameters && (type = config_get_value(obj->parameters, CN_TYPE)))
 | |
|         {
 | |
|             if (!strcmp(type, CN_SERVICE))
 | |
|             {
 | |
|                 param_set = config_service_params;
 | |
|                 module = config_get_value(obj->parameters, CN_ROUTER);
 | |
|                 module_type = MODULE_ROUTER;
 | |
|             }
 | |
|             else if (!strcmp(type, CN_LISTENER))
 | |
|             {
 | |
|                 param_set = config_listener_params;
 | |
|             }
 | |
|             else if (!strcmp(type, CN_MONITOR))
 | |
|             {
 | |
|                 param_set = config_monitor_params;
 | |
|                 module = config_get_value(obj->parameters, CN_MODULE);
 | |
|                 module_type = MODULE_MONITOR;
 | |
|             }
 | |
|             else if (!strcmp(type, CN_FILTER))
 | |
|             {
 | |
|                 param_set = config_filter_params;
 | |
|                 module = config_get_value(obj->parameters, CN_MODULE);
 | |
|                 module_type = MODULE_FILTER;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const MXS_MODULE *mod = module ? get_module(module, module_type) : NULL;
 | |
| 
 | |
|         if (param_set != NULL)
 | |
|         {
 | |
|             std::vector<std::string> to_be_removed;
 | |
| 
 | |
|             MXS_CONFIG_PARAMETER *params = obj->parameters;
 | |
|             while (params)
 | |
|             {
 | |
|                 int found = 0;
 | |
|                 for (int i = 0; param_set[i]; i++)
 | |
|                 {
 | |
|                     if (!strcmp(params->name, param_set[i]))
 | |
|                     {
 | |
|                         found = 1;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (found == 0)
 | |
|                 {
 | |
|                     if (mod == NULL ||
 | |
|                         !config_param_is_valid(mod->parameters, params->name, params->value, context))
 | |
|                     {
 | |
|                         MXS_ERROR("Unexpected parameter '%s' for object '%s' of type '%s', "
 | |
|                                   "or '%s' is an invalid value for parameter '%s'.",
 | |
|                                   params->name, obj->object, type, params->value, params->name);
 | |
|                         rval = false;
 | |
|                     }
 | |
|                     else if (is_path_parameter(mod->parameters, params->name))
 | |
|                     {
 | |
|                         process_path_parameter(params);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         /** Fix old-style object names */
 | |
|                         config_fix_param(mod->parameters, params);
 | |
|                     }
 | |
| 
 | |
|                     if (mod && param_is_deprecated(mod->parameters, params->name, obj->object))
 | |
|                     {
 | |
|                         to_be_removed.push_back(params->name);
 | |
|                     }
 | |
|                 }
 | |
|                 params = params->next;
 | |
|             }
 | |
| 
 | |
|             for (auto it = to_be_removed.begin(); it != to_be_removed.end(); it++)
 | |
|             {
 | |
|                 config_remove_param(obj, it->c_str());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (mod && missing_required_parameters(mod->parameters, obj->parameters))
 | |
|         {
 | |
|             rval = false;
 | |
|         }
 | |
| 
 | |
|         obj = obj->next;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| int
 | |
| config_truth_value(const char *str)
 | |
| {
 | |
|     if (strcasecmp(str, "true") == 0 || strcasecmp(str, "on") == 0 ||
 | |
|         strcasecmp(str, "yes") == 0 || strcasecmp(str, "1") == 0)
 | |
|     {
 | |
|         return 1;
 | |
|     }
 | |
|     if (strcasecmp(str, "false") == 0 || strcasecmp(str, "off") == 0 ||
 | |
|         strcasecmp(str, "no") == 0 || strcasecmp(str, "0") == 0)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the MAC address of first network interface
 | |
|  *
 | |
|  * and fill the provided allocated buffer with SHA1 encoding
 | |
|  * @param output        Allocated 6 bytes buffer
 | |
|  * @return 1 on success, 0 on failure
 | |
|  *
 | |
|  */
 | |
| int
 | |
| config_get_ifaddr(unsigned char *output)
 | |
| {
 | |
|     struct ifreq ifr;
 | |
|     struct ifconf ifc;
 | |
|     char buf[1024];
 | |
|     struct ifreq* it;
 | |
|     struct ifreq* end;
 | |
|     int success = 0;
 | |
| 
 | |
|     int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
 | |
|     if (sock == -1)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     ifc.ifc_len = sizeof(buf);
 | |
|     ifc.ifc_buf = buf;
 | |
|     if (ioctl(sock, SIOCGIFCONF, &ifc) == -1)
 | |
|     {
 | |
|         close(sock);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     it = ifc.ifc_req;
 | |
|     end = it + (ifc.ifc_len / sizeof(struct ifreq));
 | |
| 
 | |
|     for (; it != end; ++it)
 | |
|     {
 | |
|         strcpy(ifr.ifr_name, it->ifr_name);
 | |
| 
 | |
|         if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0)
 | |
|         {
 | |
|             if (!(ifr.ifr_flags & IFF_LOOPBACK))
 | |
|             {
 | |
|                 /* don't count loopback */
 | |
|                 if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0)
 | |
|                 {
 | |
|                     success = 1;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             close(sock);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (success)
 | |
|     {
 | |
|         memcpy(output, ifr.ifr_hwaddr.sa_data, 6);
 | |
|     }
 | |
|     close(sock);
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the linux distribution info
 | |
|  *
 | |
|  * @param release The buffer where the found distribution is copied.
 | |
|  *                Assumed to be at least _RELEASE_STR_LENGTH bytes.
 | |
|  *
 | |
|  * @return 1 on success, 0 on failure
 | |
|  */
 | |
| static int
 | |
| config_get_release_string(char* release)
 | |
| {
 | |
|     const char *masks[] =
 | |
|     {
 | |
|         "/etc/*-version", "/etc/*-release",
 | |
|         "/etc/*_version", "/etc/*_release"
 | |
|     };
 | |
| 
 | |
|     bool have_distribution;
 | |
|     char distribution[RELEASE_STR_LENGTH] = "";
 | |
|     int fd;
 | |
| 
 | |
|     have_distribution = false;
 | |
| 
 | |
|     /* get data from lsb-release first */
 | |
|     if ((fd = open("/etc/lsb-release", O_RDONLY)) != -1)
 | |
|     {
 | |
|         /* LSB-compliant distribution! */
 | |
|         size_t len = read(fd, (char*)distribution, sizeof(distribution) - 1);
 | |
|         close(fd);
 | |
| 
 | |
|         if (len != (size_t) - 1)
 | |
|         {
 | |
|             distribution[len] = 0;
 | |
| 
 | |
|             char *found = strstr(distribution, "DISTRIB_DESCRIPTION=");
 | |
| 
 | |
|             if (found)
 | |
|             {
 | |
|                 have_distribution = true;
 | |
|                 char *end = strstr(found, "\n");
 | |
|                 if (end == NULL)
 | |
|                 {
 | |
|                     end = distribution + len;
 | |
|                 }
 | |
|                 found += 20; // strlen("DISTRIB_DESCRIPTION=")
 | |
| 
 | |
|                 if (*found == '"' && end[-1] == '"')
 | |
|                 {
 | |
|                     found++;
 | |
|                     end--;
 | |
|                 }
 | |
|                 *end = 0;
 | |
| 
 | |
|                 char *to = strcpy(distribution, "lsb: ");
 | |
|                 memmove(to, found, end - found + 1 < INT_MAX ? end - found + 1 : INT_MAX);
 | |
| 
 | |
|                 strcpy(release, to);
 | |
| 
 | |
|                 return 1;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* if not an LSB-compliant distribution */
 | |
|     for (int i = 0; !have_distribution && i < 4; i++)
 | |
|     {
 | |
|         glob_t found;
 | |
|         char *new_to;
 | |
| 
 | |
|         if (glob(masks[i], GLOB_NOSORT, NULL, &found) == 0)
 | |
|         {
 | |
|             int fd;
 | |
|             size_t k = 0;
 | |
|             int skipindex = 0;
 | |
|             int startindex = 0;
 | |
| 
 | |
|             for (k = 0; k < found.gl_pathc; k++)
 | |
|             {
 | |
|                 if (strcmp(found.gl_pathv[k], "/etc/lsb-release") == 0)
 | |
|                 {
 | |
|                     skipindex = k;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (skipindex == 0)
 | |
|             {
 | |
|                 startindex++;
 | |
|             }
 | |
| 
 | |
|             if ((fd = open(found.gl_pathv[startindex], O_RDONLY)) != -1)
 | |
|             {
 | |
|                 /*
 | |
|                   +5 and -8 below cut the file name part out of the
 | |
|                   full pathname that corresponds to the mask as above.
 | |
|                 */
 | |
|                 new_to = strncpy(distribution, found.gl_pathv[0] + 5, RELEASE_STR_LENGTH - 1);
 | |
|                 new_to += 8;
 | |
|                 *new_to++ = ':';
 | |
|                 *new_to++ = ' ';
 | |
| 
 | |
|                 size_t to_len = distribution + sizeof(distribution) - 1 - new_to;
 | |
|                 size_t len = read(fd, (char*)new_to, to_len);
 | |
| 
 | |
|                 close(fd);
 | |
| 
 | |
|                 if (len != (size_t) - 1)
 | |
|                 {
 | |
|                     new_to[len] = 0;
 | |
|                     char *end = strstr(new_to, "\n");
 | |
|                     if (end)
 | |
|                     {
 | |
|                         *end = 0;
 | |
|                     }
 | |
| 
 | |
|                     have_distribution = true;
 | |
|                     strncpy(release, new_to, RELEASE_STR_LENGTH - 1);
 | |
|                     release[RELEASE_STR_LENGTH - 1] = '\0';
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         globfree(&found);
 | |
|     }
 | |
| 
 | |
|     if (have_distribution)
 | |
|     {
 | |
|         return 1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| unsigned long config_get_gateway_id()
 | |
| {
 | |
|     return gateway.id;
 | |
| }
 | |
| 
 | |
| MXS_CONFIG* config_get_global_options()
 | |
| {
 | |
|     return &gateway;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if sections are defined multiple times in the configuration file.
 | |
|  *
 | |
|  * @param filename Path to the configuration file
 | |
|  * @param context  The context object used for tracking the duplication
 | |
|  *                 section information.
 | |
|  *
 | |
|  * @return True if duplicate sections were found or an error occurred
 | |
|  */
 | |
| bool config_has_duplicate_sections(const char* filename, DUPLICATE_CONTEXT* context)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     int size = 1024;
 | |
|     char *buffer = (char*)MXS_MALLOC(size * sizeof(char));
 | |
| 
 | |
|     if (buffer)
 | |
|     {
 | |
|         FILE* file = fopen(filename, "r");
 | |
| 
 | |
|         if (file)
 | |
|         {
 | |
|             while (maxscale_getline(&buffer, &size, file) > 0)
 | |
|             {
 | |
|                 if (pcre2_match(context->re, (PCRE2_SPTR) buffer,
 | |
|                                 PCRE2_ZERO_TERMINATED, 0, 0,
 | |
|                                 context->mdata, NULL) > 0)
 | |
|                 {
 | |
|                     /**
 | |
|                      * Neither of the PCRE2 calls will fail since we know the pattern
 | |
|                      * beforehand and we allocate enough memory from the stack
 | |
|                      */
 | |
|                     PCRE2_SIZE len;
 | |
|                     pcre2_substring_length_bynumber(context->mdata, 1, &len);
 | |
|                     len += 1; /** one for the null terminator */
 | |
|                     PCRE2_UCHAR section[len];
 | |
|                     pcre2_substring_copy_bynumber(context->mdata, 1, section, &len);
 | |
| 
 | |
|                     if (hashtable_add(context->hash, section, (char*)"") == 0)
 | |
|                     {
 | |
|                         MXS_ERROR("Duplicate section found: %s", section);
 | |
|                         rval = true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             fclose(file);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Failed to open file '%s': %s", filename, mxs_strerror(errno));
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_OOM_MESSAGE("Failed to allocate enough memory when checking"
 | |
|                         " for duplicate sections in configuration file.");
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     MXS_FREE(buffer);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Read from a FILE pointer until a newline character or the end of the file is found.
 | |
|  * The provided buffer will be reallocated if it is too small to store the whole
 | |
|  * line. The size after the reallocation will be stored in @c size. The read line
 | |
|  * will be stored in @c dest and it will always be null terminated. The newline
 | |
|  * character will not be copied into the buffer.
 | |
|  * @param dest Pointer to a buffer of at least @c size bytes
 | |
|  * @param size Size of the buffer
 | |
|  * @param file A valid file stream
 | |
|  * @return When a complete line was successfully read the function returns 1. If
 | |
|  * the end of the file was reached before any characters were read the return value
 | |
|  * will be 0. If the provided buffer could not be reallocated to store the complete
 | |
|  * line the original size will be retained, everything read up to this point
 | |
|  * will be stored in it as a null terminated string and -1 will be returned.
 | |
|  */
 | |
| int maxscale_getline(char** dest, int* size, FILE* file)
 | |
| {
 | |
|     char* destptr = *dest;
 | |
|     int offset = 0;
 | |
| 
 | |
|     if (feof(file) || ferror(file))
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     while (true)
 | |
|     {
 | |
|         if (*size <= offset)
 | |
|         {
 | |
|             char* tmp = (char*)MXS_REALLOC(destptr, *size * 2);
 | |
|             if (tmp)
 | |
|             {
 | |
|                 destptr = tmp;
 | |
|                 *size *= 2;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 destptr[offset - 1] = '\0';
 | |
|                 *dest = destptr;
 | |
|                 return -1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         int c = fgetc(file);
 | |
| 
 | |
|         if ((c == '\n') || (c == EOF))
 | |
|         {
 | |
|             destptr[offset] = '\0';
 | |
|             break;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             destptr[offset] = c;
 | |
|         }
 | |
| 
 | |
|         offset++;
 | |
|     }
 | |
| 
 | |
|     *dest = destptr;
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Validate the SSL parameters for a service
 | |
|  * @param ssl_cert SSL certificate (private key)
 | |
|  * @param ssl_ca_cert SSL CA certificate
 | |
|  * @param ssl_key SSL key (public key)
 | |
|  * @return 0 if parameters are valid otherwise the number of errors if errors
 | |
|  * were detected
 | |
|  */
 | |
| static int validate_ssl_parameters(CONFIG_CONTEXT* obj, char *ssl_cert, char *ssl_ca_cert, char *ssl_key)
 | |
| {
 | |
|     int error_count = 0;
 | |
|     if (ssl_cert == NULL)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Server certificate missing for listener '%s'."
 | |
|                   "Please provide the path to the server certificate by adding "
 | |
|                   "the ssl_cert=<path> parameter", obj->object);
 | |
|     }
 | |
|     else if (access(ssl_cert, F_OK) != 0)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Server certificate file for listener '%s' not found: %s",
 | |
|                   obj->object, ssl_cert);
 | |
|     }
 | |
| 
 | |
|     if (ssl_ca_cert == NULL)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("CA Certificate missing for listener '%s'."
 | |
|                   "Please provide the path to the certificate authority "
 | |
|                   "certificate by adding the ssl_ca_cert=<path> parameter",
 | |
|                   obj->object);
 | |
|     }
 | |
|     else if (access(ssl_ca_cert, F_OK) != 0)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Certificate authority file for listener '%s' "
 | |
|                   "not found: %s", obj->object, ssl_ca_cert);
 | |
|     }
 | |
| 
 | |
|     if (ssl_key == NULL)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Server private key missing for listener '%s'. "
 | |
|                   "Please provide the path to the server certificate key by "
 | |
|                   "adding the ssl_key=<path> parameter", obj->object);
 | |
|     }
 | |
|     else if (access(ssl_key, F_OK) != 0)
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Server private key file for listener '%s' not found: %s",
 | |
|                   obj->object, ssl_key);
 | |
|     }
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Add default parameters for a module to the configuration context
 | |
|  *
 | |
|  * Only parameters that aren't defined are added to the configuration context.
 | |
|  * This allows users to override the default values.
 | |
|  *
 | |
|  * @param ctx Configuration context where the default parameters are added
 | |
|  * @param module Name of the module
 | |
|  */
 | |
| void config_add_defaults(CONFIG_CONTEXT *ctx, const MXS_MODULE_PARAM *params)
 | |
| {
 | |
|     if (params)
 | |
|     {
 | |
|         for (int i = 0; params[i].name; i++)
 | |
|         {
 | |
|             if (params[i].default_value &&
 | |
|                 config_get_param(ctx->parameters, params[i].name) == NULL)
 | |
|             {
 | |
|                 bool rv = config_add_param(ctx, params[i].name, params[i].default_value);
 | |
|                 MXS_ABORT_IF_FALSE(rv);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static json_t* param_value_json(const MXS_CONFIG_PARAMETER* param,
 | |
|                                 const MXS_MODULE* mod)
 | |
| {
 | |
|     json_t* rval = NULL;
 | |
| 
 | |
|     for (int i = 0; mod->parameters[i].name; i++)
 | |
|     {
 | |
|         if (strcmp(mod->parameters[i].name, param->name) == 0)
 | |
|         {
 | |
|             switch (mod->parameters[i].type)
 | |
|             {
 | |
|             case MXS_MODULE_PARAM_COUNT:
 | |
|             case MXS_MODULE_PARAM_INT:
 | |
|                 rval = json_integer(strtol(param->value, NULL, 10));
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_BOOL:
 | |
|                 rval = json_boolean(config_truth_value(param->value));
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 rval = json_string(param->value);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| void config_add_module_params_json(const MXS_MODULE* mod, MXS_CONFIG_PARAMETER* parameters,
 | |
|                                    const char** type_params, json_t* output)
 | |
| {
 | |
|     set<string> param_set;
 | |
| 
 | |
|     for (int i = 0; type_params[i]; i++)
 | |
|     {
 | |
|         param_set.insert(type_params[i]);
 | |
|     }
 | |
| 
 | |
|     for (MXS_CONFIG_PARAMETER* p = parameters; p; p = p->next)
 | |
|     {
 | |
|         if (param_set.find(p->name) == param_set.end())
 | |
|         {
 | |
|             json_object_set_new(output, p->name, param_value_json(p, mod));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new router for a service
 | |
|  * @param obj Service configuration context
 | |
|  * @return True if configuration was successful, false if an error occurred.
 | |
|  */
 | |
| int create_new_service(CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     char *router = config_get_value(obj->parameters, CN_ROUTER);
 | |
|     if (router == NULL)
 | |
|     {
 | |
|         obj->element = NULL;
 | |
|         MXS_ERROR("No router defined for service '%s'.", obj->object);
 | |
|         return 1;
 | |
|     }
 | |
|     else if ((obj->element = service_alloc(obj->object, router)) == NULL)
 | |
|     {
 | |
|         MXS_ERROR("Service creation failed.");
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     SERVICE* service = (SERVICE*) obj->element;
 | |
|     int error_count = 0;
 | |
|     MXS_CONFIG_PARAMETER* param;
 | |
| 
 | |
|     char *retry = config_get_value(obj->parameters, CN_RETRY_ON_FAILURE);
 | |
|     if (retry)
 | |
|     {
 | |
|         serviceSetRetryOnFailure(service, retry);
 | |
|     }
 | |
| 
 | |
|     char *enable_root_user = config_get_value(obj->parameters, CN_ENABLE_ROOT_USER);
 | |
|     if (enable_root_user)
 | |
|     {
 | |
|         serviceEnableRootUser(service, config_truth_value(enable_root_user));
 | |
|     }
 | |
| 
 | |
|     char *max_retry_interval = config_get_value(obj->parameters, CN_MAX_RETRY_INTERVAL);
 | |
| 
 | |
|     if (max_retry_interval)
 | |
|     {
 | |
|         char *endptr;
 | |
|         long val = strtol(max_retry_interval, &endptr, 10);
 | |
| 
 | |
|         if (val && *endptr == '\0')
 | |
|         {
 | |
|             service_set_retry_interval(service, val);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid value for 'max_retry_interval': %s", max_retry_interval);
 | |
|             error_count++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     char *connection_timeout = config_get_value(obj->parameters, CN_CONNECTION_TIMEOUT);
 | |
|     if (connection_timeout)
 | |
|     {
 | |
|         serviceSetTimeout(service, atoi(connection_timeout));
 | |
|     }
 | |
| 
 | |
|     const char *max_connections = config_get_value_string(obj->parameters, CN_MAX_CONNECTIONS);
 | |
|     const char *max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections");
 | |
|     const char *queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout");
 | |
|     if (strlen(max_connections))
 | |
|     {
 | |
|         serviceSetConnectionLimits(service, atoi(max_connections),
 | |
|                                    atoi(max_queued_connections), atoi(queued_connection_timeout));
 | |
|     }
 | |
| 
 | |
|     char *auth_all_servers = config_get_value(obj->parameters, CN_AUTH_ALL_SERVERS);
 | |
|     if (auth_all_servers)
 | |
|     {
 | |
|         serviceAuthAllServers(service, config_truth_value(auth_all_servers));
 | |
|     }
 | |
| 
 | |
|     char *strip_db_esc = config_get_value(obj->parameters, CN_STRIP_DB_ESC);
 | |
|     if (strip_db_esc)
 | |
|     {
 | |
|         serviceStripDbEsc(service, config_truth_value(strip_db_esc));
 | |
|     }
 | |
| 
 | |
|     char *weightby = config_get_value(obj->parameters, CN_WEIGHTBY);
 | |
|     if (weightby)
 | |
|     {
 | |
|         serviceWeightBy(service, weightby);
 | |
|     }
 | |
| 
 | |
|     char *wildcard = config_get_value(obj->parameters, CN_LOCALHOST_MATCH_WILDCARD_HOST);
 | |
|     if (wildcard)
 | |
|     {
 | |
|         serviceEnableLocalhostMatchWildcardHost(service, config_truth_value(wildcard));
 | |
|     }
 | |
| 
 | |
|     char *user = config_get_value(obj->parameters, CN_USER);
 | |
|     char *auth = config_get_password(obj->parameters);
 | |
| 
 | |
|     if (user && auth)
 | |
|     {
 | |
|         serviceSetUser(service, user, auth);
 | |
|     }
 | |
|     else if (!rcap_type_required(service_get_capabilities(service), RCAP_TYPE_NO_AUTH))
 | |
|     {
 | |
|         error_count++;
 | |
|         MXS_ERROR("Service '%s' is missing %s%s%s.",
 | |
|                   obj->object,
 | |
|                   user ? "" : "the 'user' parameter",
 | |
|                   !user && !auth ? " and " : "",
 | |
|                   auth ? "" : "the 'password' or 'passwd' parameter");
 | |
|     }
 | |
| 
 | |
|     char *log_auth_warnings = config_get_value(obj->parameters, CN_LOG_AUTH_WARNINGS);
 | |
|     if (log_auth_warnings)
 | |
|     {
 | |
|         int truthval = config_truth_value(log_auth_warnings);
 | |
|         if (truthval != -1)
 | |
|         {
 | |
|             service->log_auth_warnings = (bool) truthval;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Invalid value for 'log_auth_warnings': %s", log_auth_warnings);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     char *version_string = config_get_value(obj->parameters, CN_VERSION_STRING);
 | |
|     if (version_string)
 | |
|     {
 | |
|         /** Add the 5.5.5- string to the start of the version string if
 | |
|          * the version string starts with "10.".
 | |
|          * This mimics MariaDB 10.0 replication which adds 5.5.5- for backwards compatibility. */
 | |
|         if (version_string[0] != '5')
 | |
|         {
 | |
|             size_t len = strlen(version_string) + strlen("5.5.5-") + 1;
 | |
|             char ver[len];
 | |
|             snprintf(ver, sizeof(ver), "5.5.5-%s", version_string);
 | |
|             serviceSetVersionString(service, ver);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             serviceSetVersionString(service, version_string);
 | |
|         }
 | |
|     }
 | |
|     else if (gateway.version_string)
 | |
|     {
 | |
|         serviceSetVersionString(service, gateway.version_string);
 | |
|     }
 | |
| 
 | |
|     service->session_track_trx_state = config_get_bool(obj->parameters, CN_SESSION_TRACK_TRX_STATE);
 | |
|     /** Store the configuration parameters for the service */
 | |
|     const MXS_MODULE *mod = get_module(router, MODULE_ROUTER);
 | |
| 
 | |
|     if (mod)
 | |
|     {
 | |
|         config_add_defaults(obj, mod->parameters);
 | |
|         service_add_parameters(service, obj->parameters);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         error_count++;
 | |
|     }
 | |
| 
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if a parameter is a default server parameter.
 | |
|  * @param param Parameter name
 | |
|  * @return True if it is one of the standard server parameters
 | |
|  */
 | |
| bool is_normal_server_parameter(const char *param)
 | |
| {
 | |
|     for (int i = 0; server_params[i]; i++)
 | |
|     {
 | |
|         if (strcmp(param, server_params[i]) == 0)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     // Check if parameter is deprecated
 | |
|     for (int i = 0; deprecated_server_params[i]; i++)
 | |
|     {
 | |
|         if (strcmp(param, deprecated_server_params[i]) == 0)
 | |
|         {
 | |
|             MXS_WARNING("Server parameter '%s' is deprecated and will be ignored.", param);
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new server
 | |
|  * @param obj Server configuration context
 | |
|  * @return Number of errors
 | |
|  */
 | |
| int create_new_server(CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     int error_count = 0;
 | |
|     char *address = config_get_value(obj->parameters, CN_ADDRESS);
 | |
|     char *port = config_get_value(obj->parameters, CN_PORT);
 | |
|     char *protocol = config_get_value(obj->parameters, CN_PROTOCOL);
 | |
|     char *monuser = config_get_value(obj->parameters, CN_MONITORUSER);
 | |
|     char *monpw = config_get_value(obj->parameters, CN_MONITORPW);
 | |
|     char *auth = config_get_value(obj->parameters, CN_AUTHENTICATOR);
 | |
| 
 | |
|     if (address && port && protocol)
 | |
|     {
 | |
|         if ((obj->element = server_alloc(obj->object, address, atoi(port), protocol, auth)) == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Failed to create a new server, memory allocation failed.");
 | |
|             error_count++;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         obj->element = NULL;
 | |
|         MXS_ERROR("Server '%s' is missing a required configuration parameter. A "
 | |
|                   "server must have address, port and protocol defined.", obj->object);
 | |
|         error_count++;
 | |
|     }
 | |
| 
 | |
|     if (error_count == 0)
 | |
|     {
 | |
|         SERVER *server = (SERVER*)obj->element;
 | |
| 
 | |
|         if (monuser && monpw)
 | |
|         {
 | |
|             server_add_mon_user(server, monuser, monpw);
 | |
|         }
 | |
|         else if (monuser && monpw == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Server '%s' has a monitoruser defined but no corresponding "
 | |
|                       "password.", obj->object);
 | |
|             error_count++;
 | |
|         }
 | |
| 
 | |
|         char *endptr;
 | |
|         const char *poolmax = config_get_value_string(obj->parameters, CN_PERSISTPOOLMAX);
 | |
|         if (poolmax)
 | |
|         {
 | |
|             long int persistpoolmax = strtol(poolmax, &endptr, 0);
 | |
|             if (*endptr != '\0' || persistpoolmax < 0)
 | |
|             {
 | |
|                 MXS_ERROR("Invalid value for 'persistpoolmax' for server %s: %s",
 | |
|                           server->name, poolmax);
 | |
|                 error_count++;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 server->persistpoolmax = persistpoolmax;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const char *persistmax = config_get_value_string(obj->parameters, CN_PERSISTMAXTIME);
 | |
|         if (persistmax)
 | |
|         {
 | |
|             long int persistmaxtime = strtol(persistmax, &endptr, 0);
 | |
|             if (*endptr != '\0' || persistmaxtime < 0)
 | |
|             {
 | |
|                 MXS_ERROR("Invalid value for 'persistmaxtime' for server %s: %s",
 | |
|                           server->name, persistmax);
 | |
|                 error_count++;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 server->persistmaxtime = persistmaxtime;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const char* proxy_protocol = config_get_value_string(obj->parameters, CN_PROXY_PROTOCOL);
 | |
|         if (*proxy_protocol)
 | |
|         {
 | |
|             int truth_value = config_truth_value(proxy_protocol);
 | |
|             if (truth_value == 1)
 | |
|             {
 | |
|                 server->proxy_protocol = true;
 | |
|             }
 | |
|             else if (truth_value == 0)
 | |
|             {
 | |
|                 server->proxy_protocol = false;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Invalid value for '%s' for server %s: %s",
 | |
|                           CN_PROXY_PROTOCOL, server->name, proxy_protocol);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         MXS_CONFIG_PARAMETER *params = obj->parameters;
 | |
| 
 | |
|         server->server_ssl = make_ssl_structure(obj, false, &error_count);
 | |
|         if (server->server_ssl && listener_init_SSL(server->server_ssl) != 0)
 | |
|         {
 | |
|             MXS_ERROR("Unable to initialize server SSL");
 | |
|         }
 | |
| 
 | |
|         while (params)
 | |
|         {
 | |
|             if (!is_normal_server_parameter(params->name))
 | |
|             {
 | |
|                 server_add_parameter(server, params->name, params->value);
 | |
|             }
 | |
|             params = params->next;
 | |
|         }
 | |
|     }
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Configure a new service
 | |
|  *
 | |
|  * Add servers, router options and filters to a new service.
 | |
|  * @param context The complete configuration context
 | |
|  * @param obj The service configuration context
 | |
|  * @return Number of errors
 | |
|  */
 | |
| int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     int error_count = 0;
 | |
|     char *filters = config_get_value(obj->parameters, CN_FILTERS);
 | |
|     char *servers = config_get_value(obj->parameters, CN_SERVERS);
 | |
|     char *monitor = config_get_value(obj->parameters, CN_MONITOR);
 | |
|     char *roptions = config_get_value(obj->parameters, CN_ROUTER_OPTIONS);
 | |
|     SERVICE *service = (SERVICE*)obj->element;
 | |
| 
 | |
|     if (service)
 | |
|     {
 | |
|         if (monitor)
 | |
|         {
 | |
|             if (servers)
 | |
|             {
 | |
|                 MXS_WARNING("Both `monitor` and `servers` are defined. Only the "
 | |
|                             "value of `monitor` will be used.");
 | |
|             }
 | |
| 
 | |
|             /** `monitor` takes priority over `servers` */
 | |
|             servers = NULL;
 | |
| 
 | |
|             for (CONFIG_CONTEXT *ctx = context; ctx; ctx = ctx->next)
 | |
|             {
 | |
|                 if (strcmp(ctx->object, monitor) == 0)
 | |
|                 {
 | |
|                     servers = config_get_value(ctx->parameters, CN_SERVERS);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (servers == NULL)
 | |
|             {
 | |
|                 MXS_ERROR("Unable to find monitor '%s'.", monitor);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (servers)
 | |
|         {
 | |
|             char srv_list[strlen(servers) + 1];
 | |
|             strcpy(srv_list, servers);
 | |
|             char *lasts;
 | |
|             char *s = strtok_r(srv_list, ",", &lasts);
 | |
|             while (s)
 | |
|             {
 | |
|                 CONFIG_CONTEXT *obj1 = context;
 | |
|                 int found = 0;
 | |
|                 while (obj1)
 | |
|                 {
 | |
|                     if (strcmp(trim(s), obj1->object) == 0 && obj1->element)
 | |
|                     {
 | |
|                         found = 1;
 | |
|                         serviceAddBackend(service, (SERVER*)obj1->element);
 | |
|                         break;
 | |
|                     }
 | |
|                     obj1 = obj1->next;
 | |
|                 }
 | |
| 
 | |
|                 if (!found)
 | |
|                 {
 | |
|                     MXS_ERROR("Unable to find server '%s' that is "
 | |
|                               "configured as part of service '%s'.", s, obj->object);
 | |
|                     error_count++;
 | |
|                 }
 | |
|                 s = strtok_r(NULL, ",", &lasts);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (roptions)
 | |
|         {
 | |
|             char *lasts;
 | |
|             char *s = strtok_r(roptions, ",", &lasts);
 | |
|             while (s)
 | |
|             {
 | |
|                 serviceAddRouterOption(service, s);
 | |
|                 s = strtok_r(NULL, ",", &lasts);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (filters)
 | |
|         {
 | |
|             if (!serviceSetFilters(service, filters))
 | |
|             {
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new monitor
 | |
|  * @param context The complete configuration context
 | |
|  * @param obj Monitor configuration context
 | |
|  * @param monitorhash Hashtable containing the servers that are already monitored
 | |
|  * @return Number of errors
 | |
|  */
 | |
| int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash)
 | |
| {
 | |
|     int error_count = 0;
 | |
| 
 | |
|     char *module = config_get_value(obj->parameters, CN_MODULE);
 | |
|     if (module)
 | |
|     {
 | |
|         if ((obj->element = monitor_create(obj->object, module)) == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Failed to create monitor '%s'.", obj->object);
 | |
|             error_count++;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         obj->element = NULL;
 | |
|         MXS_ERROR("Monitor '%s' is missing the required 'module' parameter.", obj->object);
 | |
|         error_count++;
 | |
|     }
 | |
| 
 | |
|     char *servers = config_get_value(obj->parameters, CN_SERVERS);
 | |
| 
 | |
|     if (error_count == 0)
 | |
|     {
 | |
|         MXS_MONITOR* monitor = (MXS_MONITOR*)obj->element;
 | |
|         const MXS_MODULE *mod = get_module(module, MODULE_MONITOR);
 | |
| 
 | |
|         if (mod)
 | |
|         {
 | |
|             config_add_defaults(obj, mod->parameters);
 | |
|             monitor_add_parameters(monitor, obj->parameters);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             error_count++;
 | |
|         }
 | |
| 
 | |
|         char *interval_str = config_get_value(obj->parameters, CN_MONITOR_INTERVAL);
 | |
|         if (interval_str)
 | |
|         {
 | |
|             char *endptr;
 | |
|             long interval = strtol(interval_str, &endptr, 0);
 | |
|             /* The interval must be >0 because it is used as a divisor.
 | |
|                 Perhaps a greater minimum value should be added? */
 | |
|             if (*endptr == '\0' && interval > 0)
 | |
|             {
 | |
|                 monitor_set_interval(monitor, (unsigned long)interval);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_NOTICE("Invalid '%s' parameter for monitor '%s', "
 | |
|                            "using default value of %d milliseconds.",
 | |
|                            CN_MONITOR_INTERVAL, obj->object, DEFAULT_MONITOR_INTERVAL);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_NOTICE("Monitor '%s' is missing the '%s' parameter, "
 | |
|                        "using default value of %d milliseconds.",
 | |
|                        obj->object, CN_MONITOR_INTERVAL, DEFAULT_MONITOR_INTERVAL);
 | |
|         }
 | |
| 
 | |
|         char *journal_age = config_get_value(obj->parameters, CN_JOURNAL_MAX_AGE);
 | |
|         if (journal_age)
 | |
|         {
 | |
|             char *endptr;
 | |
|             long interval = strtol(journal_age, &endptr, 0);
 | |
| 
 | |
|             if (*endptr == '\0' && interval > 0)
 | |
|             {
 | |
|                 monitor_set_journal_max_age(monitor, (time_t)interval);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 error_count++;
 | |
|                 MXS_NOTICE("Invalid '%s' parameter for monitor '%s'",
 | |
|                            CN_JOURNAL_MAX_AGE, obj->object);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_NOTICE("Monitor '%s' is missing the '%s' parameter, "
 | |
|                        "using default value of %d seconds.",
 | |
|                        obj->object, CN_JOURNAL_MAX_AGE, DEFAULT_JOURNAL_MAX_AGE);
 | |
|         }
 | |
| 
 | |
|         char *script_timeout = config_get_value(obj->parameters, CN_SCRIPT_TIMEOUT);
 | |
|         if (script_timeout)
 | |
|         {
 | |
|             char *endptr;
 | |
|             long interval = strtol(script_timeout, &endptr, 0);
 | |
| 
 | |
|             if (*endptr == '\0' && interval > 0)
 | |
|             {
 | |
|                 monitor_set_script_timeout(monitor, (uint32_t)interval);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 error_count++;
 | |
|                 MXS_NOTICE("Invalid '%s' parameter for monitor '%s'",
 | |
|                            CN_SCRIPT_TIMEOUT, obj->object);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_NOTICE("Monitor '%s' is missing the '%s' parameter, "
 | |
|                        "using default value of %d seconds.",
 | |
|                        obj->object, CN_SCRIPT_TIMEOUT, DEFAULT_SCRIPT_TIMEOUT);
 | |
|         }
 | |
| 
 | |
|         char *connect_timeout = config_get_value(obj->parameters, CN_BACKEND_CONNECT_TIMEOUT);
 | |
|         if (connect_timeout)
 | |
|         {
 | |
|             if (!monitor_set_network_timeout(monitor, MONITOR_CONNECT_TIMEOUT,
 | |
|                                           atoi(connect_timeout), CN_BACKEND_CONNECT_TIMEOUT))
 | |
|             {
 | |
|                 MXS_ERROR("Failed to set '%s'", CN_BACKEND_CONNECT_TIMEOUT);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         char *read_timeout = config_get_value(obj->parameters, CN_BACKEND_READ_TIMEOUT);
 | |
|         if (read_timeout)
 | |
|         {
 | |
|             if (!monitor_set_network_timeout(monitor, MONITOR_READ_TIMEOUT,
 | |
|                                           atoi(read_timeout), CN_BACKEND_READ_TIMEOUT))
 | |
|             {
 | |
|                 MXS_ERROR("Failed to set '%s'", CN_BACKEND_READ_TIMEOUT);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         char *write_timeout = config_get_value(obj->parameters, CN_BACKEND_WRITE_TIMEOUT);
 | |
|         if (write_timeout)
 | |
|         {
 | |
|             if (!monitor_set_network_timeout(monitor, MONITOR_WRITE_TIMEOUT,
 | |
|                                           atoi(write_timeout), CN_BACKEND_WRITE_TIMEOUT))
 | |
|             {
 | |
|                 MXS_ERROR("Failed to set '%s'", CN_BACKEND_WRITE_TIMEOUT);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         char *connect_attempts = config_get_value(obj->parameters, CN_BACKEND_CONNECT_ATTEMPTS);
 | |
|         if (connect_attempts)
 | |
|         {
 | |
|             if (!monitor_set_network_timeout(monitor, MONITOR_CONNECT_ATTEMPTS,
 | |
|                                           atoi(connect_attempts), CN_BACKEND_CONNECT_ATTEMPTS))
 | |
|             {
 | |
|                 MXS_ERROR("Failed to set '%s'", CN_BACKEND_CONNECT_ATTEMPTS);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (servers)
 | |
|         {
 | |
|             /* get the servers to monitor */
 | |
|             char *s, *lasts;
 | |
|             s = strtok_r(servers, ",", &lasts);
 | |
|             while (s)
 | |
|             {
 | |
|                 CONFIG_CONTEXT *obj1 = context;
 | |
|                 int found = 0;
 | |
|                 while (obj1)
 | |
|                 {
 | |
|                     if (strcmp(trim(s), obj1->object) == 0 && obj->element && obj1->element)
 | |
|                     {
 | |
|                         found = 1;
 | |
|                         if (hashtable_add(monitorhash, obj1->object, (char*)"") == 0)
 | |
|                         {
 | |
|                             MXS_WARNING("Multiple monitors are monitoring server [%s]. "
 | |
|                                         "This will cause undefined behavior.",
 | |
|                                         obj1->object);
 | |
|                         }
 | |
|                         monitor_add_server(monitor, (SERVER*)obj1->element);
 | |
|                     }
 | |
|                     obj1 = obj1->next;
 | |
|                 }
 | |
|                 if (!found)
 | |
|                 {
 | |
|                     MXS_ERROR("Unable to find server '%s' that is "
 | |
|                               "configured in the monitor '%s'.", s, obj->object);
 | |
|                     error_count++;
 | |
|                 }
 | |
| 
 | |
|                 s = strtok_r(NULL, ",", &lasts);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         char *user = config_get_value(obj->parameters, CN_USER);
 | |
|         char *passwd = config_get_password(obj->parameters);
 | |
|         if (user && passwd)
 | |
|         {
 | |
|             monitor_add_user(monitor, user, passwd);
 | |
|         }
 | |
|         else if (user)
 | |
|         {
 | |
|             MXS_ERROR("Monitor '%s' defines a username but does not define a password.",
 | |
|                       obj->object);
 | |
|             error_count++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new listener for a service
 | |
|  * @param obj Listener configuration context
 | |
|  * @param startnow If true, start the listener now
 | |
|  * @return Number of errors
 | |
|  */
 | |
| int create_new_listener(CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     int error_count = 0;
 | |
|     char *raw_service_name = config_get_value(obj->parameters, CN_SERVICE);
 | |
|     char *port = config_get_value(obj->parameters, CN_PORT);
 | |
|     char *address = config_get_value(obj->parameters, CN_ADDRESS);
 | |
|     char *protocol = config_get_value(obj->parameters, CN_PROTOCOL);
 | |
|     char *socket = config_get_value(obj->parameters, CN_SOCKET);
 | |
|     char *authenticator = config_get_value(obj->parameters, CN_AUTHENTICATOR);
 | |
|     char *authenticator_options = config_get_value(obj->parameters, CN_AUTHENTICATOR_OPTIONS);
 | |
| 
 | |
|     if (raw_service_name && protocol && (socket || port))
 | |
|     {
 | |
|         if (socket && port)
 | |
|         {
 | |
|             MXS_ERROR("Creation of listener '%s' for service '%s' failed, because "
 | |
|                       "both 'socket' and 'port' are defined. Only either one is allowed.",
 | |
|                       obj->object, raw_service_name);
 | |
|             error_count++;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             char service_name[strlen(raw_service_name) + 1];
 | |
|             strcpy(service_name, raw_service_name);
 | |
|             fix_section_name(service_name);
 | |
| 
 | |
|             SERVICE *service = service_find(service_name);
 | |
|             if (service)
 | |
|             {
 | |
|                 SERV_LISTENER *listener;
 | |
|                 SSL_LISTENER *ssl_info = make_ssl_structure(obj, true, &error_count);
 | |
|                 if (socket)
 | |
|                 {
 | |
|                     if (address)
 | |
|                     {
 | |
|                         MXS_WARNING("In the definition of the listener `%s', the value of "
 | |
|                                     "'address' lacks meaning as the listener listens on a "
 | |
|                                     "domain socket ('%s') and not on a port.",
 | |
|                                     obj->object, socket);
 | |
|                     }
 | |
| 
 | |
|                     listener = service_find_listener(service, socket, NULL, 0);
 | |
| 
 | |
|                     if (listener)
 | |
|                     {
 | |
|                         MXS_ERROR("Creation of listener '%s' for service '%s' failed, because "
 | |
|                                   "listener '%s' already listens on the socket '%s'.",
 | |
|                                   obj->object, raw_service_name, listener->name, socket);
 | |
|                         error_count++;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         serviceCreateListener(service, obj->object, protocol, socket, 0,
 | |
|                                               authenticator, authenticator_options, ssl_info);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (port)
 | |
|                 {
 | |
|                     listener = service_find_listener(service, NULL, address, atoi(port));
 | |
| 
 | |
|                     if (listener)
 | |
|                     {
 | |
|                         MXS_ERROR("Creation of listener '%s' for service '%s' failed, because "
 | |
|                                   "listener '%s' already listens on the port %s.",
 | |
|                                   obj->object, raw_service_name, listener->name, port);
 | |
|                         error_count++;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         serviceCreateListener(service, obj->object, protocol, address, atoi(port),
 | |
|                                               authenticator, authenticator_options, ssl_info);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (ssl_info && error_count)
 | |
|                 {
 | |
|                     free_ssl_structure(ssl_info);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Listener '%s', service '%s' not found.", obj->object,
 | |
|                           service_name);
 | |
|                 error_count++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("Listener '%s' is missing a required parameter. A Listener "
 | |
|                   "must have a service, protocol and port (or socket) defined.", obj->object);
 | |
|         error_count++;
 | |
|     }
 | |
| 
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new filter
 | |
|  * @param obj Filter configuration context
 | |
|  * @return Number of errors
 | |
|  */
 | |
| int create_new_filter(CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     int error_count = 0;
 | |
|     char *module = config_get_value(obj->parameters, CN_MODULE);
 | |
| 
 | |
|     if (module)
 | |
|     {
 | |
|         if ((obj->element = filter_alloc(obj->object, module)))
 | |
|         {
 | |
|             MXS_FILTER_DEF* filter_def = (MXS_FILTER_DEF*)obj->element;
 | |
|             char *options = config_get_value(obj->parameters, CN_OPTIONS);
 | |
|             if (options)
 | |
|             {
 | |
|                 char *lasts;
 | |
|                 char *s = strtok_r(options, ",", &lasts);
 | |
|                 while (s)
 | |
|                 {
 | |
|                     filter_add_option(filter_def, s);
 | |
|                     s = strtok_r(NULL, ",", &lasts);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const MXS_MODULE *mod = get_module(module, MODULE_FILTER);
 | |
| 
 | |
|             if (mod)
 | |
|             {
 | |
|                 config_add_defaults(obj, mod->parameters);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 error_count++;
 | |
|             }
 | |
| 
 | |
|             for (MXS_CONFIG_PARAMETER *p = obj->parameters; p; p = p->next)
 | |
|             {
 | |
|                 filter_add_parameter(filter_def, p->name, p->value);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Failed to create filter '%s'. Memory allocation failed.",
 | |
|                       obj->object);
 | |
|             error_count++;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("Filter '%s' has no module defined to load.", obj->object);
 | |
|         error_count++;
 | |
|     }
 | |
| 
 | |
|     return error_count;
 | |
| }
 | |
| 
 | |
| bool config_have_required_ssl_params(CONFIG_CONTEXT *obj)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER *param = obj->parameters;
 | |
| 
 | |
|     return config_get_param(param, CN_SSL) &&
 | |
|            config_get_param(param, CN_SSL_KEY) &&
 | |
|            config_get_param(param, CN_SSL_CERT) &&
 | |
|            config_get_param(param, CN_SSL_CA_CERT) &&
 | |
|            strcmp(config_get_value_string(param, CN_SSL), CN_REQUIRED) == 0;
 | |
| }
 | |
| 
 | |
| bool config_is_ssl_parameter(const char *key)
 | |
| {
 | |
|     const char *ssl_params[] =
 | |
|     {
 | |
|         CN_SSL_CERT,
 | |
|         CN_SSL_CA_CERT,
 | |
|         CN_SSL,
 | |
|         CN_SSL_KEY,
 | |
|         CN_SSL_VERSION,
 | |
|         CN_SSL_CERT_VERIFY_DEPTH,
 | |
|         CN_SSL_VERIFY_PEER_CERTIFICATE,
 | |
|         NULL
 | |
|     };
 | |
| 
 | |
|     for (int i = 0; ssl_params[i]; i++)
 | |
|     {
 | |
|         if (strcmp(key, ssl_params[i]) == 0)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static bool check_path_parameter(const MXS_MODULE_PARAM *params, const char *value)
 | |
| {
 | |
|     bool valid = false;
 | |
| 
 | |
|     if (params->options & (MXS_MODULE_OPT_PATH_W_OK |
 | |
|                            MXS_MODULE_OPT_PATH_R_OK |
 | |
|                            MXS_MODULE_OPT_PATH_X_OK |
 | |
|                            MXS_MODULE_OPT_PATH_F_OK))
 | |
|     {
 | |
|         char buf[strlen(get_module_configdir()) + strlen(value) + 3];
 | |
| 
 | |
|         if (*value != '/')
 | |
|         {
 | |
|             sprintf(buf, "/%s/%s", get_module_configdir(), value);
 | |
|             clean_up_pathname(buf);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             strcpy(buf, value);
 | |
|         }
 | |
| 
 | |
|         int mode = F_OK;
 | |
|         int mask = X_OK;
 | |
| 
 | |
|         if (params->options & MXS_MODULE_OPT_PATH_W_OK)
 | |
|         {
 | |
|             mask |= S_IWUSR;
 | |
|             mode |= W_OK;
 | |
|         }
 | |
|         if (params->options & MXS_MODULE_OPT_PATH_R_OK)
 | |
|         {
 | |
|             mask |= S_IRUSR;
 | |
|             mode |= R_OK;
 | |
|         }
 | |
|         if (params->options & MXS_MODULE_OPT_PATH_X_OK)
 | |
|         {
 | |
|             mask |= S_IXUSR;
 | |
|         }
 | |
| 
 | |
|         if (access(buf, mode) == 0)
 | |
|         {
 | |
|             valid = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /** Save errno as we do a second call to `accept` */
 | |
|             int er = errno;
 | |
| 
 | |
|             if (access(buf, F_OK) == 0 || (params->options & MXS_MODULE_OPT_PATH_CREAT) == 0)
 | |
|             {
 | |
|                 /**
 | |
|                  * Path already exists and it doesn't have the requested access
 | |
|                  * right or the module doesn't want the directory to be created
 | |
|                  * if it doesn't exist.
 | |
|                  */
 | |
|                 MXS_ERROR("Bad path parameter '%s' (absolute path '%s'): %d, %s",
 | |
|                           value, buf, er, mxs_strerror(er));
 | |
|             }
 | |
|             else if (mxs_mkdir_all(buf, mask))
 | |
|             {
 | |
|                 /** Successfully created path */
 | |
|                 valid = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /** Failed to create the directory, errno is set in `mxs_mkdir_all` */
 | |
|                 MXS_ERROR("Can't create path '%s' (absolute path '%s'): %d, %s",
 | |
|                           value, buf, errno, mxs_strerror(errno));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /** No checks for the path are required */
 | |
|         valid = true;
 | |
|     }
 | |
| 
 | |
|     return valid;
 | |
| }
 | |
| 
 | |
| static bool config_contains_type(const CONFIG_CONTEXT *ctx, const char *name, const char *type)
 | |
| {
 | |
|     while (ctx)
 | |
|     {
 | |
|         if (strcmp(ctx->object, name) == 0 &&
 | |
|             strcmp(type, config_get_value_string(ctx->parameters, CN_TYPE)) == 0)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         ctx = ctx->next;
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| void fix_serverlist(char* value)
 | |
| {
 | |
|     string dest;
 | |
|     char* end;
 | |
|     char* start = strtok_r(value, ",", &end);
 | |
|     const char* sep = "";
 | |
| 
 | |
|     while (start)
 | |
|     {
 | |
|         fix_section_name(start);
 | |
|         dest += sep;
 | |
|         dest += start;
 | |
|         sep = ",";
 | |
|         start = strtok_r(NULL, ",", &end);
 | |
|     }
 | |
| 
 | |
|     /** The value will always be smaller than the original one or of equal size */
 | |
|     strcpy(value, dest.c_str());
 | |
| }
 | |
| 
 | |
| void config_fix_param(const MXS_MODULE_PARAM *params, MXS_CONFIG_PARAMETER *p)
 | |
| {
 | |
|     for (int i = 0; params[i].name; i++)
 | |
|     {
 | |
|         if (strcmp(params[i].name, p->name) == 0)
 | |
|         {
 | |
|             switch (params[i].type)
 | |
|             {
 | |
|             case MXS_MODULE_PARAM_SERVER:
 | |
|             case MXS_MODULE_PARAM_SERVICE:
 | |
|                 fix_section_name(p->value);
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_SERVERLIST:
 | |
|                 fix_serverlist(p->value);
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_QUOTEDSTRING:
 | |
|                 // Remove *if* once '" .. "' is no longer optional
 | |
|                 if (check_first_last_char(p->value, '"'))
 | |
|                 {
 | |
|                     remove_first_last_char(p->value);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_REGEX:
 | |
|                 // Remove *if* once '/ .. /' is no longer optional
 | |
|                 if (check_first_last_char(p->value, '/'))
 | |
|                 {
 | |
|                     remove_first_last_char(p->value);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key,
 | |
|                            const char *value, const CONFIG_CONTEXT *context)
 | |
| {
 | |
|     bool valid = false;
 | |
|     char fixed_value[strlen(value) + 1];
 | |
|     strcpy(fixed_value, value);
 | |
|     fix_section_name(fixed_value);
 | |
| 
 | |
|     for (int i = 0; params[i].name && !valid; i++)
 | |
|     {
 | |
|         if (strcmp(params[i].name, key) == 0)
 | |
|         {
 | |
|             char *endptr;
 | |
| 
 | |
|             switch (params[i].type)
 | |
|             {
 | |
|             case MXS_MODULE_PARAM_COUNT:
 | |
|                 if ((strtol(value, &endptr, 10)) >= 0 && endptr != value && *endptr == '\0')
 | |
|                 {
 | |
|                     valid = true;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_INT:
 | |
|                 {
 | |
|                     errno = 0;
 | |
|                     long int v = strtol(value, &endptr, 10);
 | |
|                     (void)v; // error: ignoring return value of 'strtol'
 | |
|                     if ((errno == 0) && (endptr != value) && (*endptr == '\0'))
 | |
|                     {
 | |
|                         valid = true;
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_SIZE:
 | |
|                 {
 | |
|                     errno = 0;
 | |
|                     long long int v = strtoll(value, &endptr, 10);
 | |
|                     (void)v; // error: ignoring return value of 'strtoll'
 | |
|                     if (errno == 0)
 | |
|                     {
 | |
|                         if (endptr != value)
 | |
|                         {
 | |
|                             switch (*endptr)
 | |
|                             {
 | |
|                             case 'T':
 | |
|                             case 't':
 | |
|                             case 'G':
 | |
|                             case 'g':
 | |
|                             case 'M':
 | |
|                             case 'm':
 | |
|                             case 'K':
 | |
|                             case 'k':
 | |
|                                 if (*(endptr + 1) == '\0' ||
 | |
|                                     ((*(endptr + 1) == 'i' || *(endptr + 1) == 'I') && *(endptr + 2) == '\0'))
 | |
|                                 {
 | |
|                                     valid = true;
 | |
|                                 }
 | |
|                                 break;
 | |
| 
 | |
|                             case '\0':
 | |
|                                 valid = true;
 | |
|                                 break;
 | |
| 
 | |
|                             default:
 | |
|                                 break;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_BOOL:
 | |
|                 if (config_truth_value(value) != -1)
 | |
|                 {
 | |
|                     valid = true;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_STRING:
 | |
|                 if (*value)
 | |
|                 {
 | |
|                     valid = true;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_QUOTEDSTRING:
 | |
|                 if (*value)
 | |
|                 {
 | |
|                     valid = true;
 | |
|                     if (!check_first_last_char(value, '"'))
 | |
|                     {
 | |
|                         // Change warning to valid=false once quotes are no longer optional
 | |
|                         MXS_WARNING("Missing quotes (\") around a quoted string is deprecated: '%s=%s'.",
 | |
|                                     key, value);
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_REGEX:
 | |
|                 valid = test_regex_string_validity(value, key);
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_ENUM:
 | |
|                 if (params[i].accepted_values)
 | |
|                 {
 | |
|                     char *endptr;
 | |
|                     const char *delim = ", \t";
 | |
|                     char buf[strlen(value) + 1];
 | |
|                     strcpy(buf, value);
 | |
|                     char *tok = strtok_r(buf, delim, &endptr);
 | |
| 
 | |
|                     while (tok)
 | |
|                     {
 | |
|                         valid = false;
 | |
| 
 | |
|                         for (int j = 0; params[i].accepted_values[j].name; j++)
 | |
|                         {
 | |
|                             if (strcmp(params[i].accepted_values[j].name, tok) == 0)
 | |
|                             {
 | |
|                                 valid = true;
 | |
|                                 break;
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         tok = strtok_r(NULL, delim, &endptr);
 | |
| 
 | |
|                         if ((params[i].options & MXS_MODULE_OPT_ENUM_UNIQUE) && (tok || !valid))
 | |
|                         {
 | |
|                             /** Either the only defined enum value is not valid
 | |
|                              * or multiple values were defined */
 | |
|                             valid = false;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_SERVICE:
 | |
|                 if (context && config_contains_type(context, fixed_value, CN_SERVICE))
 | |
|                 {
 | |
|                     valid = true;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_SERVER:
 | |
|                 if (context && config_contains_type(context, fixed_value, CN_SERVER))
 | |
|                 {
 | |
|                     valid = true;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case MXS_MODULE_PARAM_SERVERLIST:
 | |
|                 if (context)
 | |
|                 {
 | |
|                     valid = true;
 | |
|                     char **server_names = NULL;
 | |
|                     int n_serv = config_parse_server_list(value, &server_names);
 | |
|                     if (n_serv > 0)
 | |
|                     {
 | |
|                         /* Check that every server name in the list is found in the config. */
 | |
|                         for (int i = 0; i < n_serv; i++)
 | |
|                         {
 | |
|                             if (valid &&
 | |
|                                 !config_contains_type(context, server_names[i], CN_SERVER))
 | |
|                             {
 | |
|                                 valid = false;
 | |
|                             }
 | |
|                             MXS_FREE(server_names[i]);
 | |
|                         }
 | |
|                         MXS_FREE(server_names);
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
| 
 | |
|             case MXS_MODULE_PARAM_PATH:
 | |
|                 valid = check_path_parameter(¶ms[i], value);
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 MXS_ERROR("Unexpected module parameter type: %d", params[i].type);
 | |
|                 ss_dassert(false);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return valid;
 | |
| }
 | |
| 
 | |
| int config_parse_server_list(const char *servers, char ***output_array)
 | |
| {
 | |
|     ss_dassert(servers);
 | |
| 
 | |
|     /* First, check the string for the maximum amount of servers it
 | |
|      * might contain by counting the commas. */
 | |
|     int out_arr_size = 1;
 | |
|     const char *pos = servers;
 | |
|     while ((pos = strchr(pos, ',')) != NULL)
 | |
|     {
 | |
|         pos++;
 | |
|         out_arr_size++;
 | |
|     }
 | |
|     char **results = (char**)MXS_CALLOC(out_arr_size, sizeof(char*));
 | |
|     if (!results)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /* Parse the server names from the list. They are separated by ',' and will
 | |
|      * be trimmed of whitespace. */
 | |
|     char srv_list_tmp[strlen(servers) + 1];
 | |
|     strcpy(srv_list_tmp, servers);
 | |
|     trim(srv_list_tmp);
 | |
| 
 | |
|     bool error = false;
 | |
|     int output_ind = 0;
 | |
|     char *lasts;
 | |
|     char *s = strtok_r(srv_list_tmp, ",", &lasts);
 | |
|     while (s)
 | |
|     {
 | |
|         char srv_name_tmp[strlen(s) + 1];
 | |
|         strcpy(srv_name_tmp, s);
 | |
|         fix_section_name(srv_name_tmp);
 | |
| 
 | |
|         if (strlen(srv_name_tmp) > 0)
 | |
|         {
 | |
|             results[output_ind] = MXS_STRDUP(srv_name_tmp);
 | |
|             if (!results[output_ind])
 | |
|             {
 | |
|                 error = true;
 | |
|                 break;
 | |
|             }
 | |
|             output_ind++;
 | |
|         }
 | |
|         s = strtok_r(NULL, ",", &lasts);
 | |
|     }
 | |
| 
 | |
|     if (error)
 | |
|     {
 | |
|         int i = 0;
 | |
|         while (results[i])
 | |
|         {
 | |
|             MXS_FREE(results[i]);
 | |
|             i++;
 | |
|         }
 | |
|         output_ind = 0;
 | |
|     }
 | |
| 
 | |
|     if (output_ind == 0)
 | |
|     {
 | |
|         MXS_FREE(results);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         *output_array = results;
 | |
|     }
 | |
|     return output_ind;
 | |
| }
 | |
| 
 | |
| json_t* config_maxscale_to_json(const char* host)
 | |
| {
 | |
|     json_t* param = json_object();
 | |
|     json_object_set_new(param, "libdir", json_string(get_libdir()));
 | |
|     json_object_set_new(param, "datadir", json_string(get_datadir()));
 | |
|     json_object_set_new(param, "process_datadir", json_string(get_process_datadir()));
 | |
|     json_object_set_new(param, "cachedir", json_string(get_cachedir()));
 | |
|     json_object_set_new(param, "configdir", json_string(get_configdir()));
 | |
|     json_object_set_new(param, "config_persistdir", json_string(get_config_persistdir()));
 | |
|     json_object_set_new(param, "module_configdir", json_string(get_module_configdir()));
 | |
|     json_object_set_new(param, "piddir", json_string(get_piddir()));
 | |
|     json_object_set_new(param, "logdir", json_string(get_logdir()));
 | |
|     json_object_set_new(param, "langdir", json_string(get_langdir()));
 | |
|     json_object_set_new(param, "execdir", json_string(get_execdir()));
 | |
|     json_object_set_new(param, "connector_plugindir", json_string(get_connector_plugindir()));
 | |
|     json_object_set_new(param, CN_THREADS, json_integer(config_threadcount()));
 | |
|     json_object_set_new(param, CN_THREAD_STACK_SIZE, json_integer(config_thread_stack_size()));
 | |
|     json_object_set_new(param, CN_WRITEQ_HIGH_WATER, json_integer(config_writeq_high_water()));
 | |
|     json_object_set_new(param, CN_WRITEQ_LOW_WATER, json_integer(config_writeq_low_water()));
 | |
| 
 | |
|     MXS_CONFIG* cnf = config_get_global_options();
 | |
| 
 | |
|     json_object_set_new(param, CN_AUTH_CONNECT_TIMEOUT, json_integer(cnf->auth_conn_timeout));
 | |
|     json_object_set_new(param, CN_AUTH_READ_TIMEOUT, json_integer(cnf->auth_read_timeout));
 | |
|     json_object_set_new(param, CN_AUTH_WRITE_TIMEOUT, json_integer(cnf->auth_write_timeout));
 | |
|     json_object_set_new(param, CN_SKIP_PERMISSION_CHECKS, json_boolean(cnf->skip_permission_checks));
 | |
|     json_object_set_new(param, CN_ADMIN_AUTH, json_boolean(cnf->admin_auth));
 | |
|     json_object_set_new(param, CN_ADMIN_ENABLED, json_boolean(cnf->admin_enabled));
 | |
|     json_object_set_new(param, CN_ADMIN_LOG_AUTH_FAILURES, json_boolean(cnf->admin_log_auth_failures));
 | |
|     json_object_set_new(param, CN_ADMIN_HOST, json_string(cnf->admin_host));
 | |
|     json_object_set_new(param, CN_ADMIN_PORT, json_integer(cnf->admin_port));
 | |
|     json_object_set_new(param, CN_ADMIN_SSL_KEY, json_string(cnf->admin_ssl_key));
 | |
|     json_object_set_new(param, CN_ADMIN_SSL_CERT, json_string(cnf->admin_ssl_cert));
 | |
|     json_object_set_new(param, CN_ADMIN_SSL_CA_CERT, json_string(cnf->admin_ssl_ca_cert));
 | |
|     json_object_set_new(param, CN_PASSIVE, json_boolean(cnf->passive));
 | |
| 
 | |
|     json_object_set_new(param, CN_QUERY_CLASSIFIER, json_string(cnf->qc_name));
 | |
| 
 | |
|     if (cnf->qc_args)
 | |
|     {
 | |
|         json_object_set_new(param, CN_QUERY_CLASSIFIER_ARGS, json_string(cnf->qc_args));
 | |
|     }
 | |
| 
 | |
|     json_t* attr = json_object();
 | |
|     time_t started = maxscale_started();
 | |
|     time_t activated = started + MXS_CLOCK_TO_SEC(cnf->promoted_at);
 | |
|     json_object_set_new(attr, CN_PARAMETERS, param);
 | |
|     json_object_set_new(attr, "version", json_string(MAXSCALE_VERSION));
 | |
|     json_object_set_new(attr, "commit", json_string(MAXSCALE_COMMIT));
 | |
|     json_object_set_new(attr, "started_at", json_string(http_to_date(started).c_str()));
 | |
|     json_object_set_new(attr, "activated_at", json_string(http_to_date(activated).c_str()));
 | |
|     json_object_set_new(attr, "uptime", json_integer(maxscale_uptime()));
 | |
| 
 | |
|     json_t* obj = json_object();
 | |
|     json_object_set_new(obj, CN_ATTRIBUTES, attr);
 | |
|     json_object_set_new(obj, CN_ID, json_string(CN_MAXSCALE));
 | |
|     json_object_set_new(obj, CN_TYPE, json_string(CN_MAXSCALE));
 | |
| 
 | |
|     return mxs_json_resource(host, MXS_JSON_API_MAXSCALE, obj);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a global configuration at the location pointed by @c filename
 | |
|  *
 | |
|  * @param filename Filename where configuration is written
 | |
|  * @return True on success, false on error
 | |
|  */
 | |
| static bool create_global_config(const char *filename)
 | |
| {
 | |
|     int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 | |
| 
 | |
|     if (file == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to open file '%s' when serializing global configuration: %d, %s",
 | |
|                   filename, errno, mxs_strerror(errno));
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     dprintf(file, "[maxscale]\n");
 | |
|     dprintf(file, "%s=%u\n", CN_AUTH_CONNECT_TIMEOUT, gateway.auth_conn_timeout);
 | |
|     dprintf(file, "%s=%u\n", CN_AUTH_READ_TIMEOUT, gateway.auth_read_timeout);
 | |
|     dprintf(file, "%s=%u\n", CN_AUTH_WRITE_TIMEOUT, gateway.auth_write_timeout);
 | |
|     dprintf(file, "%s=%s\n", CN_ADMIN_AUTH, gateway.admin_auth ? "true" : "false");
 | |
|     dprintf(file, "%s=%u\n", CN_PASSIVE, gateway.passive);
 | |
| 
 | |
|     close(file);
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool config_global_serialize()
 | |
| {
 | |
|     static const char* GLOBAL_CONFIG_NAME = "global-options";
 | |
|     bool rval = false;
 | |
|     char filename[PATH_MAX];
 | |
| 
 | |
|     snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(),
 | |
|              GLOBAL_CONFIG_NAME);
 | |
| 
 | |
|     if (unlink(filename) == -1 && errno != ENOENT)
 | |
|     {
 | |
|         MXS_ERROR("Failed to remove temporary global configuration at '%s': %d, %s",
 | |
|                   filename, errno, mxs_strerror(errno));
 | |
|     }
 | |
|     else if (create_global_config(filename))
 | |
|     {
 | |
|         char final_filename[PATH_MAX];
 | |
|         strcpy(final_filename, filename);
 | |
| 
 | |
|         char *dot = strrchr(final_filename, '.');
 | |
|         ss_dassert(dot);
 | |
|         *dot = '\0';
 | |
| 
 | |
|         if (rename(filename, final_filename) == 0)
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Failed to rename temporary server configuration at '%s': %d, %s",
 | |
|                       filename, errno, mxs_strerror(errno));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Test if first and last char in the string are as expected.
 | |
|  *
 | |
|  * @param string Input string
 | |
|  * @param expected Required character
 | |
|  * @return True, if string has at least two chars and both first and last char
 | |
|  * equal @c expected
 | |
|  */
 | |
| static bool check_first_last_char(const char* string, char expected)
 | |
| {
 | |
|     bool valid = false;
 | |
|     {
 | |
|         size_t len = strlen(string);
 | |
|         if ((len >= 2) && (string[0] == expected) && (string[len - 1] == expected))
 | |
|         {
 | |
|             valid = true;
 | |
|         }
 | |
|     }
 | |
|     return valid;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Chop a char off from both ends of the string.
 | |
|  *
 | |
|  * @param value Input string
 | |
|  */
 | |
| static void remove_first_last_char(char* value)
 | |
| {
 | |
|     size_t len = strlen(value);
 | |
|     value[len - 1] = '\0';
 | |
|     memmove(value, value + 1, len - 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Compile a regex string using PCRE2 using the settings provided.
 | |
|  *
 | |
|  * @param regex_string The string to compile
 | |
|  * @param jit_enabled Enable JIT compilation. If true but JIT is not available,
 | |
|  * a warning is printed.
 | |
|  * @param options PCRE2 compilation options
 | |
|  * @param output_ovector_size Output for the match data ovector size. On error,
 | |
|  * nothing is written. If NULL, the parameter is ignored.
 | |
|  * @return Compiled regex code on success, NULL otherwise
 | |
|  */
 | |
| static pcre2_code* compile_regex_string(const char* regex_string, bool jit_enabled,
 | |
|                                         uint32_t options, uint32_t* output_ovector_size)
 | |
| {
 | |
|     bool success = true;
 | |
|     int errorcode = -1;
 | |
|     PCRE2_SIZE error_offset = -1;
 | |
|     uint32_t capcount = 0;
 | |
|     pcre2_code* machine =
 | |
|         pcre2_compile((PCRE2_SPTR) regex_string, PCRE2_ZERO_TERMINATED, options,
 | |
|                       &errorcode, &error_offset, NULL);
 | |
|     if (machine)
 | |
|     {
 | |
|         if (jit_enabled)
 | |
|         {
 | |
|             // Try to compile even further for faster matching
 | |
|             if (pcre2_jit_compile(machine, PCRE2_JIT_COMPLETE) < 0)
 | |
|             {
 | |
|                 MXS_WARNING("PCRE2 JIT compilation of pattern '%s' failed, "
 | |
|                             "falling back to normal compilation.", regex_string);
 | |
|             }
 | |
|         }
 | |
|         /* Check what is the required match_data size for this pattern. */
 | |
|         int ret_info = pcre2_pattern_info(machine, PCRE2_INFO_CAPTURECOUNT, &capcount);
 | |
|         if (ret_info != 0)
 | |
|         {
 | |
|             MXS_PCRE2_PRINT_ERROR(ret_info);
 | |
|             success = false;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("Invalid PCRE2 regular expression '%s' (position '%zu').",
 | |
|                   regex_string, error_offset);
 | |
|         MXS_PCRE2_PRINT_ERROR(errorcode);
 | |
|         success = false;
 | |
|     }
 | |
| 
 | |
|     if (!success)
 | |
|     {
 | |
|         pcre2_code_free(machine);
 | |
|         machine = NULL;
 | |
|     }
 | |
|     else if (output_ovector_size)
 | |
|     {
 | |
|         *output_ovector_size = capcount + 1;
 | |
|     }
 | |
|     return machine;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Test if the given string is a valid MaxScale regular expression and can be
 | |
|  * compiled to a regex machine using PCRE2.
 | |
|  *
 | |
|  * @param regex_string The input string
 | |
|  * @return True if compilation succeeded, false if string is invalid or cannot
 | |
|  * be compiled.
 | |
|  */
 | |
| static bool test_regex_string_validity(const char* regex_string, const char* key)
 | |
| {
 | |
|     if (*regex_string == '\0')
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
|     char regex_copy[strlen(regex_string) + 1];
 | |
|     strcpy(regex_copy, regex_string);
 | |
|     if (!check_first_last_char(regex_string, '/'))
 | |
|     {
 | |
|         //return false; // Uncomment this line once '/ .. /' is no longer optional
 | |
|         MXS_WARNING("Missing slashes (/) around a regular expression is deprecated: '%s=%s'.",
 | |
|                     key, regex_string);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         remove_first_last_char(regex_copy);
 | |
|     }
 | |
| 
 | |
|     pcre2_code* code = compile_regex_string(regex_copy, false, 0, NULL);
 | |
|     bool rval = (code != NULL);
 | |
|     pcre2_code_free(code);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Converts a string into the corresponding value, interpreting
 | |
|  * IEC or SI prefixes used as suffixes appropriately.
 | |
|  *
 | |
|  * @param value A numerical string, possibly suffixed by a IEC
 | |
|  *              binary prefix or SI prefix.
 | |
|  *
 | |
|  * @return The corresponding size.
 | |
|  */
 | |
| static uint64_t get_suffixed_size(const char* value)
 | |
| {
 | |
|     char *end;
 | |
|     uint64_t size = strtoll(value, &end, 10);
 | |
| 
 | |
|     switch (*end)
 | |
|     {
 | |
|     case 'T':
 | |
|     case 't':
 | |
|         if ((*(end + 1) == 'i') || (*(end + 1) == 'I'))
 | |
|         {
 | |
|             size *= 1024ULL * 1024ULL * 1024ULL * 1024ULL;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             size *= 1000ULL * 1000ULL * 1000ULL * 1000ULL;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case 'G':
 | |
|     case 'g':
 | |
|         if ((*(end + 1) == 'i') || (*(end + 1) == 'I'))
 | |
|         {
 | |
|             size *= 1024ULL * 1024ULL * 1024ULL;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             size *= 1000ULL * 1000ULL * 1000ULL;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case 'M':
 | |
|     case 'm':
 | |
|         if ((*(end + 1) == 'i') || (*(end + 1) == 'I'))
 | |
|         {
 | |
|             size *= 1024ULL * 1024ULL;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             size *= 1000ULL * 1000ULL;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case 'K':
 | |
|     case 'k':
 | |
|         if ((*(end + 1) == 'i') || (*(end + 1) == 'I'))
 | |
|         {
 | |
|             size *= 1024ULL;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             size *= 1000ULL;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     return size;
 | |
| }
 | 
