/*
   SSSD

   SSSD Configuration DB

   Copyright (C) Simo Sorce <ssorce@redhat.com>	2008

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"

#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include "sbus/sbus_opath.h"
#include "util/util.h"
#include "confdb/confdb.h"
#include "confdb/confdb_private.h"
#include "util/strtonum.h"
#include "db/sysdb.h"

#define CONFDB_ZERO_CHECK_OR_JUMP(var, ret, err, label) do { \
    if (!var) { \
        ret = err; \
        goto label; \
    } \
} while(0)

/* Warning messages */
#define SAME_DOMAINS_ERROR_MSG "Domain '%s' is the same as or differs only "\
                               "in case from domain '%s'.\n"

#define RETRIEVE_DOMAIN_ERROR_MSG "Error (%d [%s]) retrieving domain [%s], "\
                                  "skipping!\n"

static errno_t set_ldb_modules_path(void)
{
#ifdef LDB_MODULES_PATH_OVERRIDE
    static bool initialized = false;
    int ret;

    if (initialized) {
        return EOK;
    }

    ret = setenv("LDB_MODULES_PATH", LDB_MODULES_PATH_OVERRIDE, 0);
    if (ret != 0) {
        ret = errno;
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Failed to set LDB_MODULES_PATH to [%s]: %s\n",
              LDB_MODULES_PATH_OVERRIDE, strerror(ret));
        return ret;
    }

    DEBUG(SSSDBG_CONF_SETTINGS,
          "LDB_MODULES_PATH set to [%s]\n",
          LDB_MODULES_PATH_OVERRIDE);

    initialized = true;
#endif
    return EOK;
}

static int confdb_get_domain_enabled(struct confdb_ctx *cdb,
                                     const char *domain, bool *_enabled);


static char *prepend_cn(char *str, int *slen, const char *comp, int clen)
{
    char *ret;

    ret = talloc_realloc(NULL, str, char, *slen + 4 + clen + 1);
    if (!ret)
        return NULL;

    /* move current string to the end */
    memmove(&ret[clen +4], ret, *slen+1); /* includes termination */
    memcpy(ret, "cn=", 3);
    memcpy(&ret[3], comp, clen);
    ret[clen+3] = ',';

    *slen = *slen + 4 + clen;

    return ret;
}

int parse_section(TALLOC_CTX *mem_ctx, const char *section,
                  char **sec_dn, const char **rdn_name)
{
    TALLOC_CTX *tmp_ctx;
    char *dn = NULL;
    char *p;
    const char *s;
    int l, ret;

    /* section must be a non null string and must not start with '/' */
    if (!section || !*section || *section == '/') return EINVAL;

    tmp_ctx = talloc_new(mem_ctx);
    if (!tmp_ctx) return ENOMEM;

    s = section;
    l = 0;
    while ((p = strchrnul(s, '/'))) {
        if (l == 0) {
            dn = talloc_asprintf(tmp_ctx, "cn=%s", s);
            l = 3 + (p-s);
            dn[l] = '\0';
        } else {
            dn = prepend_cn(dn, &l, s, p-s);
        }
        if (!dn) {
            ret = ENOMEM;
            goto done;
        }
        if (*p == '\0') {
            if (rdn_name) *rdn_name = s;
            break; /* reached end */
        }
        s = p+1;
        if (*s == '\0') { /* a section cannot end in '.' */
            ret = EINVAL;
            goto done;
        }
    }

    *sec_dn = talloc_steal(mem_ctx, dn);
    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_add_param(struct confdb_ctx *cdb,
                     bool replace,
                     const char *section,
                     const char *attribute,
                     const char **values)
{
    TALLOC_CTX *tmp_ctx = NULL;
    struct ldb_message *msg;
    struct ldb_result *res;
    struct ldb_dn *dn;
    char *secdn;
    const char *rdn_name;
    int ret, i;

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) {
        ret = ENOMEM;
        goto done;
    }

    ret = parse_section(tmp_ctx, section, &secdn, &rdn_name);
    if (ret != EOK) {
        goto done;
    }

    dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
    CONFDB_ZERO_CHECK_OR_JUMP(dn, ret, EIO, done);

    ret = ldb_search(cdb->ldb, tmp_ctx, &res,
                     dn, LDB_SCOPE_BASE, NULL, NULL);
    if (ret != LDB_SUCCESS) {
        ret = EIO;
        goto done;
    }

    msg = ldb_msg_new(tmp_ctx);
    CONFDB_ZERO_CHECK_OR_JUMP(msg, ret, ENOMEM, done);

    msg->dn = talloc_steal(msg, dn);
    CONFDB_ZERO_CHECK_OR_JUMP(msg->dn, ret, ENOMEM, done);

    if (res->count == 0) { /* add a new message */
        errno = 0;

        /* cn first */
        ret = ldb_msg_add_string(msg, "cn", rdn_name);
        if (ret != LDB_SUCCESS) {
            if (errno) ret = errno;
            else ret = EIO;
            goto done;
        }

        /* now the requested attribute */
        for (i = 0; values[i]; i++) {
            ret = ldb_msg_add_string(msg, attribute, values[i]);
            if (ret != LDB_SUCCESS) {
                if (errno) ret = errno;
                else ret = EIO;
                goto done;
            }
        }

        ret = ldb_add(cdb->ldb, msg);
        if (ret != LDB_SUCCESS) {
            ret = EIO;
            goto done;
        }

    } else {
        int optype;
        errno = 0;

        /* mark this as a replacement */
        if (replace) optype = LDB_FLAG_MOD_REPLACE;
        else optype = LDB_FLAG_MOD_ADD;
        ret = ldb_msg_add_empty(msg, attribute, optype, NULL);
        if (ret != LDB_SUCCESS) {
            if (errno) ret = errno;
            else ret = EIO;
            goto done;
        }

        /* now the requested attribute */
        for (i = 0; values[i]; i++) {
            ret = ldb_msg_add_string(msg, attribute, values[i]);
            if (ret != LDB_SUCCESS) {
                if (errno) ret = errno;
                else ret = EIO;
                goto done;
            }
        }

        ret = ldb_modify(cdb->ldb, msg);
        if (ret != LDB_SUCCESS) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                  "ldb_modify failed: [%s](%d)[%s]\n",
                  ldb_strerror(ret), ret, ldb_errstring(cdb->ldb));
            ret = EIO;
            goto done;
        }
    }

    ret = EOK;

done:
    talloc_free(tmp_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to add [%s] to [%s], error [%d] (%s)\n",
                  attribute, section, ret, strerror(ret));
    }
    return ret;
}

int confdb_get_param(struct confdb_ctx *cdb,
                     TALLOC_CTX *mem_ctx,
                     const char *section,
                     const char *attribute,
                     char ***values)
{
    TALLOC_CTX *tmp_ctx;
    struct ldb_result *res;
    struct ldb_dn *dn;
    char *secdn = NULL;
    const char *attrs[] = { attribute, NULL };
    char **vals;
    struct ldb_message_element *el;
    int ret, i;

    tmp_ctx = talloc_new(mem_ctx);
    if (!tmp_ctx)
        return ENOMEM;

    ret = parse_section(tmp_ctx, section, &secdn, NULL);
    if (ret != EOK) {
        goto done;
    }

    dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
    if (!dn) {
        ret = EIO;
        goto done;
    }

    ret = ldb_search(cdb->ldb, tmp_ctx, &res,
                     dn, LDB_SCOPE_BASE, attrs, NULL);
    if (ret != LDB_SUCCESS) {
        ret = EIO;
        goto done;
    }
    if (res->count > 1) {
        ret = EIO;
        goto done;
    }

    vals = talloc_zero(mem_ctx, char *);
    ret = EOK;

    if (res->count > 0) {
        el = ldb_msg_find_element(res->msgs[0], attribute);
        if (el && el->num_values > 0) {
            vals = talloc_realloc(mem_ctx, vals, char *, el->num_values +1);
            if (!vals) {
                ret = ENOMEM;
                goto done;
            }
            /* should always be strings so this should be safe */
            for (i = 0; i < el->num_values; i++) {
                struct ldb_val v = el->values[i];
                vals[i] = talloc_strndup(vals, (char *)v.data, v.length);
                if (!vals[i]) {
                    ret = ENOMEM;
                    goto done;
                }
            }
            vals[i] = NULL;
        }
    }

    *values = vals;

done:
    talloc_free(tmp_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to get [%s] from [%s], error [%d] (%s)\n",
                  attribute, section, ret, strerror(ret));
    }
    return ret;
}

