/*
SSSD
Configuration Database
Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
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 <sys/stat.h>
#include <unistd.h>
#include "util/util.h"
#include "db/sysdb.h"
#include "confdb.h"
#include "confdb_private.h"
#include "confdb_setup.h"
#include "util/sss_ini.h"
#include "tools/tools_util.h"
static int confdb_test(struct confdb_ctx *cdb)
{
char **values;
int ret;
ret = confdb_get_param(cdb, cdb,
"config",
"version",
&values);
if (ret != EOK) {
return ret;
}
if (values[0] == NULL) {
/* empty database, will need to init */
talloc_free(values);
return ENOENT;
}
if (values[1] != NULL) {
/* more than 1 value ?? */
talloc_free(values);
return EIO;
}
if (strcmp(values[0], CONFDB_VERSION) != 0) {
/* Existing version does not match executable version */
DEBUG(SSSDBG_CRIT_FAILURE, "Upgrading confdb version from %s to %s\n",
values[0], CONFDB_VERSION);
/* This is recoverable, since we purge the confdb file
* when we re-initialize it.
*/
talloc_free(values);
return ENOENT;
}
talloc_free(values);
return EOK;
}
static int confdb_purge(struct confdb_ctx *cdb)
{
int ret;
unsigned int i;
TALLOC_CTX *tmp_ctx;
struct ldb_result *res;
struct ldb_dn *dn;
const char *attrs[] = { "dn", NULL };
tmp_ctx = talloc_new(NULL);
dn = ldb_dn_new(tmp_ctx, cdb->ldb, "cn=config");
/* Get the list of all DNs */
ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn,
LDB_SCOPE_SUBTREE, attrs, NULL);
if (ret != LDB_SUCCESS) {
ret = sysdb_error_to_errno(ret);
goto done;
}
for(i=0; i<res->count; i++) {
/* Delete this DN */
ret = ldb_delete(cdb->ldb, res->msgs[i]->dn);
if (ret != LDB_SUCCESS) {
ret = sysdb_error_to_errno(ret);
goto done;
}
}
done:
talloc_free(tmp_ctx);
return ret;
}
static int confdb_create_base(struct confdb_ctx *cdb)
{
int ret;
struct ldb_ldif *ldif;
const char *base_ldif = CONFDB_BASE_LDIF;
while ((ldif = ldb_ldif_read_string(cdb->ldb, &base_ldif))) {
ret = ldb_add(cdb->ldb, ldif->msg);
if (ret != LDB_SUCCESS) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Failed to initialize DB (%d,[%s]), aborting!\n",
ret, ldb_errstring(cdb->ldb));
return EIO;
}
ldb_ldif_read_free(cdb->ldb, ldif);
}
return EOK;
}
static int confdb_init_db(const char *config_file, const char *config_dir,
struct confdb_ctx *cdb)
{
TALLOC_CTX *tmp_ctx;
int ret;
int sret = EOK;
int version;
char timestr[21];
bool in_transaction = false;
const char *config_ldif;
const char *vals[2] = { timestr, NULL };
struct ldb_ldif *ldif;
struct sss_ini_initdata *init_data;
tmp_ctx = talloc_new(cdb);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory.\n");
return ENOMEM;
}
init_data = sss_ini_initdata_init(tmp_ctx);
if (!init_data) {
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory.\n");
ret = ENOMEM;
goto done;
}
/* Open config file */
ret = sss_ini_config_file_open(init_data, config_file);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_FUNC,
"sss_ini_config_file_open failed: %s [%d]\n", strerror(ret),
ret);
if (ret != ENOENT) {
/* Anything other than ENOENT is unrecoverable */
goto done;
} else {
/* Copy the default configuration file to the standard location
* and then retry
*/
ret = copy_file_secure(SSSD_DEFAULT_CONFIG_FILE,
SSSD_CONFIG_FILE,
0600,
getuid(),
getgid(),
false);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not copy default configuration: %s",
sss_strerror(ret));
/* sss specific error denoting missing configuration file */
ret = ERR_MISSING_CONF;
goto done;
}
/* Try again */
ret = sss_ini_config_file_open(init_data, config_file);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_FUNC,
"sss_ini_config_file_open(default) failed: %s [%d]\n",
strerror(ret), ret);
if (ret == ENOENT) {
/* sss specific error denoting missing configuration file */
ret = ERR_MISSING_CONF;
}
goto done;
}
}
}
ret = sss_ini_config_access_check(init_data);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Permission check on config file failed.\n");
ret = EPERM;
goto done;
}
ret = sss_ini_get_stat(init_data);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Status check on config file failed.\n");
ret = errno;
goto done;
}
errno = 0;
ret = sss_ini_get_mtime(init_data, sizeof(timestr), timestr);
if (ret <= 0 || ret >= (int)sizeof(timestr)) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Failed to convert time_t to string ??\n");
ret = errno ? errno : EFAULT;
}
/* FIXME: Determine if the conf file or any snippet has changed
* since we last updated the confdb or if some snippet was
* added or removed.
*/
ret = sss_ini_get_config(init_data, config_file, config_dir);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Failed to load configuration\n");
goto done;
}
ret = sss_ini_call_validators(init_data,
SSSDDATADIR"/cfg_rules.ini");
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to call validators\n");
/* This is not fatal, continue */
}
/* Make sure that the config file version matches the confdb version */
ret = sss_ini_get_cfgobj(init_data, "sssd", "config_file_version");
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Internal error determining config_file_version\n");
goto done;
}
ret = sss_ini_check_config_obj(init_data);
if (ret != EOK) {
/* No known version. Use default. */
DEBUG(SSSDBG_CONF_SETTINGS,
"Value of config_file_version option not found. "
"Assumed to be version %d.\n", CONFDB_DEFAULT_CFG_FILE_VER);
} else {
version = sss_ini_get_int_config_value(init_data,
CONFDB_DEFAULT_CFG_FILE_VER,
-1, &ret);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Config file version could not be determined\n");
goto done;
} else if (version < CONFDB_VERSION_INT) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Config file is an old version. "
"Please run configuration upgrade script.\n");
ret = EINVAL;
goto done;
} else if (version > CONFDB_VERSION_INT) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Config file version is newer than confdb\n");
ret = EINVAL;
goto done;
}
}
/* Set up a transaction to replace the configuration */
ret = ldb_transaction_start(cdb->ldb);
if (ret != LDB_SUCCESS) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Failed to start a transaction for "
"updating the configuration\n");
ret = sysdb_error_to_errno(ret);
goto done;
}
in_transaction = true;
/* Purge existing database */
ret = confdb_purge(cdb);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not purge existing configuration\n");
goto done;
}
ret = sss_confdb_create_ldif(tmp_ctx, init_data, &config_ldif);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Could not create LDIF for confdb\n");
goto done;
}
DEBUG(SSSDBG_TRACE_LIBS, "LDIF file to import: \n%s\n", config_ldif);
while ((ldif = ldb_ldif_read_string(cdb->ldb, &config_ldif))) {
ret = ldb_add(cdb->ldb, ldif->msg);
if (ret != LDB_SUCCESS) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Failed to initialize DB (%d,[%s]), aborting!\n",
ret, ldb_errstring(cdb->ldb));
ret = EIO;
goto done;
}
ldb_ldif_read_free(cdb->ldb, ldif);
}
/* now store the lastUpdate time so that we do not re-init if nothing
* changed on restart */
ret = confdb_add_param(cdb, true, "config", "lastUpdate", vals);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Failed to set last update time on db!\n");
goto done;
}
ret = ldb_transaction_commit(cdb->ldb);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
goto done;
}
in_transaction = false;
ret = EOK;
done:
if (in_transaction) {
sret = ldb_transaction_cancel(cdb->ldb);
if (sret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n");
}
}
sss_ini_config_destroy(init_data);
sss_ini_close_file(init_data);
talloc_zfree(tmp_ctx);
return ret;
}
errno_t confdb_setup(TALLOC_CTX *mem_ctx,
const char *cdb_file,
const char *config_file,
const char *config_dir,
struct confdb_ctx **_cdb)
{
TALLOC_CTX *tmp_ctx;
struct confdb_ctx *cdb;
errno_t ret;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
return ENOMEM;
}
ret = confdb_init(tmp_ctx, &cdb, cdb_file);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "The confdb initialization failed "
"[%d]: %s\n", ret, sss_strerror(ret));
goto done;
}
/* Initialize the CDB from the configuration file */
ret = confdb_test(cdb);
if (ret == ENOENT) {
/* First-time setup */
/* Purge any existing confdb in case an old
* misconfiguration gets in the way
*/
talloc_zfree(cdb);
ret = unlink(cdb_file);
if (ret != EOK && errno != ENOENT) {
ret = errno;
DEBUG(SSSDBG_MINOR_FAILURE,
"Purging existing confdb failed: %d [%s].\n",
ret, sss_strerror(ret));
goto done;
}
ret = confdb_init(tmp_ctx, &cdb, cdb_file);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "The confdb initialization failed "
"[%d]: %s\n", ret, sss_strerror(ret));
}
/* Load special entries */
ret = confdb_create_base(cdb);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Unable to load special entries into confdb\n");
goto done;
}
} else if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Fatal error initializing confdb\n");
goto done;
}
ret = confdb_init_db(config_file, config_dir, cdb);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "ConfDB initialization has failed "
"[%d]: %s\n", ret, sss_strerror(ret));
goto done;
}
*_cdb = talloc_steal(mem_ctx, cdb);
ret = EOK;
done:
talloc_free(tmp_ctx);
return ret;
}