diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/files | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/providers/files')
-rw-r--r-- | src/providers/files/files_auth.c | 69 | ||||
-rw-r--r-- | src/providers/files/files_certmap.c | 183 | ||||
-rw-r--r-- | src/providers/files/files_id.c | 222 | ||||
-rw-r--r-- | src/providers/files/files_init.c | 261 | ||||
-rw-r--r-- | src/providers/files/files_ops.c | 1484 | ||||
-rw-r--r-- | src/providers/files/files_private.h | 100 |
6 files changed, 2319 insertions, 0 deletions
diff --git a/src/providers/files/files_auth.c b/src/providers/files/files_auth.c new file mode 100644 index 0000000..b71de69 --- /dev/null +++ b/src/providers/files/files_auth.c @@ -0,0 +1,69 @@ +/* + SSSD + + files_auth.c - PAM operations on the files provider + + Copyright (C) 2018 Red Hat + + 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 <security/pam_modules.h> + +#include "providers/data_provider/dp.h" +#include "providers/data_provider.h" +#include "providers/files/files_private.h" +#include "util/cert.h" + +struct files_auth_ctx { + struct pam_data *pd; +}; + +struct tevent_req * +files_auth_handler_send(TALLOC_CTX *mem_ctx, + void *unused, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct files_auth_ctx *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct files_auth_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + return req; +} + +errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct files_auth_ctx *state = NULL; + + state = tevent_req_data(req, struct files_auth_ctx); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/files/files_certmap.c b/src/providers/files/files_certmap.c new file mode 100644 index 0000000..665279f --- /dev/null +++ b/src/providers/files/files_certmap.c @@ -0,0 +1,183 @@ +/* + SSSD + + files_init.c - Initialization of the files provider + + Copyright (C) 2018 Red Hat + + 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 "providers/files/files_private.h" +#include "util/util.h" +#include "util/cert.h" +#include "lib/certmap/sss_certmap.h" + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, format, ap); + va_end(ap); +} + +errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + size_t c; + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(mem_ctx, ext_debug, NULL, &id_ctx->sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(id_ctx->sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} + +errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx, + struct dp_id_data *data) +{ + errno_t ret; + char *filter; + char *user; + struct ldb_message *msg = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_cert_derb64_to_ldap_filter(tmp_ctx, data->filter_value, "", + id_ctx->sss_certmap_ctx, + id_ctx->domain, &filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + goto done; + } + if (filter == NULL || filter[0] != '(' + || filter[strlen(filter) - 1] != ')') { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter returned bad filter [%s].\n", + filter); + ret = EINVAL; + goto done; + } + + filter[strlen(filter) - 1] = '\0'; + user = sss_create_internal_fqname(tmp_ctx, &filter[1], + id_ctx->domain->name); + if (user == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapped to user: [%s].\n", user); + + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->domain, user, NULL, &msg); + if (ret == EOK) { + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_MAPPED_CERT, + data->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(id_ctx->domain->sysdb, msg->dn, attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found.\n", user); + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/files/files_id.c b/src/providers/files/files_id.c new file mode 100644 index 0000000..2103478 --- /dev/null +++ b/src/providers/files/files_id.c @@ -0,0 +1,222 @@ +/* + SSSD + + files_id.c - Identity operaions on the files provider + + Copyright (C) 2016 Red Hat + + 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 "providers/data_provider/dp.h" +#include "providers/files/files_private.h" + +struct files_account_info_handler_state { + struct dp_reply_std reply; + + struct files_id_ctx *id_ctx; + struct dp_id_data *data; +}; + +void handle_certmap(struct tevent_req *req) +{ + struct files_account_info_handler_state *state; + int ret; + + state = tevent_req_data(req, struct files_account_info_handler_state); + + ret = files_map_cert_to_user(state->id_ctx, state->data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "files_map_cert_to_user failed\n"); + } + + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +struct tevent_req * +files_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct files_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct files_account_info_handler_state *state; + struct tevent_req *req; + struct tevent_req **update_req = NULL; + bool needs_update; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct files_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->id_ctx = id_ctx; + + switch (data->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + if (data->filter_type != BE_FILTER_ENUM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected user filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->users_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_GROUP: + if (data->filter_type != BE_FILTER_ENUM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected group filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->groups_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_INITGROUPS: + if (data->filter_type != BE_FILTER_NAME) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected initgr filter type: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + if (strcmp(data->filter_value, DP_REQ_OPT_FILES_INITGR) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected initgr filter value: %d\n", data->filter_type); + ret = EINVAL; + goto immediate; + } + update_req = &id_ctx->initgroups_req; + needs_update = (id_ctx->refresh_ctx != NULL); + break; + case BE_REQ_BY_CERT: + if (data->filter_type != BE_FILTER_CERT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected filter type for lookup by cert: %d\n", + data->filter_type); + ret = EINVAL; + goto immediate; + } + + if (id_ctx->sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n"); + ret = EOK; + goto immediate; + } + + /* Refresh is running, we have to wait until it is done */ + if (id_ctx->refresh_ctx != NULL) { + state->data = data; + + ret = sf_add_certmap_req(id_ctx->refresh_ctx, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add request certmap request list.\n"); + goto immediate; + } + + return req; + } + + /* No refresh is running, we have reply immediately */ + ret = files_map_cert_to_user(id_ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "files_map_cert_to_user failed\n"); + } + goto immediate; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type: %d\n", data->entry_type & BE_REQ_TYPE_MASK); + ret = EINVAL; + goto immediate; + } + + if (needs_update == false) { + DEBUG(SSSDBG_TRACE_LIBS, "The files domain no longer needs an update\n"); + ret = EOK; + goto immediate; + } + + if (*update_req != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Received a concurrent update!\n"); + ret = EAGAIN; + goto immediate; + } + + /* id_ctx now must mark the requests as updated when the inotify-induced + * update finishes + */ + *update_req = req; + return req; + +immediate: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, params->ev); + return req; +} + +static void finish_update_req(struct tevent_req **update_req, + errno_t ret) +{ + if (*update_req == NULL) { + return; + } + + if (ret != EOK) { + tevent_req_error(*update_req, ret); + } else { + tevent_req_done(*update_req); + } + *update_req = NULL; +} + +void files_account_info_finished(struct files_id_ctx *id_ctx, + int req_type, + errno_t ret) +{ + finish_update_req(&id_ctx->users_req, ret); + finish_update_req(&id_ctx->groups_req, ret); + finish_update_req(&id_ctx->initgroups_req, ret); +} + +errno_t files_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct files_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct files_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + return EOK; +} diff --git a/src/providers/files/files_init.c b/src/providers/files/files_init.c new file mode 100644 index 0000000..ab6fa87 --- /dev/null +++ b/src/providers/files/files_init.c @@ -0,0 +1,261 @@ +/* + SSSD + + files_init.c - Initialization of the files provider + + Copyright (C) 2016 Red Hat + + 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 "providers/data_provider/dp.h" +#include "providers/files/files_private.h" +#include "util/util.h" + +#define DEFAULT_PASSWD_FILE "/etc/passwd" +#define DEFAULT_GROUP_FILE "/etc/group" + +static errno_t files_init_file_sources(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + const char ***_passwd_files, + const char ***_group_files) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *conf_passwd_files; + char *conf_group_files; + char **passwd_list = NULL; + char **group_list = NULL; + int num_passwd_files = 0; + int num_group_files = 0; + const char **passwd_files = NULL; + const char **group_files = NULL; + char *dfl_passwd_files = NULL; + char *env_group_files = NULL; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_getenv(tmp_ctx, "SSS_FILES_PASSWD", DEFAULT_PASSWD_FILE, + &dfl_passwd_files); + if (ret == EOK) { + sss_log(SSS_LOG_ALERT, + "Defaulting to %s for the passwd file, " + "this should only be used for testing!\n", + dfl_passwd_files); + } else if (ret != ENOENT) { + sss_log(SSS_LOG_ALERT, "sss_getenv() failed"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Using passwd file: [%s].\n", + dfl_passwd_files); + + ret = sss_getenv(tmp_ctx, "SSS_FILES_GROUP", DEFAULT_GROUP_FILE, + &env_group_files); + if (ret == EOK) { + sss_log(SSS_LOG_ALERT, + "Defaulting to %s for the group file, " + "this should only be used for testing!\n", + env_group_files); + } else if (ret != ENOENT) { + sss_log(SSS_LOG_ALERT, "sss_getenv() failed"); + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Using group file: [%s].\n", + env_group_files); + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_FILES_PASSWD, dfl_passwd_files, + &conf_passwd_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve confdb passwd files!\n"); + goto done; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_FILES_GROUP, env_group_files, + &conf_group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to retrieve confdb group files!\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, conf_passwd_files, ',', true, true, + &passwd_list, &num_passwd_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse passwd list!\n"); + goto done; + } + + passwd_files = talloc_zero_array(tmp_ctx, const char *, + num_passwd_files + 1); + if (passwd_files == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_passwd_files; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using passwd file: [%s].\n", passwd_list[i]); + + passwd_files[i] = talloc_strdup(passwd_files, passwd_list[i]); + if (passwd_files[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Retrieve list of group files */ + ret = split_on_separator(tmp_ctx, conf_group_files, ',', true, true, + &group_list, &num_group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse group files!\n"); + goto done; + } + + group_files = talloc_zero_array(tmp_ctx, const char *, + num_group_files + 1); + if (group_files == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_group_files; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using group file: [%s].\n", group_list[i]); + group_files[i] = talloc_strdup(group_files, group_list[i]); + if (group_files[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_passwd_files = talloc_steal(mem_ctx, passwd_files); + *_group_files = talloc_steal(mem_ctx, group_files); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int sssm_files_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct files_id_ctx *ctx; + errno_t ret; + + ctx = talloc_zero(mem_ctx, struct files_id_ctx); + if (ctx == NULL) { + return ENOMEM; + } + + ctx->be = be_ctx; + ctx->domain = be_ctx->domain; + + ret = files_init_file_sources(ctx, be_ctx, + &ctx->passwd_files, + &ctx->group_files); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize the passwd/group source files\n"); + goto done; + } + + ctx->fctx = sf_init(ctx, be_ctx->ev, + ctx->passwd_files, + ctx->group_files, + ctx); + if (ctx->fctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } else { + ret = files_init_certmap(ctx, ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "files_init_certmap failed. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + } + + *_module_data = ctx; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sssm_files_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct files_id_ctx *ctx; + + ctx = talloc_get_type(module_data, struct files_id_ctx); + if (ctx == NULL) { + return EINVAL; + } + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + files_account_info_handler_send, + files_account_info_handler_recv, + ctx, struct files_id_ctx, + struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, + default_account_domain_recv, + NULL, void, + struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +int sssm_files_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + files_auth_handler_send, files_auth_handler_recv, NULL, void, + struct pam_data, struct pam_data *); + + return EOK; +} diff --git a/src/providers/files/files_ops.c b/src/providers/files/files_ops.c new file mode 100644 index 0000000..556d56d --- /dev/null +++ b/src/providers/files/files_ops.c @@ -0,0 +1,1484 @@ +/* + SSSD + + Files provider operations + + Copyright (C) 2016 Red Hat + + 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 <dlfcn.h> + +#include "config.h" + +#include "providers/files/files_private.h" +#include "db/sysdb.h" +#include "util/inotify.h" +#include "util/util.h" +#include "providers/data_provider/dp_iface.h" + +/* When changing this constant, make sure to also adjust the files integration + * test for reallocation branch + */ +#define FILES_REALLOC_CHUNK 64 + +#define PWD_MAXSIZE 1024 +#define GRP_MAXSIZE 2048 + +#define SF_UPDATE_PASSWD 1<<0 +#define SF_UPDATE_GROUP 1<<1 +#define SF_UPDATE_BOTH (SF_UPDATE_PASSWD | SF_UPDATE_GROUP) +#define SF_UPDATE_IMMEDIATE 1<<2 + +struct files_ctx { + struct files_ops_ctx *ops; +}; + +static errno_t enum_files_users(TALLOC_CTX *mem_ctx, + const char *passwd_file, + struct passwd ***_users) +{ + errno_t ret, close_ret; + struct passwd *pwd_iter = NULL; + struct passwd *pwd = NULL; + struct passwd **users = NULL; + FILE *pwd_handle = NULL; + size_t n_users = 0; + + pwd_handle = fopen(passwd_file, "r"); + if (pwd_handle == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot open passwd file %s [%d]\n", + passwd_file, ret); + goto done; + } + + users = talloc_zero_array(mem_ctx, struct passwd *, + FILES_REALLOC_CHUNK); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + + while ((pwd_iter = fgetpwent(pwd_handle)) != NULL) { + /* FIXME - we might want to support paging of sorts to avoid allocating + * all users atop a memory context or only return users that differ from + * the local storage as a diff to minimize memory spikes + */ + DEBUG(SSSDBG_TRACE_LIBS, + "User found (%s, %s, %"SPRIuid", %"SPRIgid", %s, %s, %s)\n", + pwd_iter->pw_name, pwd_iter->pw_passwd, + pwd_iter->pw_uid, pwd_iter->pw_gid, + pwd_iter->pw_gecos, pwd_iter->pw_dir, + pwd_iter->pw_shell); + + pwd = talloc_zero(users, struct passwd); + if (pwd == NULL) { + ret = ENOMEM; + goto done; + } + + pwd->pw_uid = pwd_iter->pw_uid; + pwd->pw_gid = pwd_iter->pw_gid; + + pwd->pw_name = talloc_strdup(pwd, pwd_iter->pw_name); + if (pwd->pw_name == NULL) { + /* We only check pw_name here on purpose to allow broken + * records to be optionally rejected when saving them + * or fallback values to be used. + */ + ret = ENOMEM; + goto done; + } + + pwd->pw_dir = talloc_strdup(pwd, pwd_iter->pw_dir); + pwd->pw_gecos = talloc_strdup(pwd, pwd_iter->pw_gecos); + pwd->pw_shell = talloc_strdup(pwd, pwd_iter->pw_shell); + pwd->pw_passwd = talloc_strdup(pwd, pwd_iter->pw_passwd); + + users[n_users] = pwd; + n_users++; + if (n_users % FILES_REALLOC_CHUNK == 0) { + users = talloc_realloc(mem_ctx, + users, + struct passwd *, + talloc_array_length(users) + FILES_REALLOC_CHUNK); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + ret = EOK; + users[n_users] = NULL; + *_users = users; +done: + if (ret != EOK) { + talloc_free(users); + } + + if (pwd_handle) { + close_ret = fclose(pwd_handle); + if (close_ret != 0) { + close_ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot close passwd file %s [%d]\n", + passwd_file, close_ret); + } + } + return ret; +} + +static errno_t enum_files_groups(TALLOC_CTX *mem_ctx, + const char *group_file, + struct group ***_groups) +{ + errno_t ret, close_ret; + struct group *grp_iter = NULL; + struct group *grp = NULL; + struct group **groups = NULL; + size_t n_groups = 0; + FILE *grp_handle = NULL; + + grp_handle = fopen(group_file, "r"); + if (grp_handle == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot open group file %s [%d]\n", + group_file, ret); + goto done; + } + + groups = talloc_zero_array(mem_ctx, struct group *, + FILES_REALLOC_CHUNK); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + while ((grp_iter = fgetgrent(grp_handle)) != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Group found (%s, %"SPRIgid")\n", + grp_iter->gr_name, grp_iter->gr_gid); + + grp = talloc_zero(groups, struct group); + if (grp == NULL) { + ret = ENOMEM; + goto done; + } + + grp->gr_gid = grp_iter->gr_gid; + grp->gr_name = talloc_strdup(grp, grp_iter->gr_name); + if (grp->gr_name == NULL) { + /* We only check gr_name here on purpose to allow broken + * records to be optionally rejected when saving them + * or fallback values to be used. + */ + ret = ENOMEM; + goto done; + } + grp->gr_passwd = talloc_strdup(grp, grp_iter->gr_passwd); + + if (grp_iter->gr_mem != NULL) { + size_t nmem; + + for (nmem = 0; grp_iter->gr_mem[nmem] != NULL; nmem++); + + grp->gr_mem = talloc_zero_array(grp, char *, nmem + 1); + if (grp->gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + for (nmem = 0; grp_iter->gr_mem[nmem] != NULL; nmem++) { + grp->gr_mem[nmem] = talloc_strdup(grp, grp_iter->gr_mem[nmem]); + if (grp->gr_mem[nmem] == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + groups[n_groups] = grp; + n_groups++; + if (n_groups % FILES_REALLOC_CHUNK == 0) { + groups = talloc_realloc(mem_ctx, + groups, + struct group *, + talloc_array_length(groups) + FILES_REALLOC_CHUNK); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + ret = EOK; + groups[n_groups] = NULL; + *_groups = groups; +done: + if (ret != EOK) { + talloc_free(groups); + } + + if (grp_handle) { + close_ret = fclose(grp_handle); + if (close_ret != 0) { + close_ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot close group file %s [%d]\n", + group_file, close_ret); + } + } + return ret; +} + +static errno_t delete_all_users(struct sss_domain_info *dom) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + base_dn = sysdb_user_base_dn(tmp_ctx, dom); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(dom->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete users subtree [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t save_file_user(struct files_id_ctx *id_ctx, + struct passwd *pw) +{ + errno_t ret; + char *fqname; + TALLOC_CTX *tmp_ctx = NULL; + const char *shell; + const char *gecos; + struct sysdb_attrs *attrs = NULL; + + if (strcmp(pw->pw_name, "root") == 0 + || pw->pw_uid == 0 + || pw->pw_gid == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping %s\n", pw->pw_name); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + fqname = sss_create_internal_fqname(tmp_ctx, pw->pw_name, + id_ctx->domain->name); + if (fqname == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + if (pw->pw_shell && pw->pw_shell[0] != '\0') { + shell = pw->pw_shell; + } else { + shell = NULL; + } + + if (pw->pw_gecos && pw->pw_gecos[0] != '\0') { + gecos = pw->pw_gecos; + } else { + gecos = NULL; + } + + /* FIXME - optimize later */ + ret = sysdb_store_user(id_ctx->domain, + fqname, + pw->pw_passwd, + pw->pw_uid, + pw->pw_gid, + gecos, + pw->pw_dir, + shell, + NULL, attrs, + NULL, 0, 0); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t refresh_override_attrs(struct files_id_ctx *id_ctx, + enum sysdb_member_type type) +{ + const char *override_attrs[] = { SYSDB_OVERRIDE_OBJECT_DN, + NULL}; + struct ldb_dn *base_dn; + size_t count; + struct ldb_message **msgs; + struct ldb_message *msg = NULL; + struct ldb_context *ldb_ctx; + size_t c; + TALLOC_CTX *tmp_ctx; + int ret; + const char *filter; + + ldb_ctx = sysdb_ctx_get_ldb(id_ctx->domain->sysdb); + if (ldb_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing ldb_context.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "%s=%s", SYSDB_OBJECTCLASS, + type == SYSDB_MEMBER_USER ? + SYSDB_OVERRIDE_USER_CLASS : + SYSDB_OVERRIDE_GROUP_CLASS ); + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + base_dn = ldb_dn_new(tmp_ctx, ldb_ctx, SYSDB_TMPL_VIEW_BASE); + if (base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, id_ctx->domain->sysdb, base_dn, + LDB_SCOPE_SUBTREE, filter, + override_attrs, &count, &msgs); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No overrides, nothing to do.\n"); + ret = EOK; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_entry failed.\n"); + } + goto done; + } + + for (c = 0; c < count; c++) { + talloc_free(msg); + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + msg->dn = ldb_msg_find_attr_as_dn(ldb_ctx, tmp_ctx, msgs[c], + SYSDB_OVERRIDE_OBJECT_DN); + if (msg->dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get object DN, skipping.\n"); + continue; + } + + ret = ldb_msg_add_empty(msg, SYSDB_OVERRIDE_DN, LDB_FLAG_MOD_ADD, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_empty failed.\n"); + continue; + } + + ret = ldb_msg_add_string(msg, SYSDB_OVERRIDE_DN, + ldb_dn_get_linearized(msgs[c]->dn)); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_string failed.\n"); + continue; + } + + ret = ldb_modify(ldb_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store override DN: %s(%d)[%s], skipping.\n", + ldb_strerror(ret), ret, ldb_errstring(ldb_ctx)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sf_enum_groups(struct files_id_ctx *id_ctx, + struct group **groups, size_t start, size_t size); + +static errno_t sf_enum_users(struct files_id_ctx *id_ctx, struct passwd **users, + size_t start, size_t size) +{ + errno_t ret; + size_t i; + + for (i = start; i < (start + size) && users[i] != NULL; i++) { + ret = save_file_user(id_ctx, users[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save user %s: [%d]: %s\n", + users[i]->pw_name, ret, sss_strerror(ret)); + continue; + } + } + + if (users[i] == NULL) { + ret = refresh_override_attrs(id_ctx, SYSDB_MEMBER_USER); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to refresh override attributes, " + "override values might not be available.\n"); + } + + ret = EOK; + } else { + ret = EAGAIN; + } + + return ret; +} + +static const char **get_cached_user_names(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom) +{ + errno_t ret; + struct ldb_result *res = NULL; + const char **user_names = NULL; + unsigned c = 0; + + ret = sysdb_enumpwent(mem_ctx, dom, &res); + if (ret != EOK) { + goto done; + } + + user_names = talloc_zero_array(mem_ctx, const char *, res->count + 1); + if (user_names == NULL) { + goto done; + } + + for (unsigned i = 0; i < res->count; i++) { + user_names[c] = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (user_names[c] == NULL) { + continue; + } + c++; + } + +done: + /* Don't free res and keep it around to avoid duplicating the names */ + return user_names; +} + +static errno_t delete_all_groups(struct sss_domain_info *dom) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + base_dn = sysdb_group_base_dn(tmp_ctx, dom); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(dom->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete groups subtree [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t save_file_group(struct files_id_ctx *id_ctx, + struct group *grp, + const char **cached_users) +{ + errno_t ret; + char *fqname; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx = NULL; + char **fq_gr_files_mem; + const char **fq_gr_mem; + unsigned mi = 0; + + if (strcmp(grp->gr_name, "root") == 0 + || grp->gr_gid == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping %s\n", grp->gr_name); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + fqname = sss_create_internal_fqname(tmp_ctx, grp->gr_name, + id_ctx->domain->name); + if (fqname == NULL) { + ret = ENOMEM; + goto done; + + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + if (grp->gr_mem && grp->gr_mem[0]) { + fq_gr_files_mem = sss_create_internal_fqname_list( + tmp_ctx, + (const char *const*) grp->gr_mem, + id_ctx->domain->name); + if (fq_gr_files_mem == NULL) { + ret = ENOMEM; + goto done; + } + + fq_gr_mem = talloc_zero_array(tmp_ctx, const char *, + talloc_array_length(fq_gr_files_mem)); + if (fq_gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + for (unsigned i=0; fq_gr_files_mem[i] != NULL; i++) { + if (string_in_list(fq_gr_files_mem[i], + discard_const(cached_users), + true)) { + fq_gr_mem[mi] = fq_gr_files_mem[i]; + mi++; + + DEBUG(SSSDBG_TRACE_LIBS, + "User %s is cached, will become a member of %s\n", + fq_gr_files_mem[i], grp->gr_name); + } else { + ret = sysdb_attrs_add_string(attrs, + SYSDB_GHOST, + fq_gr_files_mem[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot add ghost %s for group %s\n", + fq_gr_files_mem[i], fqname); + continue; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "User %s is not cached, will become a ghost of %s\n", + fq_gr_files_mem[i], grp->gr_name); + } + } + + if (fq_gr_mem != NULL && fq_gr_mem[0] != NULL) { + ret = sysdb_attrs_users_from_str_list( + attrs, SYSDB_MEMBER, id_ctx->domain->name, + (const char *const *) fq_gr_mem); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group members\n"); + goto done; + } + } + + } + + ret = sysdb_store_group(id_ctx->domain, fqname, grp->gr_gid, + attrs, 0, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group to cache\n"); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sf_enum_groups(struct files_id_ctx *id_ctx, + struct group **groups, size_t start, size_t size) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **cached_users = NULL; + size_t i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cached_users = get_cached_user_names(tmp_ctx, id_ctx->domain); + if (cached_users == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = start; i < (start + size) && groups[i] != NULL; i++) { + ret = save_file_group(id_ctx, groups[i], cached_users); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot save group %s\n", groups[i]->gr_name); + continue; + } + } + + if (groups[i] == NULL) { + ret = refresh_override_attrs(id_ctx, SYSDB_MEMBER_GROUP); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to refresh override attributes, " + "override values might not be available.\n"); + } + + ret = EOK; + } else { + ret = EAGAIN; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +enum update_steps { + WAIT_TO_START_USERS, + DELETE_USERS, + READ_USERS, + SAVE_USERS, + WAIT_TO_START_GROUPS, + DELETE_GROUPS, + READ_GROUPS, + SAVE_GROUPS, + UPDATE_FINISH, + UPDATE_DONE, +}; + +struct certmap_req_list { + struct tevent_req *req; + struct certmap_req_list *prev; + struct certmap_req_list *next; +}; + +struct files_refresh_ctx { + struct timeval start_passwd_refresh; + enum refresh_task_status updating_passwd; + bool passwd_start_again; + struct timeval start_group_refresh; + enum refresh_task_status updating_groups; + bool group_start_again; + + struct certmap_req_list *certmap_req_list; +}; + +errno_t sf_add_certmap_req(struct files_refresh_ctx *refresh_ctx, + struct tevent_req *req) +{ + struct certmap_req_list *certmap_req_item; + + certmap_req_item = talloc_zero(refresh_ctx, struct certmap_req_list); + if (certmap_req_item == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allow memory for certmap request list.\n"); + return ENOMEM; + } + certmap_req_item->req = req; + DLIST_ADD(refresh_ctx->certmap_req_list, certmap_req_item); + + return EOK; +} + +static errno_t check_state(struct files_refresh_ctx *refresh_ctx, uint8_t flags) +{ + errno_t ret; + struct timeval tv; + struct timeval delay = { 1, 0 }; + const struct timeval tv_zero = {0 , 0}; + + errno = 0; + ret = gettimeofday(&tv, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], keeping old value.\n", + ret, sss_strerror(ret)); + } + + if ((flags & SF_UPDATE_PASSWD) && (flags & SF_UPDATE_GROUP)) { + if (flags & SF_UPDATE_IMMEDIATE) { + refresh_ctx->start_passwd_refresh = tv_zero; + } else { + if (ret == EOK) { + timeradd(&tv, &delay, + &refresh_ctx->start_passwd_refresh); + } + } + + switch (refresh_ctx->updating_passwd) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh is already waiting to start, nothing to do.\n"); + return EAGAIN; + case REFRESH_ACTIVE: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh currently active, queing another refresh.\n"); + refresh_ctx->passwd_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_passwd); + return EINVAL; + } + + /* Groups are updated after passwd, in case a new passwd update + * arrives we have to run the passwd steps again. */ + switch (refresh_ctx->updating_groups) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + refresh_ctx->passwd_start_again = true; + return EAGAIN; + case REFRESH_ACTIVE: + refresh_ctx->passwd_start_again = true; + refresh_ctx->group_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_groups); + return EINVAL; + } + + refresh_ctx->passwd_start_again = false; + refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + return EOK; + } else if (flags & SF_UPDATE_GROUP) { + if (flags & SF_UPDATE_IMMEDIATE) { + refresh_ctx->start_group_refresh = tv_zero; + } else { + if (ret == EOK) { + timeradd(&tv, &delay, + &refresh_ctx->start_group_refresh); + } + } + + switch (refresh_ctx->updating_groups) { + case REFRESH_NOT_RUNNIG: + break; + case REFRESH_WAITING_TO_START: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh is already waiting to start, nothing to do.\n"); + return EAGAIN; + case REFRESH_ACTIVE: + DEBUG(SSSDBG_TRACE_FUNC, + "Refresh currently active, queing another refresh.\n"); + refresh_ctx->group_start_again = true; + return EAGAIN; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown refresh state [%d].\n", + refresh_ctx->updating_passwd); + return EINVAL; + } + + refresh_ctx->group_start_again = false; + refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected refresh flags [%"PRIu8"].\n", flags); + return EINVAL; +} + +struct sf_enum_files_state { + struct files_id_ctx *id_ctx; + struct files_refresh_ctx *refresh_ctx; + uint8_t flags; + struct tevent_timer *te; + enum update_steps current_step; + size_t step; + bool in_transaction; + size_t batch_size; + size_t obj_idx; + size_t file_idx; + struct passwd **users; + struct group **groups; + uint32_t delay; + uint32_t initial_delay; +}; + +static int clear_refresh_ctx(void *ptr) +{ + struct sf_enum_files_state *state = (struct sf_enum_files_state *) ptr; + + state->id_ctx->refresh_ctx = NULL; + + return 0; +} + +static void sf_enum_files_steps(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data); +static struct tevent_req *sf_enum_files_send(struct files_id_ctx *id_ctx, + uint8_t flags) +{ + struct tevent_req *req; + struct sf_enum_files_state *state; + struct timeval tv; + errno_t ret; + struct files_refresh_ctx *refresh_ctx = NULL; + + if (id_ctx->refresh_ctx != NULL) { + refresh_ctx = id_ctx->refresh_ctx; + } else { + refresh_ctx = talloc_zero(id_ctx, struct files_refresh_ctx); + if (refresh_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate refresh context.\n"); + return NULL; + } + refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + refresh_ctx->certmap_req_list = NULL; + } + + ret = check_state(refresh_ctx, flags); + if (ret != EOK) { + return NULL; + } + + req = tevent_req_create(id_ctx, &state, struct sf_enum_files_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (id_ctx->refresh_ctx == NULL) { + id_ctx->refresh_ctx = talloc_steal(state, refresh_ctx); + talloc_set_destructor((TALLOC_CTX *) state, clear_refresh_ctx); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "The files refresh task should run only " + "once, but a second was detected. Error in internal procession " + "logic.\n"); + ret = EFAULT; + goto done; + } + + state->id_ctx = id_ctx; + state->flags = flags; + state->step = 0; + state->batch_size = 1000; + state->obj_idx = 0; + state->file_idx = 0; + state->initial_delay = 100; + state->delay = 100; + + if (state->flags & SF_UPDATE_PASSWD) { + state->current_step = WAIT_TO_START_USERS; + } else if (state->flags & SF_UPDATE_GROUP) { + state->current_step = WAIT_TO_START_GROUPS; + } else { + DEBUG(SSSDBG_OP_FAILURE, "None of the expected flags are set, " + "cannot start the refresh.\n"); + ret = EINVAL; + goto done; + } + + tv = tevent_timeval_current_ofs(0, state->initial_delay); + state->te = tevent_add_timer(id_ctx->be->ev, state, tv, + sf_enum_files_steps, req); + if (state->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule files update.\n"); + ret = EFAULT; + goto done; + } + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, id_ctx->be->ev); + return req; +} + +static void sf_enum_files_steps(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *data) +{ + errno_t ret; + errno_t tret; + struct sf_enum_files_state *state; + struct tevent_req *req; + struct files_id_ctx *id_ctx; + const char *filename = NULL; + struct timeval now; + struct timeval diff; + uint32_t delay; + struct certmap_req_list *certmap_req_item; + struct certmap_req_list *certmap_req_tmp; + + req = talloc_get_type(data, struct tevent_req); + state = tevent_req_data(req, struct sf_enum_files_state); + + state->te = NULL; + id_ctx = state->id_ctx; + delay = state->delay; + + switch (state->current_step) { + case WAIT_TO_START_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step WAIT_TO_START_USERS.\n"); + errno = 0; + ret = gettimeofday(&now, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], starting user refresh now.\n", + ret, sss_strerror(ret)); + state->current_step = DELETE_USERS; + delay = 0; + } else { + timersub(&state->id_ctx->refresh_ctx->start_passwd_refresh, &now, + &diff); + if (diff.tv_sec < 0) { + state->current_step = DELETE_USERS; + delay = 0; + } else { + delay = diff.tv_sec*1000000 + diff.tv_usec; + } + } + break; + case DELETE_USERS: + if (!state->in_transaction) { + ret = sysdb_transaction_start(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = true; + } + + id_ctx->refresh_ctx->updating_passwd = REFRESH_ACTIVE; + DEBUG(SSSDBG_TRACE_ALL, "Step DELETE_USERS.\n"); + ret = delete_all_users(id_ctx->domain); + if (ret != EOK) { + goto done; + } + state->file_idx = 0; + state->current_step = READ_USERS; + break; + case READ_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step READ_USERS.\n"); + talloc_zfree(state->users); + state->obj_idx = 0; + /* All users were deleted, therefore we need to enumerate each file again */ + if (id_ctx->passwd_files[state->file_idx] != NULL) { + filename = id_ctx->passwd_files[state->file_idx++]; + ret = enum_files_users(state, filename, &state->users); + if (ret == EOK) { + state->current_step = SAVE_USERS; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The file %s does not exist (yet), skipping\n", + filename); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enumerate users from %s, aborting\n", + filename); + goto done; + } + } else { + id_ctx->refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + if (state->flags & SF_UPDATE_GROUP) { + state->current_step = WAIT_TO_START_GROUPS; + } else { + if (state->id_ctx->refresh_ctx->passwd_start_again) { + state->id_ctx->refresh_ctx->passwd_start_again = false; + id_ctx->refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_USERS; + } else if (state->id_ctx->refresh_ctx->group_start_again) { + state->id_ctx->refresh_ctx->group_start_again = false; + id_ctx->refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_GROUPS; + } else { + state->current_step = UPDATE_FINISH; + } + } + } + break; + case SAVE_USERS: + DEBUG(SSSDBG_TRACE_ALL, "Step SAVE_USERS.\n"); + if (state->users != NULL) { + ret = sf_enum_users(id_ctx, state->users, + state->obj_idx, state->batch_size); + if (ret == EOK) { + /* check next file */ + state->current_step = READ_USERS; + } else if (ret == EAGAIN) { + state->obj_idx += state->batch_size; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Saving users failed.\n"); + goto done; + } + } + break; + case WAIT_TO_START_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step WAIT_TO_START_GROUPS.\n"); + errno = 0; + ret = gettimeofday(&now, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "gettimeofday failed [%d][%s], starting user refresh now.\n", + ret, sss_strerror(ret)); + state->current_step = DELETE_GROUPS; + delay = 0; + } else { + timersub(&state->id_ctx->refresh_ctx->start_passwd_refresh, &now, + &diff); + if (diff.tv_sec < 0) { + state->current_step = DELETE_GROUPS; + delay = 0; + } else { + delay = diff.tv_sec*1000000 + diff.tv_usec; + } + } + break; + case DELETE_GROUPS: + if (!state->in_transaction) { + ret = sysdb_transaction_start(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = true; + } + id_ctx->refresh_ctx->updating_groups = REFRESH_ACTIVE; + DEBUG(SSSDBG_TRACE_ALL, "Step DELETE_GROUPS.\n"); + ret = delete_all_groups(id_ctx->domain); + if (ret != EOK) { + goto done; + } + state->file_idx = 0; + state->current_step = READ_GROUPS; + break; + case READ_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step READ_GROUPS.\n"); + talloc_zfree(state->groups); + state->obj_idx = 0; + /* All groups were deleted, therefore we need to enumerate each file again */ + if (id_ctx->group_files[state->file_idx] != NULL) { + filename = id_ctx->group_files[state->file_idx++]; + ret = enum_files_groups(state, filename, &state->groups); + if (ret == EOK) { + state->current_step = SAVE_GROUPS; + } else if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The file %s does not exist (yet), skipping\n", + filename); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enumerate groups from %s, aborting\n", + filename); + goto done; + } + } else { + id_ctx->refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + if (state->id_ctx->refresh_ctx->passwd_start_again) { + state->id_ctx->refresh_ctx->passwd_start_again = false; + id_ctx->refresh_ctx->updating_passwd = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_USERS; + } else if (state->id_ctx->refresh_ctx->group_start_again) { + state->id_ctx->refresh_ctx->group_start_again = false; + id_ctx->refresh_ctx->updating_groups = REFRESH_WAITING_TO_START; + state->current_step = WAIT_TO_START_GROUPS; + } else { + state->current_step = UPDATE_FINISH; + } + } + break; + case SAVE_GROUPS: + DEBUG(SSSDBG_TRACE_ALL, "Step SAVE_GROUPS.\n"); + if (state->groups != NULL) { + ret = sf_enum_groups(id_ctx, state->groups, + state->obj_idx, state->batch_size); + if (ret == EOK) { + state->current_step = READ_GROUPS; + } else if (ret == EAGAIN) { + state->obj_idx += state->batch_size; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Saving groups failed.\n"); + goto done; + } + } + break; + case UPDATE_FINISH: + DEBUG(SSSDBG_TRACE_ALL, "Step UPDATE_FINISH.\n"); + ret = dp_add_sr_attribute(id_ctx->be); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add session recording attribute, ignored.\n"); + } + + ret = sysdb_transaction_commit(id_ctx->domain->sysdb); + if (ret != EOK) { + goto done; + } + state->in_transaction = false; + + state->current_step = UPDATE_DONE; + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Undefined update step [%u].\n", + state->current_step); + ret = EINVAL; + goto done; + } + + if (state->current_step != UPDATE_DONE) { + tv = tevent_timeval_current_ofs(0, delay); + state->te = tevent_add_timer(id_ctx->be->ev, state, tv, + sf_enum_files_steps, req); + if (state->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule files update.\n"); + ret = EFAULT; + goto done; + } + + return; + } + + ret = EOK; +done: + if (state->in_transaction) { + tret = sysdb_transaction_cancel(id_ctx->domain->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot cancel transaction: %d\n", ret); + } + state->in_transaction = false; + } + + DLIST_FOR_EACH_SAFE(certmap_req_item, certmap_req_tmp, + id_ctx->refresh_ctx->certmap_req_list) { + handle_certmap(certmap_req_item->req); + DLIST_REMOVE(certmap_req_item, + id_ctx->refresh_ctx->certmap_req_list); + talloc_free(certmap_req_item); + } + + id_ctx->refresh_ctx->updating_passwd = REFRESH_NOT_RUNNIG; + id_ctx->refresh_ctx->updating_groups = REFRESH_NOT_RUNNIG; + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t sf_enum_files_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void sf_cb_done(struct files_id_ctx *id_ctx) +{ + /* Only activate a domain when both callbacks are done */ + if (id_ctx->refresh_ctx == NULL) { + dp_sbus_domain_active(id_ctx->be->provider, + id_ctx->domain); + } +} + +static void sf_passwd_cb_done(struct tevent_req *req); +static int sf_passwd_cb(const char *filename, uint32_t flags, void *pvt) +{ + struct files_id_ctx *id_ctx; + struct tevent_req *req; + errno_t ret; + + id_ctx = talloc_get_type(pvt, struct files_id_ctx); + if (id_ctx == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passwd notification\n"); + dp_sbus_domain_inconsistent(id_ctx->be->provider, id_ctx->domain); + + dp_sbus_reset_users_ncache(id_ctx->be->provider, id_ctx->domain); + dp_sbus_reset_users_memcache(id_ctx->be->provider); + dp_sbus_reset_initgr_memcache(id_ctx->be->provider); + + /* Using SF_UDPATE_BOTH here the case when someone edits /etc/group, adds a group member and + * only then edits passwd and adds the user. The reverse is not needed, + * because member/memberof links are established when groups are saved. + */ + req = sf_enum_files_send(id_ctx, SF_UPDATE_BOTH); + if (req == NULL) { + if (id_ctx->refresh_ctx != NULL) { + /* Update is currently active, nothing to do */ + return EOK; + } + DEBUG(SSSDBG_OP_FAILURE, "Failed to start files update.\n"); + ret = ENOMEM; + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_USER, ret); + return ret; + } + + tevent_req_set_callback(req, sf_passwd_cb_done, id_ctx); + + return EOK; +} + +static void sf_passwd_cb_done(struct tevent_req *req) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + + id_ctx = tevent_req_callback_data(req, struct files_id_ctx); + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_USER, ret); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); +} + +static void sf_group_cb_done(struct tevent_req *req); +static int sf_group_cb(const char *filename, uint32_t flags, void *pvt) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + struct tevent_req *req; + + id_ctx = talloc_get_type(pvt, struct files_id_ctx); + if (id_ctx == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "group notification\n"); + dp_sbus_domain_inconsistent(id_ctx->be->provider, id_ctx->domain); + + dp_sbus_reset_groups_ncache(id_ctx->be->provider, id_ctx->domain); + dp_sbus_reset_groups_memcache(id_ctx->be->provider); + dp_sbus_reset_initgr_memcache(id_ctx->be->provider); + + req = sf_enum_files_send(id_ctx, SF_UPDATE_GROUP); + if (req == NULL) { + if (id_ctx->refresh_ctx != NULL) { + /* Update is currently active, nothing to do */ + return EOK; + } + DEBUG(SSSDBG_OP_FAILURE, "Failed to start files update.\n"); + ret = ENOMEM; + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); + return ret; + } + + tevent_req_set_callback(req, sf_group_cb_done, id_ctx); + + return EOK; +} + +static void sf_group_cb_done(struct tevent_req *req) +{ + struct files_id_ctx *id_ctx; + errno_t ret; + + id_ctx = tevent_req_callback_data(req, struct files_id_ctx); + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + sf_cb_done(id_ctx); + files_account_info_finished(id_ctx, BE_REQ_GROUP, ret); +} + +static void startup_enum_files_done(struct tevent_req *req); +static void startup_enum_files(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct files_id_ctx *id_ctx = talloc_get_type(pvt, struct files_id_ctx); + struct tevent_req *req; + + talloc_zfree(imm); + + req = sf_enum_files_send(id_ctx, SF_UPDATE_BOTH|SF_UPDATE_IMMEDIATE); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not update files after startup.\n"); + return; + } + + tevent_req_set_callback(req, startup_enum_files_done, NULL); +} + +static void startup_enum_files_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sf_enum_files_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not update files after startup: [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static struct snotify_ctx *sf_setup_watch(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *filename, + snotify_cb_fn fn, + struct files_id_ctx *id_ctx) +{ + return snotify_create(mem_ctx, ev, SNOTIFY_WATCH_DIR, + filename, NULL, + IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF | \ + IN_CREATE | IN_MOVED_TO, + fn, id_ctx); +} + +struct files_ctx *sf_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char **passwd_files, + const char **group_files, + struct files_id_ctx *id_ctx) +{ + struct files_ctx *fctx; + struct tevent_immediate *imm; + int i; + struct snotify_ctx *snctx; + + fctx = talloc(mem_ctx, struct files_ctx); + if (fctx == NULL) { + return NULL; + } + + for (i = 0; passwd_files[i]; i++) { + snctx = sf_setup_watch(fctx, ev, passwd_files[i], + sf_passwd_cb, id_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot set watch for passwd file %s\n", passwd_files[i]); + /* Rather than reporting incomplete or inconsistent information + * in case e.g. group memberships span multiple files, just abort + */ + talloc_free(fctx); + return NULL; + } + } + + for (i = 0; group_files[i]; i++) { + snctx = sf_setup_watch(fctx, ev, group_files[i], + sf_group_cb, id_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot set watch for group file %s\n", group_files[i]); + /* Rather than reporting incomplete or inconsistent information + * in case e.g. group memberships span multiple files, just abort + */ + talloc_free(fctx); + return NULL; + } + } + + /* Enumerate users and groups on startup to process any changes when + * sssd was down. We schedule a request here to minimize the time + * we spend in the init function + */ + imm = tevent_create_immediate(id_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(fctx); + return NULL; + } + tevent_schedule_immediate(imm, ev, startup_enum_files, id_ctx); + + return fctx; +} diff --git a/src/providers/files/files_private.h b/src/providers/files/files_private.h new file mode 100644 index 0000000..4134a05 --- /dev/null +++ b/src/providers/files/files_private.h @@ -0,0 +1,100 @@ +/* + SSSD + + Files provider declarations + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef __FILES_PRIVATE_H_ +#define __FILES_PRIVATE_H_ + +#include "config.h" + +#include <talloc.h> +#include <tevent.h> +#include <errno.h> +#include <sys/types.h> +#include <nss.h> +#include <pwd.h> +#include <grp.h> + +#include "providers/data_provider/dp.h" + +enum refresh_task_status { + REFRESH_NOT_RUNNIG = 0, + REFRESH_WAITING_TO_START, + REFRESH_ACTIVE +}; + +struct files_id_ctx { + struct be_ctx *be; + struct sss_domain_info *domain; + struct files_ctx *fctx; + struct sss_certmap_ctx *sss_certmap_ctx; + + const char **passwd_files; + const char **group_files; + + struct files_refresh_ctx *refresh_ctx; + + struct tevent_req *users_req; + struct tevent_req *groups_req; + struct tevent_req *initgroups_req; +}; + +/* files_ops.c */ +struct files_ctx *sf_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char **passwd_files, + const char **group_files, + struct files_id_ctx *id_ctx); + +errno_t sf_add_certmap_req(struct files_refresh_ctx *refresh_ctx, + struct tevent_req *req); +/* files_id.c */ +void handle_certmap(struct tevent_req *req); + +struct tevent_req * +files_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct files_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t files_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +void files_account_info_finished(struct files_id_ctx *id_ctx, + int req_type, + errno_t ret); + +/* files_auth.c */ +struct tevent_req *files_auth_handler_send(TALLOC_CTX *mem_ctx, + void *unused, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* files_certmap.c */ +errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx); + +errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx, + struct dp_id_data *data); +#endif /* __FILES_PRIVATE_H_ */ |