int confdb_set_string(struct confdb_ctx *cdb,
                      const char *section,
                      const char *attribute,
                      const char *val)
{
    TALLOC_CTX *tmp_ctx;
    struct ldb_dn *dn;
    char *secdn;
    struct ldb_message *msg;
    int ret, lret;

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) {
        return ENOMEM;
    }

    ret = parse_section(tmp_ctx, section, &secdn, NULL);
    if (ret != EOK) {
        goto done;
    }

    dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
    if (!dn) {
        ret = EIO;
        goto done;
    }

    msg = ldb_msg_new(tmp_ctx);
    if (!msg) {
        ret = ENOMEM;
        goto done;
    }

    msg->dn = dn;

    lret = ldb_msg_add_empty(msg, attribute, LDB_FLAG_MOD_REPLACE, NULL);
    if (lret != LDB_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldb_msg_add_empty failed: [%s]\n", ldb_strerror(lret));
        ret = EIO;
        goto done;
    }

    lret = ldb_msg_add_string(msg, attribute, val);
    if (lret != LDB_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldb_msg_add_string failed: [%s]\n", ldb_strerror(lret));
        ret = EIO;
        goto done;
    }

    lret = ldb_modify(cdb->ldb, msg);
    if (lret != LDB_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldb_modify failed: [%s](%d)[%s]\n",
              ldb_strerror(lret), lret, ldb_errstring(cdb->ldb));
        ret = EIO;
        goto done;
    }

    ret = EOK;

done:
    talloc_free(tmp_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to set [%s] from [%s], error [%d] (%s)\n",
               attribute, section, ret, strerror(ret));
    }
    return ret;
}

int confdb_get_string(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
                      const char *section, const char *attribute,
                      const char *defstr, char **result)
{
    char **values = NULL;
    char *restr;
    int ret;

    ret = confdb_get_param(cdb, ctx, section, attribute, &values);
    if (ret != EOK) {
        goto failed;
    }

    if (values[0]) {
        if (values[1] != NULL) {
            /* too many values */
            ret = EINVAL;
            goto failed;
        }
        restr = talloc_steal(ctx, values[0]);
    } else {
        /* Did not return a value, so use the default */

        if (defstr == NULL) { /* No default given */
            *result = NULL;
            talloc_free(values);
            return EOK;
        }

        /* Copy the default string */
        restr = talloc_strdup(ctx, defstr);
    }
    if (!restr) {
        ret = ENOMEM;
        goto failed;
    }

    talloc_free(values);

    *result = restr;
    return EOK;

failed:
    talloc_free(values);
    DEBUG(SSSDBG_CRIT_FAILURE,
          "Failed to get [%s] from [%s], error [%d] (%s)\n",
              attribute, section, ret, strerror(ret));
    return ret;
}

int confdb_get_int(struct confdb_ctx *cdb,
                   const char *section, const char *attribute,
                   int defval, int *result)
{
    char **values = NULL;
    long val;
    int ret;
    char *endptr;
    TALLOC_CTX *tmp_ctx;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        ret = ENOMEM;
        goto failed;
    }

    ret = confdb_get_param(cdb, tmp_ctx, section, attribute, &values);
    if (ret != EOK) {
        goto failed;
    }

    if (values[0]) {
        if (values[1] != NULL) {
            /* too many values */
            ret = EINVAL;
            goto failed;
        }

        errno = 0;
        val = strtol(values[0], &endptr, 0);
        ret = errno;
        if (ret != 0) {
            goto failed;
        }
        if (*endptr || (values[0] == endptr)) {
            ret = EINVAL;
            goto failed;
        }
        if (val < INT_MIN || val > INT_MAX) {
            ret = ERANGE;
            goto failed;
        }

    } else {
        val = defval;
    }

    talloc_free(tmp_ctx);

    *result = (int)val;
    return EOK;

failed:
    talloc_free(tmp_ctx);
    DEBUG(SSSDBG_CRIT_FAILURE,
          "Failed to read [%s] from [%s], error [%d] (%s)\n",
              attribute, section, ret, strerror(ret));
    return ret;
}

long confdb_get_long(struct confdb_ctx *cdb,
                     const char *section, const char *attribute,
                     long defval, long *result)
{
    char **values = NULL;
    long val;
    int ret;
    char *endptr;
    TALLOC_CTX *tmp_ctx;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        ret = ENOMEM;
        goto failed;
    }

    ret = confdb_get_param(cdb, tmp_ctx, section, attribute, &values);
    if (ret != EOK) {
        goto failed;
    }

    if (values[0]) {
        if (values[1] != NULL) {
            /* too many values */
            ret = EINVAL;
            goto failed;
        }

        errno = 0;
        val = strtol(values[0], &endptr, 0);
        ret = errno;
        if (ret != 0) {
            goto failed;
        }
        if (*endptr || (values[0] == endptr)) {
            ret = EINVAL;
            goto failed;
        }
    } else {
        val = defval;
    }

    talloc_free(tmp_ctx);

    *result = val;
    return EOK;

failed:
    talloc_free(tmp_ctx);
    DEBUG(SSSDBG_CRIT_FAILURE,
          "Failed to read [%s] from [%s], error [%d] (%s)\n",
              attribute, section, ret, strerror(ret));
    return ret;
}

int confdb_get_bool(struct confdb_ctx *cdb,
                    const char *section, const char *attribute,
                    bool defval, bool *result)
{
    char **values = NULL;
    bool val;
    int ret;
    TALLOC_CTX *tmp_ctx;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        ret = ENOMEM;
        goto failed;
    }

    ret = confdb_get_param(cdb, tmp_ctx, section, attribute, &values);
    if (ret != EOK) {
        goto failed;
    }

    if (values[0]) {
        if (values[1] != NULL) {
            /* too many values */
            ret = EINVAL;
            goto failed;
        }

        if (strcasecmp(values[0], "FALSE") == 0) {
            val = false;

        } else if (strcasecmp(values[0], "TRUE") == 0) {
            val = true;

        } else {

            DEBUG(SSSDBG_OP_FAILURE, "Value is not a boolean!\n");
            ret = EINVAL;
            goto failed;
        }

    } else {
        val = defval;
    }

    talloc_free(tmp_ctx);

    *result = val;
    return EOK;

failed:
    talloc_free(tmp_ctx);
    DEBUG(SSSDBG_CRIT_FAILURE,
          "Failed to read [%s] from [%s], error [%d] (%s)\n",
              attribute, section, ret, strerror(ret));
    return ret;
}

/* WARNING: Unlike other similar functions, this one does NOT take a default,
 * and returns ENOENT if the attribute was not found ! */
int confdb_get_string_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
                              const char *section, const char *attribute,
                              char ***result)
{
    char **values = NULL;
    int ret;

    ret = confdb_get_param(cdb, ctx, section, attribute, &values);
    if (ret != EOK) {
        goto done;
    }

    if (values && values[0]) {
        if (values[1] != NULL) {
            /* too many values */
            ret = EINVAL;
            goto done;
        }
    } else {
        /* Did not return a value */
        ret = ENOENT;
        goto done;
    }

    ret = split_on_separator(ctx, values[0], ',', true, true, result, NULL);

done:
    talloc_free(values);
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Failed to get [%s] from [%s], error [%d] (%s)\n",
                  attribute, section, ret, strerror(ret));
    }
    return ret;
}

static errno_t add_implicit_services(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
                                     char ***_services)
{
    int ret;
    char **domain_names;
    TALLOC_CTX *tmp_ctx;
    size_t c;
    char *conf_path;
    char *id_provider;
    bool add_pac = false;
    bool implicit_pac_responder = true;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
        return ENOMEM;
    }

    ret = confdb_get_enabled_domain_list(cdb, tmp_ctx, &domain_names);
    if (ret == ENOENT) {
        /* confdb_expand_app_domains() wasn't called yet, so this might be ok */
        ret = EOK;
        goto done;
    } else if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Error retrieving domains list [%d]: %s\n",
              ret, sss_strerror(ret));
        goto done;
    }

    ret = confdb_get_bool(cdb, CONFDB_MONITOR_CONF_ENTRY,
                          CONFDB_MONITOR_IMPLICIT_PAC_RESPONDER, true,
                          &implicit_pac_responder);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Failed to read implicit_pac_responder option, "
              "using default 'true'.\n");
        implicit_pac_responder = true;
    }

    for (c = 0; domain_names[c] != NULL; c++) {
        if (!is_valid_domain_name(domain_names[c])) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Skipping invalid domain name '%s'\n", domain_names[c]);
            continue;
        }
        conf_path = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL,
                                    domain_names[c]);
        if (conf_path == NULL) {
            DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
            ret = ENOMEM;
            goto done;
        }

        ret = confdb_get_string(cdb, tmp_ctx, conf_path,
                                CONFDB_DOMAIN_ID_PROVIDER, NULL, &id_provider);
        if (ret == EOK) {
            if (id_provider == NULL) {
                DEBUG(SSSDBG_OP_FAILURE, "id_provider is not set for "
                      "domain [%s], trying next domain.\n", domain_names[c]);
                continue;
            }

            if (strcasecmp(id_provider, "IPA") == 0
                        || strcasecmp(id_provider, "AD") == 0) {
                if (implicit_pac_responder) {
                    add_pac = true;
                } else {
                    DEBUG(SSSDBG_CONF_SETTINGS,
                          "PAC responder not enabled for id provider [%s] "
                          "because implicit_pac_responder is set to 'false'.\n",
                          id_provider);
                    add_pac = false;
                }
            }
        } else {
            DEBUG(SSSDBG_OP_FAILURE, "Failed to get id_provider for " \
                                      "domain [%s], trying next domain.\n",
                                      domain_names[c]);
        }
    }

    if (BUILD_WITH_PAC_RESPONDER && add_pac &&
        !string_in_list("pac", *_services, false)) {
        ret = add_string_to_list(mem_ctx, "pac", _services);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE, "add_string_to_list failed.\n");
            goto done;
        }
    }

    ret = EOK;

