/*
 * 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 <fstream>
#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/hk_heartbeat.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_TICKS[]                         = "ticks";
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";

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 */
static CONFIG_CONTEXT config_context;

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,
    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_AUTHENTICATOR_OPTIONS,
    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
};

void config_init()
{
    config_context.object = (char*)"";
    config_context.next = NULL;
}

void config_finish()
{
    config_context_free(config_context.next);
}

/**
 * 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;
}

static bool is_maxscale_section(const char* section)
{
    return strcasecmp(section, CN_GATEWAY) == 0 || strcasecmp(section, CN_MAXSCALE) == 0;
}

static bool is_root_config_file = 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 (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;
    }

    if (is_maxscale_section(section))
    {
        if (is_root_config_file || is_persisted_config)
        {
            return handle_global_item(name, value);
        }
        else
        {
            MXS_ERROR("The [maxscale] section must only be defined in the root configuration file.");
            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);
        }
    }

    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 && *filename != '.') // that have a suffix and are not hidden,
        {
            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;
}

bool export_config_file(const char* filename)
{
    bool rval = true;
    std::vector<CONFIG_CONTEXT*> contexts;

    // The config objects are stored in reverse order so first convert it back
    // to the correct order
    for (CONFIG_CONTEXT* ctx = config_context.next; ctx; ctx = ctx->next)
    {
        contexts.push_back(ctx);
    }

    std::ofstream file(filename);

    if (file)
    {
        time_t now = time(NULL);
        file << "# Generated by MaxScale " << MAXSCALE_VERSION << '\n';
        file << "# Documentation: https://mariadb.com/kb/en/mariadb-enterprise/maxscale/ \n\n";

        for (auto it = contexts.rbegin(); it != contexts.rend(); it++)
        {
            CONFIG_CONTEXT* ctx = *it;

            file << '[' << ctx->object << "]\n";

            // Parameters are also stored in reverse order
            std::vector<MXS_CONFIG_PARAMETER*> params;

            for (MXS_CONFIG_PARAMETER* p = ctx->parameters; p; p = p->next)
            {
                params.push_back(p);
            }

            for (auto pit = params.rbegin(); pit != params.rend(); pit++)
            {
                MXS_CONFIG_PARAMETER* p = *pit;
                file << p->name << '=' << p->value << '\n';
            }

            file << '\n';
        }
    }
    else
    {
        MXS_ERROR("Failed to open configuration export file '%s': %d, %s",
                  filename, errno, mxs_strerror(errno));
        rval = false;
    }

    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))
    {
        if (config_load_single_file(filename, &dcontext, &config_context))
        {
            is_root_config_file = false;
            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, &config_context);
            }

            /** 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, &config_context);
                    duplicate_context_finish(&p_dcontext);
                }
                else
                {
                    rval = false;
                }
                is_persisted_config = false;
            }

            if (rval)
            {
                if (!check_config_objects(config_context.next) || !process_config(config_context.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);
                    }
                }
            }
        }

        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)
    {
        if (is_maxscale_section(obj->object))
        {
            obj = obj->next;
            continue;
        }

        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)
        {
            if (is_maxscale_section(obj->object))
            {
                obj = obj->next;
                continue;
            }

            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;
}

/**
 * Free a configuration parameter
 * @param p1 Parameter to free
 */
void config_parameter_free(MXS_CONFIG_PARAMETER* p1)
{
    while (p1)
    {
        MXS_FREE(p1->name);
        MXS_FREE(p1->value);
        MXS_CONFIG_PARAMETER* p2 = p1->next;
        MXS_FREE(p1);
        p1 = p2;
    }
}

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;
    }
}

/**
 * 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;
}

/**
 * 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)
{
    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_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_THREADS);
            gateway.n_threads = MXS_MAX_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)
    {
        gateway.n_nbpoll = atoi(value);
    }
    else if (strcmp(name, CN_POLL_SLEEP) == 0)
    {
        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
        {
            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
        for (i = 0; lognames[i].name; i++)
        {
            if (strcasecmp(name, lognames[i].name) == 0)
            {
                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));
            }
        }
    }
    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 && listener_set_ssl_version(new_ssl, ssl_version) != 0)
            {
                MXS_ERROR("Unknown parameter value for 'ssl_version' for '%s': %s",
                          obj->object, ssl_version);
                local_errors++;
            }

            if (ssl_cert_verify_depth &&
                (new_ssl->ssl_cert_verify_depth = atoi(ssl_cert_verify_depth)) < 0)
            {
                MXS_ERROR("Invalid parameter value for 'ssl_cert_verify_depth for '%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 '%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)
            {
                if (new_ssl->ssl_cert == NULL)
                {
                    local_errors++;
                    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(new_ssl->ssl_cert, F_OK) != 0)
                {
                    MXS_ERROR("Server certificate file for listener '%s' not found: %s",
                              obj->object, new_ssl->ssl_cert);
                    local_errors++;
                }

                if (new_ssl->ssl_key == NULL)
                {
                    local_errors++;
                    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(new_ssl->ssl_key, F_OK) != 0)
                {
                    MXS_ERROR("Server private key file for listener '%s' not found: %s",
                              obj->object, new_ssl->ssl_key);
                    local_errors++;
                }
            }

            if (new_ssl->ssl_ca_cert == NULL)
            {
                local_errors++;
                MXS_ERROR("CA Certificate missing for '%s'."
                          "Please provide the path to the certificate authority "
                          "certificate by adding the ssl_ca_cert=<path> parameter",
                          obj->object);
            }
            else if (access(new_ssl->ssl_ca_cert, F_OK) != 0)
            {
                MXS_ERROR("Certificate authority file for '%s' not found: %s",
                          obj->object, new_ssl->ssl_ca_cert);
                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;
    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;
    }
}

/**
 * @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)
    {
        if (is_maxscale_section(obj->object))
        {
            obj = obj->next;
            continue;
        }

        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)
        {
            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);
                    }
                }
                params = params->next;
            }
        }

        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;
}

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;
}

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);
    }


    /** 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;
        }
    }
    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);
    char *auth_opts = config_get_value(obj->parameters, CN_AUTHENTICATOR_OPTIONS);

    if (address && port && protocol)
    {
        if ((obj->element = server_alloc(obj->object, address, atoi(port), protocol, auth, auth_opts)) == 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->unique_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->unique_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->unique_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_alloc(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);
            monitorAddParameters(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)
            {
                monitorSetInterval(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)
            {
                monitorSetJournalMaxAge(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)
            {
                monitorSetScriptTimeout(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 (!monitorSetNetworkTimeout(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 (!monitorSetNetworkTimeout(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 (!monitorSetNetworkTimeout(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 (!monitorSetNetworkTimeout(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);
                        }
                        monitorAddServer(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)
        {
            monitorAddUser(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(&params[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()));

    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 + HB_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;
}
