summaryrefslogtreecommitdiffstats
path: root/src/providers/be_refresh.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/be_refresh.c')
-rw-r--r--src/providers/be_refresh.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/src/providers/be_refresh.c b/src/providers/be_refresh.c
new file mode 100644
index 0000000..95cac20
--- /dev/null
+++ b/src/providers/be_refresh.c
@@ -0,0 +1,581 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2013 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 <tevent.h>
+#include <talloc.h>
+#include <time.h>
+#include <ldb.h>
+
+#include "providers/backend.h"
+#include "providers/be_ptask.h"
+#include "providers/be_refresh.h"
+#include "util/util_errors.h"
+#include "db/sysdb.h"
+
+static errno_t be_refresh_get_values_ex(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ time_t period,
+ struct ldb_dn *base_dn,
+ const char *key_attr,
+ const char *value_attr,
+ enum sysdb_cache_type search_cache,
+ char ***_values)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *attrs[] = {value_attr, NULL};
+ const char *filter = NULL;
+ char **values = NULL;
+ struct sysdb_attrs **records = NULL;
+ struct ldb_result *res;
+ time_t now = time(NULL);
+ errno_t ret;
+
+ if (key_attr == NULL || domain == NULL || base_dn == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ filter = talloc_asprintf(tmp_ctx, "(&(%s<=%lld))",
+ key_attr, (long long) now + period);
+ if (filter == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sysdb_search_with_ts_attr(tmp_ctx, domain, base_dn,
+ LDB_SCOPE_SUBTREE,
+ search_cache,
+ filter, attrs,
+ &res);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sysdb_msg2attrs(tmp_ctx, res->count, res->msgs, &records);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not convert ldb message to sysdb_attrs\n");
+ goto done;
+ }
+
+ ret = sysdb_attrs_to_list(tmp_ctx, records, res->count, value_attr, &values);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ *_values = talloc_steal(mem_ctx, values);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static errno_t be_refresh_get_values(TALLOC_CTX *mem_ctx,
+ enum be_refresh_type type,
+ const char *attr_name,
+ struct sss_domain_info *domain,
+ time_t period,
+ char ***_values)
+{
+ struct ldb_dn *base_dn = NULL;
+ errno_t ret;
+ const char *key_attr;
+ enum sysdb_cache_type search_cache = SYSDB_CACHE_TYPE_TIMESTAMP;
+
+ switch (type) {
+ case BE_REFRESH_TYPE_INITGROUPS:
+ key_attr = SYSDB_INITGR_EXPIRE;
+ base_dn = sysdb_user_base_dn(mem_ctx, domain);
+ break;
+ case BE_REFRESH_TYPE_USERS:
+ key_attr = SYSDB_CACHE_EXPIRE;
+ base_dn = sysdb_user_base_dn(mem_ctx, domain);
+ break;
+ case BE_REFRESH_TYPE_GROUPS:
+ key_attr = SYSDB_CACHE_EXPIRE;
+ base_dn = sysdb_group_base_dn(mem_ctx, domain);
+ break;
+ case BE_REFRESH_TYPE_NETGROUPS:
+ key_attr = SYSDB_CACHE_EXPIRE;
+ // Netgroup will reside in persistent cache rather than timestamp one
+ search_cache = SYSDB_CACHE_TYPE_PERSISTENT;
+ base_dn = sysdb_netgroup_base_dn(mem_ctx, domain);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Uknown or unsupported refresh type %d\n", type);
+ return ERR_INTERNAL;
+ break;
+ }
+
+ if (base_dn == NULL) {
+ return ENOMEM;
+ }
+
+ ret = be_refresh_get_values_ex(mem_ctx, domain, period,
+ base_dn, key_attr,
+ attr_name, search_cache, _values);
+
+ talloc_free(base_dn);
+ return ret;
+}
+
+struct be_refresh_cb_ctx {
+ const char *name;
+ const char *attr_name;
+ bool enabled;
+ struct be_refresh_cb cb;
+};
+
+struct be_refresh_ctx {
+ struct be_refresh_cb_ctx callbacks[BE_REFRESH_TYPE_SENTINEL];
+};
+
+static errno_t be_refresh_ctx_init(struct be_ctx *be_ctx,
+ const char *attr_name)
+{
+ struct be_refresh_ctx *ctx = NULL;
+ uint32_t refresh_interval;
+ uint32_t offset;
+ errno_t ret;
+
+ ctx = talloc_zero(be_ctx, struct be_refresh_ctx);
+ if (ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ctx->callbacks[BE_REFRESH_TYPE_INITGROUPS].name = "initgroups";
+ ctx->callbacks[BE_REFRESH_TYPE_INITGROUPS].attr_name = SYSDB_NAME;
+ ctx->callbacks[BE_REFRESH_TYPE_USERS].name = "users";
+ ctx->callbacks[BE_REFRESH_TYPE_USERS].attr_name = attr_name;
+ ctx->callbacks[BE_REFRESH_TYPE_GROUPS].name = "groups";
+ ctx->callbacks[BE_REFRESH_TYPE_GROUPS].attr_name = attr_name;
+ ctx->callbacks[BE_REFRESH_TYPE_NETGROUPS].name = "netgroups";
+ ctx->callbacks[BE_REFRESH_TYPE_NETGROUPS].attr_name = SYSDB_NAME;
+
+ refresh_interval = be_ctx->domain->refresh_expired_interval;
+ if (refresh_interval > 0) {
+ offset = be_ctx->domain->refresh_expired_interval_offset;
+ ret = be_ptask_create(be_ctx, be_ctx, refresh_interval, 30, 5, offset,
+ refresh_interval, 0,
+ be_refresh_send, be_refresh_recv,
+ ctx, "Refresh Records",
+ BE_PTASK_OFFLINE_SKIP |
+ BE_PTASK_SCHEDULE_FROM_NOW,
+ NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Unable to initialize refresh periodic task [%d]: %s\n",
+ ret, sss_strerror(ret));
+ talloc_free(ctx);
+ return ret;
+ }
+ }
+
+ be_ctx->refresh_ctx = ctx;
+ return EOK;
+}
+
+static errno_t be_refresh_add_cb(struct be_refresh_ctx *ctx,
+ enum be_refresh_type type,
+ struct be_refresh_cb *cb)
+{
+ if (ctx == NULL || cb->send_fn == NULL || cb->recv_fn == NULL
+ || type >= BE_REFRESH_TYPE_SENTINEL) {
+ return EINVAL;
+ }
+
+ if (ctx->callbacks[type].enabled) {
+ return EEXIST;
+ }
+
+ ctx->callbacks[type].enabled = true;
+ ctx->callbacks[type].cb.send_fn = cb->send_fn;
+ ctx->callbacks[type].cb.recv_fn = cb->recv_fn;
+ ctx->callbacks[type].cb.pvt = cb->pvt;
+
+ return EOK;
+}
+
+static errno_t be_refresh_set_callbacks(struct be_refresh_ctx *refresh_ctx,
+ struct be_refresh_cb *callbacks)
+{
+ errno_t ret;
+
+ if (callbacks == NULL || refresh_ctx == NULL) {
+ return EINVAL;
+ }
+
+ ret = be_refresh_add_cb(refresh_ctx,
+ BE_REFRESH_TYPE_INITGROUPS,
+ &callbacks[BE_REFRESH_TYPE_INITGROUPS]);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of initgroups "
+ "will not work [%d]: %s\n", ret, strerror(ret));
+ }
+
+ ret = be_refresh_add_cb(refresh_ctx,
+ BE_REFRESH_TYPE_USERS,
+ &callbacks[BE_REFRESH_TYPE_USERS]);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of users "
+ "will not work [%d]: %s\n", ret, strerror(ret));
+ }
+
+ ret = be_refresh_add_cb(refresh_ctx,
+ BE_REFRESH_TYPE_GROUPS,
+ &callbacks[BE_REFRESH_TYPE_GROUPS]);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of groups "
+ "will not work [%d]: %s\n", ret, strerror(ret));
+ }
+
+ ret = be_refresh_add_cb(refresh_ctx,
+ BE_REFRESH_TYPE_NETGROUPS,
+ &callbacks[BE_REFRESH_TYPE_NETGROUPS]);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh of netgroups "
+ "will not work [%d]: %s\n", ret, strerror(ret));
+ }
+
+ return EOK;
+}
+
+errno_t be_refresh_ctx_init_with_callbacks(struct be_ctx *be_ctx,
+ const char *attr_name,
+ struct be_refresh_cb *callbacks)
+{
+ errno_t ret;
+
+ if (be_ctx == NULL || attr_name == NULL || callbacks == NULL) {
+ return EINVAL;
+ }
+
+ ret = be_refresh_ctx_init(be_ctx, attr_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize refresh_ctx\n");
+ return ret;
+ }
+
+ ret = be_refresh_set_callbacks(be_ctx->refresh_ctx, callbacks);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize refresh callbacks\n");
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+struct be_refresh_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;
+ struct be_refresh_ctx *ctx;
+ struct be_refresh_cb_ctx *cb_ctx;
+
+ struct sss_domain_info *domain;
+ enum be_refresh_type index;
+ time_t period;
+
+ char **refresh_values;
+ size_t refresh_val_size;
+ size_t refresh_index;
+
+ size_t batch_size;
+ char **refresh_batch;
+};
+
+static errno_t be_refresh_batch_step(struct tevent_req *req,
+ uint32_t msec_delay);
+static void be_refresh_batch_step_wakeup(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv,
+ void *pvt);
+static errno_t be_refresh_step(struct tevent_req *req);
+static void be_refresh_done(struct tevent_req *subreq);
+
+struct tevent_req *be_refresh_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct be_ptask *be_ptask,
+ void *pvt)
+{
+ struct be_refresh_state *state = NULL;
+ struct tevent_req *req = NULL;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct be_refresh_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->be_ctx = be_ctx;
+ state->domain = be_ctx->domain;
+ state->period = be_ptask_get_period(be_ptask);
+ state->ctx = talloc_get_type(pvt, struct be_refresh_ctx);
+ if (state->ctx == NULL) {
+ ret = EINVAL;
+ goto immediately;
+ }
+
+ state->batch_size = 200;
+ state->refresh_batch = talloc_zero_array(state, char *, state->batch_size+1);
+ if (state->refresh_batch == NULL) {
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ ret = be_refresh_step(req);
+ if (ret == EOK) {
+ goto immediately;
+ } else if (ret != EAGAIN) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "be_refresh_step() failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto immediately;
+ }
+
+ return req;
+
+immediately:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, ev);
+
+ return req;
+}
+
+static errno_t be_refresh_step(struct tevent_req *req)
+{
+ struct be_refresh_state *state = NULL;
+ errno_t ret;
+
+ state = tevent_req_data(req, struct be_refresh_state);
+
+ while (state->domain != NULL) {
+ /* find first enabled callback */
+ state->cb_ctx = &state->ctx->callbacks[state->index];
+ while (state->index != BE_REFRESH_TYPE_SENTINEL
+ && !state->cb_ctx->enabled) {
+ state->index++;
+ state->cb_ctx = &state->ctx->callbacks[state->index];
+ }
+
+ /* if not found than continue with next domain */
+ if (state->index == BE_REFRESH_TYPE_SENTINEL) {
+ state->domain = get_next_domain(state->domain,
+ SSS_GND_DESCEND);
+ /* we can update just subdomains */
+ if (state->domain != NULL && !IS_SUBDOMAIN(state->domain)) {
+ break;
+ }
+ state->index = 0;
+ continue;
+ }
+
+ if (state->cb_ctx->cb.send_fn == NULL
+ || state->cb_ctx->cb.recv_fn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid parameters!\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ talloc_zfree(state->refresh_values);
+ ret = be_refresh_get_values(state, state->index,
+ state->cb_ctx->attr_name,
+ state->domain, state->period,
+ &state->refresh_values);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain DN list [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ for (state->refresh_val_size = 0;
+ state->refresh_values[state->refresh_val_size] != NULL;
+ state->refresh_val_size++);
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Refreshing %zu %s in domain %s\n",
+ state->refresh_val_size,
+ state->cb_ctx->name,
+ state->domain->name);
+
+ ret = be_refresh_batch_step(req, 0);
+ if (ret == EOK) {
+ state->index++;
+ continue;
+ } else if (ret != EAGAIN) {
+ goto done;
+ }
+ /* EAGAIN only, refreshing something.. */
+
+ state->index++;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+static errno_t be_refresh_batch_step(struct tevent_req *req,
+ uint32_t msec_delay)
+{
+ struct be_refresh_state *state = tevent_req_data(req, struct be_refresh_state);
+ struct timeval tv;
+ struct tevent_timer *timeout = NULL;
+
+ size_t remaining;
+ size_t batch_size;
+
+ memset(state->refresh_batch, 0, sizeof(char *) * state->batch_size);
+
+ if (state->refresh_index >= state->refresh_val_size) {
+ DEBUG(SSSDBG_FUNC_DATA, "The batch is done\n");
+ state->refresh_index = 0;
+ return EOK;
+ }
+
+ remaining = state->refresh_val_size - state->refresh_index;
+ batch_size = MIN(remaining, state->batch_size);
+ DEBUG(SSSDBG_FUNC_DATA,
+ "This batch will refresh %zu entries (so far %zu/%zu)\n",
+ batch_size, state->refresh_index, state->refresh_val_size);
+
+ for (size_t i = 0; i < batch_size; i++) {
+ state->refresh_batch[i] = state->refresh_values[state->refresh_index];
+ state->refresh_index++;
+ }
+
+ tv = tevent_timeval_current_ofs(0, msec_delay * 1000);
+ timeout = tevent_add_timer(state->be_ctx->ev, req, tv,
+ be_refresh_batch_step_wakeup, req);
+ if (timeout == NULL) {
+ return ENOMEM;
+ }
+
+ return EAGAIN;
+}
+
+static void be_refresh_batch_step_wakeup(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv,
+ void *pvt)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq = NULL;
+ struct be_refresh_state *state = NULL;
+
+ req = talloc_get_type(pvt, struct tevent_req);
+ state = tevent_req_data(req, struct be_refresh_state);
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Issuing refresh\n");
+ subreq = state->cb_ctx->cb.send_fn(state, state->ev, state->be_ctx,
+ state->domain,
+ state->refresh_batch,
+ state->cb_ctx->cb.pvt);
+ if (subreq == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, be_refresh_done, req);
+}
+
+static void be_refresh_done(struct tevent_req *subreq)
+{
+ struct be_refresh_state *state = NULL;
+ struct tevent_req *req = NULL;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct be_refresh_state);
+
+ ret = state->cb_ctx->cb.recv_fn(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = be_refresh_batch_step(req, 500);
+ if (ret == EAGAIN) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Another batch in this step in progress\n");
+ return;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "be_refresh_batch_step failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "All batches in this step refreshed\n");
+
+ /* Proceed to the next step */
+ ret = be_refresh_step(req);
+ if (ret == EAGAIN) {
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Another step in progress\n");
+ return;
+ }
+
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+errno_t be_refresh_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+struct dp_id_data *be_refresh_acct_req(TALLOC_CTX *mem_ctx,
+ uint32_t entry_type,
+ uint32_t filter_type,
+ struct sss_domain_info *domain)
+{
+ struct dp_id_data *account_req;
+
+ account_req = talloc_zero(mem_ctx, struct dp_id_data);
+ if (account_req == NULL) {
+ return NULL;
+ }
+
+ account_req->entry_type = entry_type;
+ account_req->filter_type = filter_type;
+ account_req->extra_value = NULL;
+ account_req->domain = domain->name;
+ return account_req;
+}