done:
    talloc_free(tmp_ctx);

    return ret;
}

int confdb_get_services_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
                                char ***_result)
{
    int ret;

    ret = confdb_get_string_as_list(cdb, ctx,
                                    CONFDB_MONITOR_CONF_ENTRY,
                                    CONFDB_MONITOR_ACTIVE_SERVICES,
                                    _result);
#ifdef HAVE_SYSTEMD
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Failed to get the explicitly configured services!\n");
        return EINVAL;
    }
#else
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "No services configured!\n");
        return EINVAL;
    }
#endif

    /* `add_implicit_services()` can handle (*_result == NULL) */
    ret = add_implicit_services(cdb, ctx, _result);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to add implicitly configured services\n");
        return EINVAL;
    }

    return EOK;
}

int confdb_init(TALLOC_CTX *mem_ctx,
                struct confdb_ctx **cdb_ctx,
                const char *confdb_location)
{
    struct confdb_ctx *cdb;
    int ret = EOK;
    mode_t old_umask;

    ret = set_ldb_modules_path();
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Failed to initialize LDB modules path [%d]: %s\n",
              ret, sss_strerror(ret));
        return ret;
    }

    if (cdb_ctx == NULL) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Bad argument\n");
        return EFAULT;
    }

    cdb = talloc_zero(mem_ctx, struct confdb_ctx);
    if (!cdb)
        return ENOMEM;

    /* Because confdb calls use sync ldb calls, we create a separate event
     * context here. This will prevent the ldb sync calls to start nested
     * events.
     * NOTE: this means that we *cannot* do async calls and return in confdb
     * unless we convert all calls and hook back to the main event context.
     */

    cdb->pev = tevent_context_init(cdb);
    if (!cdb->pev) {
        talloc_free(cdb);
        return EIO;
    }

    cdb->ldb = ldb_init(cdb, cdb->pev);
    if (!cdb->ldb) {
        talloc_free(cdb);
        return EIO;
    }

    ret = ldb_set_debug(cdb->ldb, ldb_debug_messages, NULL);
    if (ret != LDB_SUCCESS) {
        DEBUG(SSSDBG_FATAL_FAILURE,"Could not set up debug fn.\n");
        talloc_free(cdb);
        return EIO;
    }

    old_umask = umask(SSS_DFL_UMASK);
    ret = ldb_connect(cdb->ldb, confdb_location, 0, NULL);
    umask(old_umask);

    if (ret != LDB_SUCCESS) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Unable to open config database [%s]\n",
                  confdb_location);
        talloc_free(cdb);
        return EIO;
    }

    *cdb_ctx = cdb;

    return EOK;
}

static errno_t get_entry_as_uint32(struct ldb_message *msg,
                                   uint32_t *return_value,
                                   const char *entry,
                                   uint32_t default_value)
{
    const char *tmp = NULL;
    char *endptr;
    uint32_t u32ret = 0;

    *return_value = 0;

    if (!msg || !entry) {
        return EFAULT;
    }

    tmp = ldb_msg_find_attr_as_string(msg, entry, NULL);
    if (tmp == NULL) {
        *return_value = default_value;
        return EOK;
    }

    if ((*tmp == '-') || (*tmp == '\0')) {
        return EINVAL;
    }

    u32ret = strtouint32 (tmp, &endptr, 10);
    if (errno) {
        return errno;
    }

    if (*endptr != '\0') {
        /* Not all of the string was a valid number */
        return EINVAL;
    }

    *return_value = u32ret;
    return EOK;
}

static errno_t get_entry_as_bool(struct ldb_message *msg,
                                   bool *return_value,
                                   const char *entry,
                                   bool default_value)
{
    const char *tmp = NULL;

    *return_value = 0;

    if (!msg || !entry) {
        return EFAULT;
    }

    tmp = ldb_msg_find_attr_as_string(msg, entry, NULL);
    if (tmp == NULL || *tmp == '\0') {
        *return_value = default_value;
        return EOK;
    }

    if (strcasecmp(tmp, "FALSE") == 0) {
        *return_value = 0;
    }
    else if (strcasecmp(tmp, "TRUE") == 0) {
        *return_value = 1;
    }
    else {
        return EINVAL;
    }

    return EOK;
}


static errno_t init_cached_auth_timeout(struct confdb_ctx *cdb,
                                        struct ldb_message *msg,
                                        uint32_t *_cached_auth_timeout)
{
    int cred_expiration;
    int id_timeout;
    errno_t ret;
    uint32_t cached_auth_timeout;

    ret = get_entry_as_uint32(msg, &cached_auth_timeout,
                              CONFDB_DOMAIN_CACHED_AUTH_TIMEOUT, 0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n", CONFDB_DOMAIN_CACHED_AUTH_TIMEOUT);
        goto done;
    }

    ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
                         CONFDB_PAM_CRED_TIMEOUT, 0, &cred_expiration);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to read expiration time [%s] of offline credentials.\n",
              CONFDB_PAM_CRED_TIMEOUT);
        goto done;
    }

    /* convert from days to seconds */
    cred_expiration *= 3600 * 24;
    if (cred_expiration != 0 &&
        cred_expiration < cached_auth_timeout) {
        cached_auth_timeout = cred_expiration;
    }

    /* Set up the PAM identity timeout */
    ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
                         CONFDB_PAM_ID_TIMEOUT, 5,
                         &id_timeout);
    if (ret != EOK) goto done;

    if (cached_auth_timeout > id_timeout) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "cached_auth_timeout is greater than pam_id_timeout so be aware "
              "that back end could be called to handle initgroups.\n");
    }

    ret = EOK;

done:
    if (ret == EOK) {
        *_cached_auth_timeout = cached_auth_timeout;
    }
    return ret;
}

static int confdb_get_domain_section(TALLOC_CTX *mem_ctx,
                                     struct confdb_ctx *cdb,
                                     const char *section,
                                     const char *name,
                                     struct ldb_result **_res)
{
    TALLOC_CTX *tmp_ctx;
    int ret;
    struct ldb_result *res;
    struct ldb_dn *dn;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    dn = ldb_dn_new_fmt(tmp_ctx, cdb->ldb, "cn=%s,%s", name, section);
    if (dn == NULL) {
        ret = ENOMEM;
        goto done;
    }

    ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn,
                     LDB_SCOPE_BASE, NULL, NULL);
    if (ret != LDB_SUCCESS) {
        ret = sss_ldb_error_to_errno(ret);
        goto done;
    }

    if (res->count == 0) {
        ret = ENOENT;
        goto done;
    } else if (res->count > 1) {
        ret = E2BIG;
        goto done;
    }

    *_res = talloc_steal(mem_ctx, res);
    ret = EOK;
done:
    talloc_free(tmp_ctx);
    return ret;
}

static char *confdb_get_domain_hostname(TALLOC_CTX *mem_ctx,
                                        struct ldb_result *res,
                                        const char *provider)
{
    char sys[HOST_NAME_MAX + 1] = {'\0'};
    const char *opt = NULL;
    int ret;

    if (strcasecmp(provider, "ad") == 0) {
        opt = ldb_msg_find_attr_as_string(res->msgs[0], "ad_hostname", NULL);
    } else if (strcasecmp(provider, "ipa") == 0) {
        opt = ldb_msg_find_attr_as_string(res->msgs[0], "ipa_hostname", NULL);
    }

    if (opt != NULL) {
        return talloc_strdup(mem_ctx, opt);
    }

    ret = gethostname(sys, sizeof(sys));
    if (ret != 0) {
        ret = errno;
        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get hostname [%d]: %s\n", ret,
              sss_strerror(ret));
        return NULL;
    }

    return talloc_strdup(mem_ctx, sys);
}

static char *confdb_get_domain_bus(TALLOC_CTX *mem_ctx,
                                   struct sss_domain_info *domain)
{
    struct sss_domain_info *head;
    char *safe_name;
    char *bus_name;

    /* There is only one bus that belongs to the top level domain. */
    head = get_domains_head(domain);

    safe_name = sbus_opath_escape(mem_ctx, head->name);
    if (safe_name == NULL) {
        return NULL;
    }

    /* Parts of bus names must not start with digit thus we concatenate
     * the name with underscore instead of period. */
    bus_name = talloc_asprintf(mem_ctx, "sssd.domain_%s", safe_name);
    talloc_free(safe_name);

