/* Authors: Pavel Březina 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 . */ #include #include #include #include #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; }