    return bus_name;
}

static errno_t confdb_init_domain(struct sss_domain_info *domain,
                                  struct ldb_result *res)
{
    errno_t ret;
    const char *tmp;

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], "cn", NULL);
    if (!tmp) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid configuration entry, fatal error!\n");
        ret = EINVAL;
        goto done;
    }
    domain->name = talloc_strdup(domain, tmp);
    if (!domain->name) {
        ret = ENOMEM;
        goto done;
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_DOMAIN_ID_PROVIDER,
                                      NULL);
    if (tmp) {
        domain->provider = talloc_strdup(domain, tmp);
        if (!domain->provider) {
            ret = ENOMEM;
            goto done;
        }
    }
    else {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Domain [%s] does not specify an ID provider, disabling!\n",
                  domain->name);
        ret = EINVAL;
        goto done;
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_DOMAIN_AUTO_UPG, NULL);
    if (tmp == NULL || *tmp == '\0') {
        if (domain->provider != NULL
                && strcasecmp(domain->provider, "idp") == 0) {
            tmp = "true";
        } else {
            tmp = "false";
        }
    }

    domain->mpg_mode = str_to_domain_mpg_mode(tmp);

    domain->timeout = ldb_msg_find_attr_as_int(res->msgs[0],
                                               CONFDB_DOMAIN_TIMEOUT, 0);

    ret = get_entry_as_bool(res->msgs[0], &domain->ignore_group_members,
                            CONFDB_DOMAIN_IGNORE_GROUP_MEMBERS, 0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for %s\n",
               CONFDB_DOMAIN_IGNORE_GROUP_MEMBERS);
        goto done;
    }

    ret = get_entry_as_uint32(res->msgs[0], &domain->id_min,
                              CONFDB_DOMAIN_MINID,
                              SSSD_MIN_ID);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Invalid value for minId\n");
        ret = EINVAL;
        goto done;
    }

    ret = get_entry_as_uint32(res->msgs[0], &domain->id_max,
                              CONFDB_DOMAIN_MAXID, 0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Invalid value for maxId\n");
        ret = EINVAL;
        goto done;
    }

    if (domain->id_max && (domain->id_max < domain->id_min)) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Invalid domain range\n");
        ret = EINVAL;
        goto done;
    }

    /* Do we allow to cache credentials */
    ret = get_entry_as_bool(res->msgs[0], &domain->cache_credentials,
                            CONFDB_DOMAIN_CACHE_CREDS, 0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for %s\n", CONFDB_DOMAIN_CACHE_CREDS);
        goto done;
    }

    ret = get_entry_as_uint32(res->msgs[0],
                              &domain->cache_credentials_min_ff_length,
                              CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH,
                              CONFDB_DEFAULT_CACHE_CREDS_MIN_FF_LENGTH);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for %s\n",
              CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH);
        goto done;
    }

    ret = get_entry_as_uint32(res->msgs[0], &domain->override_gid,
                              CONFDB_DOMAIN_OVERRIDE_GID, 0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n", CONFDB_DOMAIN_OVERRIDE_GID);
        goto done;
    }

    domain->hostname = confdb_get_domain_hostname(domain, res, domain->provider);
    if (domain->hostname == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain hostname\n");
        goto done;
    }

    domain->krb5_keytab = NULL;
    tmp = ldb_msg_find_attr_as_string(res->msgs[0], "krb5_keytab", NULL);
    if (tmp != NULL) {
        domain->krb5_keytab = talloc_strdup(domain, tmp);
        if (domain->krb5_keytab == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain keytab!\n");
            goto done;
        }
    }

    domain->has_views = false;
    domain->view_name = NULL;

    domain->state = DOM_ACTIVE;

    domain->conn_name = confdb_get_domain_bus(domain, domain);
    if (!domain->conn_name) {
        ret = ENOMEM;
        goto done;
    }

    domain->not_found_counter = 0;
    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_provider_and_enum(struct sss_domain_info *domain,
                                                    struct ldb_result *res)
{
    errno_t ret;
    const char *tmp, *tmp_pam_target, *tmp_auth;

    if (domain->provider != NULL &&
           ((strcasecmp(domain->provider, "ldap") == 0)
         || (strcasecmp(domain->provider, "proxy") == 0)) ) {
        ret = get_entry_as_bool(res->msgs[0], &domain->enumerate,
                                CONFDB_DOMAIN_ENUMERATE, false);
        if(ret != EOK) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Invalid value for %s\n", CONFDB_DOMAIN_ENUMERATE);
            goto done;
        }
    } else {
        domain->enumerate = false;
    }

    if (domain->provider != NULL && strcasecmp(domain->provider, "proxy") == 0) {
        /* The password field must be reported as 'x' for proxy provider
         * using files library, else pam_unix won't authenticate this entry.
         * We set this only for sssd-shadowutils target which can be used
         * to authenticate with pam_unix only. Otherwise we let administrator
         * to overwrite default * value with pwfield option to avoid regression
         * on more common use case where remote authentication is required. */
        tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                          CONFDB_PROXY_LIBNAME,
                                          NULL);

        tmp_auth = ldb_msg_find_attr_as_string(res->msgs[0],
                                               CONFDB_DOMAIN_AUTH_PROVIDER,
                                               NULL);

        tmp_pam_target = ldb_msg_find_attr_as_string(res->msgs[0],
                                                     CONFDB_PROXY_PAM_TARGET,
                                                     NULL);

        if (tmp != NULL && tmp_pam_target != NULL
            && strcasecmp(tmp, "files") == 0
            && (tmp_auth == NULL || strcasecmp(tmp_auth, "proxy") == 0)
            && strcmp(tmp_pam_target, "sssd-shadowutils") == 0) {
            domain->pwfield = "x";
        }
    }

    if (!domain->enumerate) {
        DEBUG(SSSDBG_TRACE_FUNC, "No enumeration for [%s]\n", domain->name);
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_fqn(struct confdb_ctx *cdb,
                                      TALLOC_CTX *mem_ctx,
                                      struct sss_domain_info *domain,
                                      struct ldb_result *res)
{
    errno_t ret;
    char *default_domain;
    bool fqnames_default = false;

    ret = confdb_get_string(cdb, mem_ctx, CONFDB_MONITOR_CONF_ENTRY,
                            CONFDB_MONITOR_DEFAULT_DOMAIN, NULL,
                            &default_domain);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Cannot get the default domain [%d]: %s\n",
               ret, strerror(ret));
        goto done;
    }

    /* Determine if user/group names will be Fully Qualified
     * in NSS interfaces */
    if (default_domain != NULL) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "Default domain suffix set. Changing default for "
              "use_fully_qualified_names to True.\n");
        fqnames_default = true;
    }

    ret = get_entry_as_bool(res->msgs[0], &domain->fqnames,
                            CONFDB_DOMAIN_FQ, fqnames_default);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Invalid value for %s\n",
              CONFDB_DOMAIN_FQ);
        goto done;
    }

    if (default_domain != NULL
            && domain->fqnames == false
            ) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid configuration detected (default_domain_suffix is used "
              "while use_fully_qualified_names was set to false).\n");
        ret = ERR_INVALID_CONFIG;
        goto done;
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_timeouts(struct confdb_ctx *cdb,
                                           struct sss_domain_info *domain,
                                           struct ldb_result *res)
{
    errno_t ret;
    uint32_t entry_cache_timeout;
    int memcache_timeout;

    ret = confdb_get_int(cdb,
                         CONFDB_NSS_CONF_ENTRY,
                         CONFDB_MEMCACHE_TIMEOUT,
                         300, &memcache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Unable to get memory cache entry timeout [%s].\n",
              CONFDB_MEMCACHE_TIMEOUT);
        goto done;
    }

    /* Get the global entry cache timeout setting */
    ret = get_entry_as_uint32(res->msgs[0], &entry_cache_timeout,
                              CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT, 5400);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the user cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->user_timeout,
                              CONFDB_DOMAIN_USER_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_USER_CACHE_TIMEOUT);
        goto done;
    }

    if (domain->user_timeout < memcache_timeout) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "%s is less than %s. User records will not be updated before "
              "memory cache entry expires.\n",
              CONFDB_DOMAIN_USER_CACHE_TIMEOUT, CONFDB_MEMCACHE_TIMEOUT);
    }

    /* Override the group cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->group_timeout,
                              CONFDB_DOMAIN_GROUP_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_GROUP_CACHE_TIMEOUT);
        goto done;
    }

    if (domain->group_timeout < memcache_timeout) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "%s is less than %s. Group records will not be updated before "
              "memory cache entry expires.\n",
              CONFDB_DOMAIN_GROUP_CACHE_TIMEOUT, CONFDB_MEMCACHE_TIMEOUT);
    }

    /* Override the netgroup cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->netgroup_timeout,
                              CONFDB_DOMAIN_NETGROUP_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_NETGROUP_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the service cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->service_timeout,
                              CONFDB_DOMAIN_SERVICE_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_SERVICE_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the autofs cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->autofsmap_timeout,
                              CONFDB_DOMAIN_AUTOFS_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_AUTOFS_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the sudo cache timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->sudo_timeout,
                              CONFDB_DOMAIN_SUDO_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_SUDO_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the ssh known hosts timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->ssh_host_timeout,
                              CONFDB_DOMAIN_SSH_HOST_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_SSH_HOST_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the computer timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->computer_timeout,
                              CONFDB_DOMAIN_COMPUTER_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_COMPUTER_CACHE_TIMEOUT);
        goto done;
    }

    /* Override the resolver timeout, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->resolver_timeout,
                              CONFDB_DOMAIN_RESOLVER_CACHE_TIMEOUT,
                              entry_cache_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_RESOLVER_CACHE_TIMEOUT);
        goto done;
    }

    /* Set refresh_expired_interval, if specified */
    ret = get_entry_as_uint32(res->msgs[0], &domain->refresh_expired_interval,
                              CONFDB_DOMAIN_REFRESH_EXPIRED_INTERVAL,
                              0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_REFRESH_EXPIRED_INTERVAL);
        goto done;
    }

    /* Set refresh_expired_interval_offset, if specified */
    ret = get_entry_as_uint32(res->msgs[0],
                              &domain->refresh_expired_interval_offset,
                              CONFDB_DOMAIN_REFRESH_EXPIRED_INTERVAL_OFFSET,
                              0);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n",
              CONFDB_DOMAIN_REFRESH_EXPIRED_INTERVAL_OFFSET);
        goto done;
    }

    /* detect and fix misconfiguration */
    if (domain->refresh_expired_interval + domain->refresh_expired_interval_offset > entry_cache_timeout) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "refresh_expired_interval (%d) + "
              "refresh_expired_interval_offset (%d) cannot be greater than "
              "entry_cache_timeout (%u)\n",
              domain->refresh_expired_interval,
              domain->refresh_expired_interval_offset, entry_cache_timeout);

        domain->refresh_expired_interval = 0.70 * entry_cache_timeout;
        domain->refresh_expired_interval_offset =
                0.10 * domain->refresh_expired_interval;

        DEBUG(SSSDBG_CONF_SETTINGS,
              "refresh_expired_interval is being set to recommended value "
              "entry_cache_timeout * 0.70 (%u).\n",
              domain->refresh_expired_interval);
        DEBUG(SSSDBG_CONF_SETTINGS,
              "refresh_expired_interval_offset is being set to recommended value "
              "refresh_expired_interval * 0.10 (%u).\n",
              domain->refresh_expired_interval_offset);
    }

    ret = init_cached_auth_timeout(cdb, res->msgs[0],
                                   &domain->cached_auth_timeout);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "init_cached_auth_timeout failed: %s:[%d].\n",
              sss_strerror(ret), ret);
        goto done;
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_user_info(struct sss_domain_info *domain,
                                            struct ldb_result *res)
{
    errno_t ret;
    const char *tmp;

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_NSS_OVERRIDE_HOMEDIR, NULL);
    if (tmp != NULL) {
        domain->override_homedir = talloc_strdup(domain, tmp);
        if (!domain->override_homedir) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_NSS_FALLBACK_HOMEDIR, NULL);
    if (tmp != NULL) {
        domain->fallback_homedir = talloc_strdup(domain, tmp);
        if (!domain->fallback_homedir) {
            ret = ENOMEM;
            goto done;
        }
    } else {
        if (strcasecmp(domain->provider, "ad") == 0) {
            /* ad provider default */
            domain->fallback_homedir = talloc_strdup(domain, "/home/%d/%u");
            if (!domain->fallback_homedir) {
                ret = ENOMEM;
                goto done;
            }
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_DOMAIN_SUBDOMAIN_HOMEDIR,
                                      CONFDB_DOMAIN_DEFAULT_SUBDOMAIN_HOMEDIR);
    if (tmp != NULL) {
        domain->subdomain_homedir = talloc_strdup(domain, tmp);
        if (!domain->subdomain_homedir) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_NSS_HOMEDIR_SUBSTRING, NULL);
    if (tmp != NULL) {
        domain->homedir_substr = talloc_strdup(domain, tmp);
        if (domain->homedir_substr == NULL) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_NSS_OVERRIDE_SHELL, NULL);
    if (tmp != NULL) {
        domain->override_shell = talloc_strdup(domain, tmp);
        if (!domain->override_shell) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_NSS_DEFAULT_SHELL, NULL);
    if (tmp != NULL) {
        domain->default_shell = talloc_strdup(domain, tmp);
        if (!domain->default_shell) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_NSS_PWFIELD, NULL);
    if (tmp != NULL) {
        domain->pwfield = talloc_strdup(domain, tmp);
        if (!domain->pwfield) {
            ret = ENOMEM;
            goto done;
        }
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_case(struct sss_domain_info *domain,
                                       struct ldb_result *res)
{
    errno_t ret;
    const char *tmp;

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_DOMAIN_CASE_SENSITIVE, NULL);
    if (tmp != NULL) {
        if (strcasecmp(tmp, "true") == 0) {
            domain->case_sensitive = true;
            domain->case_preserve = true;
        } else if (strcasecmp(tmp, "false") == 0) {
            domain->case_sensitive = false;
            domain->case_preserve = false;
        } else if (strcasecmp(tmp, "preserving") == 0) {
            domain->case_sensitive = false;
            domain->case_preserve = true;
        } else {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Invalid value for %s\n", CONFDB_DOMAIN_CASE_SENSITIVE);
            ret = EINVAL;
            goto done;
        }
    } else {
        /* default */
        if (strcasecmp(domain->provider, "ad") == 0) {
            domain->case_sensitive = false;
            domain->case_preserve = false;
        } else {
            domain->case_sensitive = true;
            domain->case_preserve = true;
        }
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_subdomains(struct sss_domain_info *domain,
                                             struct ldb_result *res)
{
    errno_t ret;
    const char *tmp;

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_DOMAIN_SUBDOMAIN_INHERIT,
                                      NULL);
    if (tmp != NULL) {
        ret = split_on_separator(domain, tmp, ',', true, true,
                                 &domain->sd_inherit, NULL);
        if (ret != 0) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Cannot parse %s\n", CONFDB_DOMAIN_SUBDOMAIN_INHERIT);
            goto done;
        }
    }

    domain->type = DOM_TYPE_POSIX;
    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_DOMAIN_TYPE,
                                      CONFDB_DOMAIN_TYPE_POSIX);
    if (tmp != NULL) {
        if (strcasecmp(tmp, CONFDB_DOMAIN_TYPE_POSIX) == 0) {
            domain->type = DOM_TYPE_POSIX;
        } else if (strcasecmp(tmp, CONFDB_DOMAIN_TYPE_APP) == 0) {
            domain->type = DOM_TYPE_APPLICATION;
        } else {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Invalid value %s for [%s]\n", tmp, CONFDB_DOMAIN_TYPE);
            ret = EINVAL;
            goto done;
        }
    }

    ret = get_entry_as_uint32(res->msgs[0], &domain->subdomain_refresh_interval,
                              CONFDB_DOMAIN_SUBDOMAIN_REFRESH,
                              CONFDB_DOMAIN_SUBDOMAIN_REFRESH_DEFAULT_VALUE);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n", CONFDB_DOMAIN_SUBDOMAIN_REFRESH);
        goto done;
    } else if (domain->subdomain_refresh_interval == 0) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "Invalid value for [%s]. Setting up the default value: %d\n",
              CONFDB_DOMAIN_SUBDOMAIN_REFRESH,
              CONFDB_DOMAIN_SUBDOMAIN_REFRESH_DEFAULT_VALUE);

        domain->subdomain_refresh_interval =
            CONFDB_DOMAIN_SUBDOMAIN_REFRESH_DEFAULT_VALUE;
    }

    ret = get_entry_as_uint32(res->msgs[0],
                              &domain->subdomain_refresh_interval_offset,
                              CONFDB_DOMAIN_SUBDOMAIN_REFRESH_OFFSET,
                              CONFDB_DOMAIN_SUBDOMAIN_REFRESH_OFFSET_DEFAULT_VALUE);
    if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Invalid value for [%s]\n", CONFDB_DOMAIN_SUBDOMAIN_REFRESH_OFFSET);
        goto done;
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_gssapi(struct sss_domain_info *domain,
                                         struct ldb_result *res)
{
    errno_t ret;
    const char *tmp;

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_PAM_GSSAPI_SERVICES,
                                      NULL);
    if (tmp != NULL) {
        ret = split_on_separator(domain, tmp, ',', true, true,
                                 &domain->gssapi_services, NULL);
        if (ret != 0) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Cannot parse %s\n", CONFDB_PAM_GSSAPI_SERVICES);
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0], CONFDB_PAM_GSSAPI_CHECK_UPN,
                                      NULL);
    if (tmp != NULL) {
        domain->gssapi_check_upn = talloc_strdup(domain, tmp);
        if (domain->gssapi_check_upn == NULL) {
            ret = ENOMEM;
            goto done;
        }
    }

    tmp = ldb_msg_find_attr_as_string(res->msgs[0],
                                      CONFDB_PAM_GSSAPI_INDICATORS_MAP,
                                      NULL);
    if (tmp != NULL && tmp[0] != '\0') {
        ret = split_on_separator(domain, tmp, ',', true, true,
                                 &domain->gssapi_indicators_map, NULL);
        if (ret != 0) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Cannot parse %s\n", CONFDB_PAM_GSSAPI_INDICATORS_MAP);
            goto done;
        }
    }

    ret = EOK;

done:
    return ret;
}

static errno_t confdb_init_domain_pwd_expire(struct confdb_ctx *cdb,
                                             struct sss_domain_info *domain,
                                             struct ldb_result *res)
{
    int val;
    errno_t ret;

    /* Set the PAM warning time, if specified. If not specified, pass on
     * the "not set" value of "-1" which means "use provider default". The
     * value 0 means "always display the warning if server sends one" */
    domain->pwd_expiration_warning = -1;

    val = ldb_msg_find_attr_as_int(res->msgs[0],
                                   CONFDB_DOMAIN_PWD_EXPIRATION_WARNING,
                                   -1);
    if (val == -1) {
        ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
                             CONFDB_PAM_PWD_EXPIRATION_WARNING,
                             -1, &val);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Failed to read PAM expiration warning, not fatal.\n");
            val = -1;
        }
    }

    DEBUG(SSSDBG_TRACE_LIBS, "pwd_expiration_warning is %d\n", val);
    if (val >= 0) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "Setting domain password expiration warning to %d days\n", val);
        /* The value is in days, transform it to seconds */
        domain->pwd_expiration_warning = val * 24 * 3600;
    }

    return EOK;
}

static int confdb_get_domain_internal(struct confdb_ctx *cdb,
                                      TALLOC_CTX *mem_ctx,
                                      const char *name,
                                      struct sss_domain_info **_domain)
{
    struct sss_domain_info *domain;
    struct ldb_result *res;
    TALLOC_CTX *tmp_ctx;
    errno_t ret;

    tmp_ctx = talloc_new(mem_ctx);
    if (!tmp_ctx) return ENOMEM;

    ret = confdb_get_domain_section(tmp_ctx, cdb, CONFDB_DOMAIN_BASEDN,
                                    name, &res);
    if (ret == ENOENT) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Unknown domain [%s]\n", name);
        goto done;
    } else if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "Error %d: %s while retrieving %s\n",
              ret, sss_strerror(ret), name);
        goto done;
    }

    domain = talloc_zero(mem_ctx, struct sss_domain_info);
    if (!domain) {
        ret = ENOMEM;
        goto done;
    }

    ret = confdb_init_domain(domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Determine if this domain can be enumerated */
    ret = confdb_init_domain_provider_and_enum(domain, res);
    if (ret != EOK) {
        goto done;
    }

    ret = confdb_init_domain_fqn(cdb, mem_ctx, domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get all the timeouts */
    ret = confdb_init_domain_timeouts(cdb, domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get password expiration information */
    ret = confdb_init_domain_pwd_expire(cdb, domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get the behavior about the homedir */
    ret = confdb_init_domain_user_info(domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get the case sensitivity */
    ret = confdb_init_domain_case(domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get the subdomains information */
    ret = confdb_init_domain_subdomains(domain, res);
    if (ret != EOK) {
        goto done;
    }

    /* Get the GSSAPI information */
    ret = confdb_init_domain_gssapi(domain, res);
    if (ret != EOK) {
        goto done;
    }


    *_domain = domain;
    ret = EOK;
done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_get_domains(struct confdb_ctx *cdb,
                       struct sss_domain_info **domains)
{
    TALLOC_CTX *tmp_ctx;
    struct sss_domain_info *domain = NULL;
    char **domlist;
    int ret, i;

    if (cdb->doms) {
        *domains = cdb->doms;
        return EOK;
    }

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) return ENOMEM;

    ret = confdb_get_enabled_domain_list(cdb, tmp_ctx, &domlist);
    if (ret == ENOENT) {
        DEBUG(SSSDBG_CRIT_FAILURE, "No domains configured, fatal error!\n");
        ret = ERR_NO_DOMAIN_ENABLED;
        goto done;
    } else if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Fatal error retrieving domains list!\n");
        goto done;
    }

    for (i = 0; domlist[i]; i++) {
        /* check if domain name is really unique */
        DLIST_FOR_EACH(domain, cdb->doms) {
            if (strcasecmp(domain->name, domlist[i]) == 0) {
                DEBUG(SSSDBG_FATAL_FAILURE,
                      SAME_DOMAINS_ERROR_MSG, domlist[i], domain->name);
                sss_log(SSS_LOG_CRIT,
                        SAME_DOMAINS_ERROR_MSG, domlist[i], domain->name);

                ret = EINVAL;
                goto done;
            }
        }

        domain = NULL;
       ret = confdb_get_domain_internal(cdb, cdb, domlist[i], &domain);
        if (ret) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  RETRIEVE_DOMAIN_ERROR_MSG,
                  ret, sss_strerror(ret), domlist[i]);
            sss_log(SSS_LOG_CRIT,
                    RETRIEVE_DOMAIN_ERROR_MSG,
                    ret, sss_strerror(ret), domlist[i]);

            continue;
        }

        DLIST_ADD_END(cdb->doms, domain, struct sss_domain_info *);
    }

    if (cdb->doms == NULL) {
        DEBUG(SSSDBG_FATAL_FAILURE,
              "No properly configured domains, fatal error!\n");
        ret = ENOENT;
        goto done;
    }

    *domains = cdb->doms;
    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_get_domain(struct confdb_ctx *cdb,
                      const char *name,
                      struct sss_domain_info **_domain)
{
    struct sss_domain_info *dom, *doms;
    int ret;

    ret = confdb_get_domains(cdb, &doms);
    if (ret != EOK) {
        return ret;
    }

    for (dom = doms; dom; dom = get_next_domain(dom, 0)) {
        if (strcasecmp(dom->name, name) == 0) {
            *_domain = dom;
            return EOK;
        }
    }

    return ENOENT;
}

int confdb_list_all_domain_names(TALLOC_CTX *mem_ctx,
                                 struct confdb_ctx *cdb,
                                 char ***_names)
{
    TALLOC_CTX *tmp_ctx = NULL;
    struct ldb_dn *dn = NULL;
    struct ldb_result *res = NULL;
    static const char *attrs[] = {CONFDB_DOMAIN_ATTR, NULL};
    const char *name = NULL;
    char **names = NULL;
    int i;
    int ret;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    dn = ldb_dn_new(tmp_ctx, cdb->ldb, CONFDB_DOMAIN_BASEDN);
    if (dn == NULL) {
        ret = ENOMEM;
        goto done;
    }

    ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn, LDB_SCOPE_ONELEVEL,
                     attrs, NULL);
    if (ret != LDB_SUCCESS) {
        ret = EIO;
        goto done;
    }

    names = talloc_zero_array(tmp_ctx, char*, res->count + 1);
    if (names == NULL) {
        ret = ENOMEM;
        goto done;
    }

    for (i = 0; i < res->count; i++) {
        name = ldb_msg_find_attr_as_string(res->msgs[i], CONFDB_DOMAIN_ATTR,
                                           NULL);
        if (name == NULL) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                  "The object [%s] doesn't have a name\n",
                   ldb_dn_get_linearized(res->msgs[i]->dn));
            ret = EINVAL;
            goto done;
        }

        names[i] = talloc_strdup(names, name);
        if (names[i] == NULL) {
            ret = ENOMEM;
            goto done;
        }
    }

    *_names = talloc_steal(mem_ctx, names);

    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_get_sub_sections(TALLOC_CTX *mem_ctx,
                            struct confdb_ctx *cdb,
                            const char *section,
                            char ***sections,
                            int *num_sections)
{
    TALLOC_CTX *tmp_ctx = NULL;
    char *secdn;
    struct ldb_dn *base = NULL;
    struct ldb_result *res = NULL;
    static const char *attrs[] = {"cn", NULL};
    char **names;
    int base_comp_num;
    int num;
    int i;
    int ret;

    tmp_ctx = talloc_new(mem_ctx);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    ret = parse_section(tmp_ctx, section, &secdn, NULL);
    if (ret != EOK) {
        goto done;
    }

    base = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
    if (base == NULL) {
        ret = ENOMEM;
        goto done;
    }

    base_comp_num = ldb_dn_get_comp_num(base);

    ret = ldb_search(cdb->ldb, tmp_ctx, &res, base, LDB_SCOPE_SUBTREE,
                     attrs, NULL);
    if (ret != LDB_SUCCESS) {
        ret = EIO;
        goto done;
    }

    names = talloc_zero_array(tmp_ctx, char *, res->count + 1);
    if (names == NULL) {
        ret = ENOMEM;
        goto done;
    }

    for (num = 0, i = 0; i < res->count; i++) {
        const struct ldb_val *val;
        char *name;
        int n;
        int j;

        n = ldb_dn_get_comp_num(res->msgs[i]->dn);
        if (n == base_comp_num) continue;

        name = NULL;
        for (j = n - base_comp_num - 1; j >= 0; j--) {
            val = ldb_dn_get_component_val(res->msgs[i]->dn, j);
            if (name == NULL) {
                name = talloc_strndup(names,
                                      (const char *)val->data, val->length);
            } else {
                name = talloc_asprintf(names, "%s/%.*s", name,
                                       (int)val->length,
                                       (const char *)val->data);
            }
            if (name == NULL) {
                ret = ENOMEM;
                goto done;
            }
        }

        names[num] = name;
        if (names[num] == NULL) {
            ret = ENOMEM;
            goto done;
        }

        num++;
    }

    *sections = talloc_steal(mem_ctx, names);
    *num_sections = num;
    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

static int confdb_get_parent_domain(TALLOC_CTX *mem_ctx,
                                    const char *name,
                                    struct confdb_ctx *cdb,
                                    struct ldb_result *app_dom,
                                    struct ldb_result **_parent_dom)
{
    const char *inherit_from;

    inherit_from = ldb_msg_find_attr_as_string(app_dom->msgs[0],
                                               CONFDB_DOMAIN_INHERIT_FROM, NULL);
    if (inherit_from == NULL) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "%s does not inherit from any POSIX domain\n", name);
        *_parent_dom = NULL;
        return EOK;
    }

    return confdb_get_domain_section(mem_ctx, cdb,
                                     CONFDB_DOMAIN_BASEDN, inherit_from,
                                     _parent_dom);
}

static int confdb_add_app_domain(TALLOC_CTX *mem_ctx,
                                 struct confdb_ctx *cdb,
                                 const char *name)
{
    char *cdb_path = NULL;
    const char *val[2] = { NULL, NULL };
    int ret;

    cdb_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, name);
    if (cdb_path == NULL) {
        return ENOMEM;
    }

    val[0] = CONFDB_DOMAIN_TYPE_APP;
    ret = confdb_add_param(cdb, true, cdb_path, CONFDB_DOMAIN_TYPE, val);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add id_provider [%d]: %s\n",
              ret, sss_strerror(ret));
        return ret;
    }

    return EOK;
}

static int confdb_merge_parent_domain(const char *name,
                                      struct confdb_ctx *cdb,
                                      struct ldb_result *app_section)
{
    int ret;
    int ldb_flag;
    struct ldb_result *parent_domain = NULL;
    struct ldb_message *replace_msg = NULL;
    struct ldb_message *app_msg = NULL;
    struct ldb_dn *domain_dn;
    struct ldb_message_element *el = NULL;
    TALLOC_CTX *tmp_ctx = NULL;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
        return ENOMEM;
    }

    domain_dn = ldb_dn_new_fmt(tmp_ctx,
                               cdb->ldb,
                               "%s=%s,%s",
                               CONFDB_DOMAIN_ATTR,
                               name,
                               CONFDB_DOMAIN_BASEDN);
    if (domain_dn == NULL) {
        ret = ENOMEM;
        goto done;
    }

    /* Copy the parent domain parameters */
    ret = confdb_get_parent_domain(tmp_ctx, name, cdb,
                                   app_section, &parent_domain);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Cannot retrieve the parent domain [%d]: %s\n",
              ret, sss_strerror(ret));
        goto done;
    }

    if (parent_domain != NULL) {
        replace_msg = ldb_msg_copy(tmp_ctx, parent_domain->msgs[0]);
        if (replace_msg == NULL) {
            ret = ENOMEM;
            goto done;
        }
        replace_msg->dn = domain_dn;

        for (unsigned i = 0; i < replace_msg->num_elements; i++) {
            replace_msg->elements[i].flags = LDB_FLAG_MOD_ADD;
        }

        el = ldb_msg_find_element(replace_msg, "cn");
        if (el != NULL) {
            /* Don't add second cn */
            ldb_msg_remove_element(replace_msg, el);
        }

        ret = ldb_modify(cdb->ldb, replace_msg);
        if (ret != LDB_SUCCESS) {
            ret = sss_ldb_error_to_errno(ret);
            DEBUG(SSSDBG_OP_FAILURE,
                "Inheriting options from parent domain failed [%d]: %s\n",
                ret, sss_strerror(ret));
            goto done;
        }
    }

    /* Finally, add any app-domain specific overrides */
    app_msg = ldb_msg_new(tmp_ctx);
    if (app_msg == NULL) {
        ret = ENOMEM;
        goto done;
    }
    app_msg->dn = domain_dn;

    for (unsigned i = 0; i < app_section->msgs[0]->num_elements; i++) {
        struct ldb_message_element *app_el = &app_section->msgs[0]->elements[i];

        /* These elements will be skipped when replacing attributes in
         * a domain to avoid EEXIST errors
         */
        if (strcasecmp(app_el->name, "cn") == 0) {
            continue;
        }

        if (replace_msg != NULL) {
            el = ldb_msg_find_element(replace_msg,
                                      app_section->msgs[0]->elements[i].name);
            if (el == NULL) {
                /* Adding an element */
                ldb_flag = LDB_FLAG_MOD_ADD;
            } else {
                /* Overriding an element */
                ldb_flag = LDB_FLAG_MOD_REPLACE;
            }
        } else {
            /* If there was no domain to inherit from, just add all */
            ldb_flag = LDB_FLAG_MOD_ADD;
        }

        ret = ldb_msg_add(app_msg,
                          &app_section->msgs[0]->elements[i],
                          ldb_flag);
        if (ret != LDB_SUCCESS) {
            continue;
        }
    }

    /* We use permissive modification here because adding cn or
     * distinguishedName from the app_section to the application
     * message would throw EEXIST
     */
    ret = sss_ldb_modify_permissive(cdb->ldb, app_msg);
    if (ret != LDB_SUCCESS) {
        ret = sss_ldb_error_to_errno(ret);
        DEBUG(SSSDBG_OP_FAILURE,
              "Adding app-specific options failed [%d]: %s\n",
              ret, sss_strerror(ret));
        goto done;
    }

    DEBUG(SSSDBG_TRACE_LIBS, "Added a domain section for %s\n", name);
    ret = EOK;
done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_expand_app_domains(struct confdb_ctx *cdb)
{
    int ret;
    char **domlist;
    TALLOC_CTX *tmp_ctx;
    struct ldb_result *app_domain = NULL;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    ret = confdb_get_enabled_domain_list(cdb, tmp_ctx, &domlist);
    if (ret == ENOENT) {
        DEBUG(SSSDBG_FATAL_FAILURE, "No domains configured, fatal error!\n");
        ret = ERR_NO_DOMAIN_ENABLED;
        goto done;
    } else if (ret != EOK) {
        DEBUG(SSSDBG_FATAL_FAILURE, "Fatal error retrieving domains list!\n");
        goto done;
    }

    for (int i = 0; domlist[i]; i++) {
        ret = confdb_get_domain_section(tmp_ctx, cdb,
                                        CONFDB_APP_DOMAIN_BASEDN, domlist[i],
                                        &app_domain);
        if (ret == ENOENT) {
            DEBUG(SSSDBG_TRACE_INTERNAL,
                  "%s is not an app domain\n", domlist[i]);
            continue;
        } else if (ret != EOK) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "Error %d: %s while retrieving %s\n",
                  ret, sss_strerror(ret), domlist[i]);
            goto done;
        }

        ret = confdb_add_app_domain(tmp_ctx, cdb, domlist[i]);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "Cannot add the app domain section [%d]: %s\n",
                  ret, sss_strerror(ret));
            goto done;
        }

        ret = confdb_merge_parent_domain(domlist[i], cdb, app_domain);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "Cannot add options into the app domain section [%d]: %s\n",
                  ret, sss_strerror(ret));
            goto done;
        }
    }

    ret = EOK;
done:
    talloc_free(tmp_ctx);
    return ret;
}

static errno_t certmap_local_check(struct ldb_message *msg)
{
    const char *rule_name;
    const char *tmp_str;
    int ret;
    const char *sep;

    rule_name = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_NAME, NULL);
    if (rule_name == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Certificate mapping rule [%s] has no name.",
                                   ldb_dn_get_linearized(msg->dn));
        return EINVAL;
    }

    tmp_str = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_DOMAINS, NULL);
    if (tmp_str != NULL) {
        DEBUG(SSSDBG_CONF_SETTINGS,
              "Option [%s] is ignored for local certmap rules.\n",
              CONFDB_CERTMAP_DOMAINS);
    }

    tmp_str = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_MAPRULE, NULL);
    if (tmp_str != NULL) {
        /* The body of the rule must be enclosed with '(' and ')' but the rule
         * may start with a prefix which ends with ':'. */
        sep = strchr(tmp_str, '(');
        if (sep != NULL) {
            if (sep != tmp_str && sep[-1] != ':') {
                DEBUG(SSSDBG_CONF_SETTINGS, "Prefix before the first opening "
                                            "'(' must end with a ':'.\n");
                return EINVAL;
            }
        } else {
            DEBUG(SSSDBG_CONF_SETTINGS,
                  "Missing opening braces '('.\n");
            return EINVAL;
        }
        /* We already know that there is a '(' */
        if (tmp_str[strlen(tmp_str) - 1] != ')') {
            DEBUG(SSSDBG_CONF_SETTINGS,
                  "Mapping rule must be in braces (...).\n");
            return EINVAL;
        }
        DEBUG(SSSDBG_TRACE_ALL, "Using [%s] mapping rule of [%s].\n",
                                tmp_str, ldb_dn_get_linearized(msg->dn));
        return EOK;
    }

    tmp_str = talloc_asprintf(msg, "(%s)", rule_name);
    if (tmp_str == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
        return ENOMEM;
    }
    ret = ldb_msg_add_string(msg, CONFDB_CERTMAP_MAPRULE, tmp_str);
    if (ret != LDB_SUCCESS) {
        talloc_free(discard_const(tmp_str));
        DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_string failed.\n");
        return EIO;
    }

    DEBUG(SSSDBG_TRACE_ALL, "Using [%s] as mapping rule for [%s].\n",
                            tmp_str, ldb_dn_get_linearized(msg->dn));

    return EOK;
}

static errno_t confdb_get_all_certmaps(TALLOC_CTX *mem_ctx,
                                       struct confdb_ctx *cdb,
                                       struct sss_domain_info *dom,
                                       bool certmaps_for_local_users,
                                       struct certmap_info ***_certmap_list)
{
    TALLOC_CTX *tmp_ctx = NULL;
    struct ldb_dn *dn = NULL;
    struct ldb_result *res = NULL;
    /* The attribute order is important, because it is used in
     * sysdb_ldb_msg_attr_to_certmap_info and must match
     * enum certmap_info_member. */
    static const char *attrs[] = { CONFDB_CERTMAP_NAME,
                                   CONFDB_CERTMAP_MAPRULE,
                                   CONFDB_CERTMAP_MATCHRULE,
                                   CONFDB_CERTMAP_PRIORITY,
                                   CONFDB_CERTMAP_DOMAINS,
                                   NULL};
    struct certmap_info **certmap_list = NULL;
    size_t c;
    int ret;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    dn = ldb_dn_new_fmt(tmp_ctx, cdb->ldb, "cn=%s,%s", dom->name,
                                                       CONFDB_CERTMAP_BASEDN);
    if (dn == NULL) {
        ret = ENOMEM;
        goto done;
    }

    ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn, LDB_SCOPE_ONELEVEL,
                     attrs, NULL);
    if (ret != LDB_SUCCESS) {
        ret = EIO;
        goto done;
    }

    certmap_list = talloc_zero_array(tmp_ctx, struct certmap_info *,
                                     res->count + 1);
    if (certmap_list == NULL) {
        ret = ENOMEM;
        goto done;
    }

    for (c = 0; c < res->count; c++) {
        if (certmaps_for_local_users) {
            ret = certmap_local_check(res->msgs[c]);
            if (ret != EOK) {
                DEBUG(SSSDBG_CONF_SETTINGS,
                      "Invalid certificate mapping [%s] for local user, "
                      "ignored.\n", ldb_dn_get_linearized(res->msgs[c]->dn));
                continue;
            }
        }
        ret = sysdb_ldb_msg_attr_to_certmap_info(certmap_list, res->msgs[c],
                                                 attrs, &certmap_list[c]);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "sysdb_ldb_msg_attr_to_certmap_info failed.\n");
            goto done;
        }
    }

    *_certmap_list = talloc_steal(mem_ctx, certmap_list);

    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

int confdb_certmap_to_sysdb(struct confdb_ctx *cdb,
                            struct sss_domain_info *dom,
                            bool certmaps_for_local_users)
{
    int ret;
    TALLOC_CTX *tmp_ctx;
    struct certmap_info **certmap_list;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
        return ENOMEM;
    }

    ret = confdb_get_all_certmaps(tmp_ctx, cdb, dom, certmaps_for_local_users,
                                  &certmap_list);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "confdb_get_all_certmaps failed.\n");
        goto done;
    }

    ret = sysdb_update_certmap(dom->sysdb, certmap_list, false /* TODO */);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_certmap failed.\n");
        goto done;
    }

    ret = EOK;

done:
    talloc_free(tmp_ctx);

    return ret;
}

int confdb_get_enabled_domain_list(struct confdb_ctx *cdb,
                                   TALLOC_CTX *ctx, char ***_result)
{
    int ret;
    char **domlist = NULL;
    char **all_domains = NULL;
    bool enabled = false;

    TALLOC_CTX *tmp_ctx = NULL;

    tmp_ctx = talloc_new(NULL);
    ret = confdb_get_string_as_list(cdb, tmp_ctx,
                                    CONFDB_MONITOR_CONF_ENTRY,
                                    CONFDB_MONITOR_ACTIVE_DOMAINS,
                                    &domlist);
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Failed to get [%s] from [%s], error [%d] (%s)\n",
              CONFDB_MONITOR_ACTIVE_DOMAINS, "sssd",
              ret, strerror(ret));
        goto done;
    }

    ret = confdb_list_all_domain_names(tmp_ctx, cdb, &all_domains);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Failed retrieving all domain name "
              "list, error [%d], description '%s'\n",
              ret, strerror(ret));
        goto done;
    }

    for (int idx = 0; all_domains[idx]; idx++) {
        ret = confdb_get_domain_enabled(cdb, all_domains[idx], &enabled);
        if (ret != EOK && ret != ENOENT) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "Failed retrieving 'enabled' attribute from '%s' domain; "
                  "error [%d], description '%s'\n",
                  all_domains[idx],
                  ret, strerror(ret));
            goto done;
        }

        if (ret == ENOENT) continue;

        if (enabled && !string_in_list(all_domains[idx], domlist, false)) {
            ret = add_string_to_list(tmp_ctx, all_domains[idx], &domlist);
            if (ret != EOK) {
                DEBUG(SSSDBG_OP_FAILURE,
                    "Failed adding '%s' domain to domain list; "
                    "error [%d], description '%s'\n",
                    all_domains[idx],
                    ret, strerror(ret));
                goto done;
            }
        }

        if (!enabled && string_in_list(all_domains[idx], domlist, false)) {
            ret = del_string_from_list(all_domains[idx], &domlist, false);
            if (ret != EOK) {
                DEBUG(SSSDBG_OP_FAILURE,
                    "Failed deleting '%s' domain from domain list; "
                    "error [%d], description '%s'\n",
                    all_domains[idx],
                    ret, strerror(ret));
                goto done;
            }
        }
    }

    if (domlist == NULL || domlist[0] == NULL) {
        ret = ENOENT;
        goto done;
    }

    ret = EOK;
    talloc_steal(ctx, domlist);
    *_result = domlist;

done:
    talloc_free(tmp_ctx);
    return ret;
}


/**
 * Retrieve the enabled attribute for a specific domain.
 * @param cdb The database configuration context.
 * @param domain The domain name.
 * @param enabled The output variable; it can not be NULL; if the
 * domain is explicitely enabled, *enabled is equal to 1; if the
 * domain is explicitely disabled, *enabled is equal to 0.
 * @param Return EOK if the operation happened properly and *enabled
 * contain the value of the attribute; if no entry found for enabled
 * attribute it returns ENOENT, else an error code.
 */
static int confdb_get_domain_enabled(struct confdb_ctx *cdb,
                                     const char *domain, bool *_enabled)
{
    TALLOC_CTX *tmp_ctx = NULL;
    char *section = NULL;
    char **values = NULL;
    int ret = EINVAL;

    section = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, domain);
    values = NULL;
    ret = confdb_get_param(cdb, tmp_ctx, section, CONFDB_DOMAIN_ENABLED, &values);
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Failed retrieving '%s' attribute in '%s' section; "
              "error [%d], description '%s'\n",
              CONFDB_DOMAIN_ENABLED, section,
              ret, strerror(ret));
        goto done;
    }

    /* Check return and output value */
    if (ret == ENOENT || !values || !values[0]) {
        ret = ENOENT;
        goto done;
    }
    if (values[1]) {
        /* More than one value it's an invalid configuration file */
        ret = EINVAL;
        goto done;
    }
    if (0 == strcasecmp(values[0], "true")) {
        ret = EOK;
        *_enabled = true;
        goto done;
    }
    if (0 == strcasecmp(values[0], "false")) {
        ret = EOK;
        *_enabled = false;
        goto done;
    }
    /* Failed to parse value */
    ret = EINVAL;
done:
    talloc_free(tmp_ctx);
    return ret;
}
