summaryrefslogtreecommitdiffstats
path: root/src/providers/data_provider/dp_request.c
diff options
context:
space:
mode:
authorPavel Březina <pbrezina@redhat.com>2016-01-15 13:00:45 +0100
committerJakub Hrozek <jhrozek@redhat.com>2016-06-20 14:48:47 +0200
commitd3dee2a07f1a8ee9ae6f94e149ced754ef76c248 (patch)
treedcb92cf97dd70a4183d05258b9db0414b91d60a8 /src/providers/data_provider/dp_request.c
parent565b9955cc439ade58cc24a98168060a60f33e7a (diff)
downloadsssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.gz
sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.xz
sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.zip
DP: Introduce new interface for backend
Terminology: * Backend: Implemenation of domain * Data Provider: interface between backend and responders * Module: ldap/ipa/ad/... dlopened library that implements dp interface * Target: id/autofs/sudo/... functionality of module Benefits over current code: * data provider is a black box completely separated from backend * method handlers are just simple tevent requests on backend side * no need of spy on be_client * simplified and error proof adding of new responders * simplified adding of new methods * reply to D-Bus message is completely handled by DP code * each target can have several methods defined * properties can be added on objects * each method can have output parameters * modules now support constructor * improved debugging * clear memory hierarchy * ability to chain requests * type safe private data Reviewed-by: Sumit Bose <sbose@redhat.com> Reviewed-by: Jakub Hrozek <jhrozek@redhat.com> Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
Diffstat (limited to 'src/providers/data_provider/dp_request.c')
-rw-r--r--src/providers/data_provider/dp_request.c460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/providers/data_provider/dp_request.c b/src/providers/data_provider/dp_request.c
new file mode 100644
index 000000000..6c0a0b72d
--- /dev/null
+++ b/src/providers/data_provider/dp_request.c
@@ -0,0 +1,460 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ 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 <talloc.h>
+#include <tevent.h>
+#include <dbus/dbus.h>
+
+#include "sbus/sssd_dbus_errors.h"
+#include "providers/data_provider/dp_private.h"
+#include "providers/backend.h"
+#include "util/dlinklist.h"
+#include "util/util.h"
+
+struct dp_req {
+ struct data_provider *provider;
+ struct dp_client *client;
+ uint32_t dp_flags;
+
+ struct sss_domain_info *domain;
+
+ enum dp_targets target;
+ enum dp_methods method;
+ struct dp_method *execute;
+ const char *name;
+ uint32_t num;
+
+ struct tevent_req *req;
+ struct tevent_req *handler_req;
+ void *request_data;
+
+ /* Active request list. */
+ struct dp_req *prev;
+ struct dp_req *next;
+};
+
+static bool check_data_type(const char *expected,
+ const char *description,
+ void *ptr)
+{
+ void *tmp;
+
+ /* If ptr is NULL we still return true since it is valid case. */
+ tmp = talloc_check_name(ptr, expected);
+ if (tmp != NULL || ptr == NULL) {
+ return true;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid %s data type provided. Expected [%s], "
+ "got [%s].\n", description, expected, talloc_get_name(ptr));
+
+ return false;
+}
+
+static bool check_method_data(struct dp_method *method,
+ void *request_data)
+{
+ if (!check_data_type(method->method_dtype, "method", method->method_data)) {
+ return false;
+ }
+
+ if (!check_data_type(method->request_dtype, "request", request_data)) {
+ return false;
+ }
+
+ return true;
+}
+
+static int dp_req_destructor(struct dp_req *dp_req)
+{
+ DLIST_REMOVE(dp_req->provider->requests.active, dp_req);
+
+ if (dp_req->provider->requests.num_active == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: there are no active requests!\n");
+ return 0;
+ }
+
+ dp_req->provider->requests.num_active--;
+
+ DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, "Request removed.");
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n",
+ dp_req->provider->requests.num_active);
+
+ return 0;
+}
+
+static errno_t dp_attach_req(struct dp_req *dp_req,
+ struct data_provider *provider,
+ const char *name,
+ uint32_t dp_flags)
+{
+ /* If we run out of numbers we simply overflow. */
+ dp_req->num = provider->requests.index++;
+ dp_req->name = talloc_asprintf(dp_req, "%s #%u", name, dp_req->num);
+ if (dp_req->name == NULL) {
+ return ENOMEM;
+ }
+
+ /* Attach this request to active request list. */
+ DLIST_ADD(provider->requests.active, dp_req);
+ provider->requests.num_active++;
+
+ talloc_set_destructor(dp_req, dp_req_destructor);
+
+ DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name,
+ "New request. Flags [%#.4x].", dp_flags);
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n",
+ provider->requests.num_active);
+
+ return EOK;
+}
+
+static errno_t
+dp_req_new(TALLOC_CTX *mem_ctx,
+ struct data_provider *provider,
+ struct dp_client *dp_cli,
+ const char *domainname,
+ const char *name,
+ enum dp_targets target,
+ enum dp_methods method,
+ uint32_t dp_flags,
+ void *request_data,
+ struct tevent_req *req,
+ struct dp_req **_dp_req)
+{
+ struct dp_req *dp_req;
+ struct be_ctx *be_ctx;
+ errno_t ret;
+
+ /* We set output even for error to simplify code flow in the caller. */
+ *_dp_req = NULL;
+
+ dp_req = talloc_zero(mem_ctx, struct dp_req);
+ if (dp_req == NULL) {
+ return ENOMEM;
+ }
+
+ dp_req->provider = provider;
+ dp_req->client = dp_cli;
+ dp_req->dp_flags = dp_flags;
+ dp_req->target = target;
+ dp_req->method = method;
+ dp_req->request_data = request_data;
+ dp_req->req = req;
+
+ ret = dp_attach_req(dp_req, provider, name, dp_flags);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP request "
+ "[%s] [%d]: %s\n", name, ret, sss_strerror(ret));
+ talloc_free(dp_req);
+ return ret;
+ }
+
+ /* Now the request is created. We will return it even in case of error
+ * so we can get better debug messages. */
+
+ talloc_steal(dp_req, dp_req->request_data);
+ *_dp_req = dp_req;
+
+ be_ctx = provider->be_ctx;
+ dp_req->domain = be_ctx->domain;
+ if (domainname != NULL) {
+ dp_req->domain = find_domain_by_name(be_ctx->domain, domainname, true);
+ if (dp_req->domain == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain: %s\n", domainname);
+ return ERR_DOMAIN_NOT_FOUND;
+ }
+ }
+
+ ret = dp_find_method(provider, target, method, &dp_req->execute);
+
+ return ret;
+}
+
+static errno_t
+file_dp_request(TALLOC_CTX *mem_ctx,
+ struct data_provider *provider,
+ struct dp_client *dp_cli,
+ const char *domainname,
+ const char *name,
+ enum dp_targets target,
+ enum dp_methods method,
+ uint32_t dp_flags,
+ void *request_data,
+ struct tevent_req *req,
+ struct dp_req **_dp_req)
+{
+ struct dp_req_params *dp_params;
+ dp_req_send_fn send_fn;
+ struct dp_req *dp_req;
+ struct be_ctx *be_ctx;
+ errno_t ret;
+
+ be_ctx = provider->be_ctx;
+
+ ret = dp_req_new(mem_ctx, provider, dp_cli, domainname, name, target,
+ method, dp_flags, request_data, req, &dp_req);
+ if (ret != EOK) {
+ *_dp_req = dp_req;
+ goto done;
+ }
+
+ /* DP request is already created. We will always return it to get nice
+ * debug messages. */
+ *_dp_req = dp_req;
+
+ /* Check that provided data are of correct type. */
+
+ if (!check_method_data(dp_req->execute, dp_req->request_data)) {
+ ret = ERR_INVALID_DATA_TYPE;
+ goto done;
+ }
+
+ /* Process data provider flags */
+
+ if (dp_flags & DP_FAST_REPLY && be_is_offline(be_ctx)) {
+ ret = ERR_OFFLINE;
+ goto done;
+ }
+
+ /* File request */
+
+ dp_params = talloc_zero(dp_req, struct dp_req_params);
+ if (dp_params == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ dp_params->ev = provider->ev;
+ dp_params->be_ctx = be_ctx;
+ dp_params->domain = dp_req->domain;
+ dp_params->target = dp_req->target;
+ dp_params->method = dp_req->method;
+
+ send_fn = dp_req->execute->send_fn;
+ dp_req->handler_req = send_fn(dp_req, dp_req->execute->method_data,
+ dp_req->request_data, dp_params);
+ if (dp_req->handler_req == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_dp_req = dp_req;
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+struct dp_req_state {
+ struct dp_req *dp_req;
+ dp_req_recv_fn recv_fn;
+ void *output_data;
+};
+
+static void dp_req_done(struct tevent_req *subreq);
+
+struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx,
+ struct data_provider *provider,
+ struct dp_client *dp_cli,
+ const char *domain,
+ const char *name,
+ enum dp_targets target,
+ enum dp_methods method,
+ uint32_t dp_flags,
+ void *request_data,
+ const char **_request_name)
+{
+ struct dp_req_state *state;
+ const char *request_name;
+ struct tevent_req *req;
+ struct dp_req *dp_req;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct dp_req_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ ret = file_dp_request(state, provider, dp_cli, domain, name, target,
+ method, dp_flags, request_data, req, &dp_req);
+
+ if (dp_req == NULL) {
+ /* An error ocurred before request could be created. */
+ if (_request_name != NULL) {
+ *_request_name = "Request Not Yet Created";
+ }
+
+ goto immediately;
+ }
+
+ state->dp_req = dp_req;
+ if (_request_name != NULL) {
+ request_name = talloc_strdup(mem_ctx, dp_req->name);
+ if (request_name == NULL) {
+ *_request_name = "Request Not Yet Created";
+ ret = ENOMEM;
+ goto immediately;
+ }
+ *_request_name = request_name;
+ }
+
+ if (ret != EOK) {
+ goto immediately;
+ }
+
+ state->recv_fn = dp_req->execute->recv_fn;
+ state->output_data = talloc_zero_size(state, dp_req->execute->output_size);
+ if (state->output_data == NULL) {
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ talloc_set_name_const(state->output_data, dp_req->execute->output_dtype);
+
+ tevent_req_set_callback(dp_req->handler_req, dp_req_done, req);
+
+ return req;
+
+immediately:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, provider->ev);
+
+ return req;
+}
+
+static void dp_req_done(struct tevent_req *subreq)
+{
+ struct dp_req_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct dp_req_state);
+
+ ret = state->recv_fn(state->output_data, subreq, state->output_data);
+
+ /* subreq is the same as dp_req->handler_req */
+ talloc_zfree(subreq);
+ state->dp_req->handler_req = NULL;
+
+ DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name,
+ "Request handler finished [%d]: %s", ret, sss_strerror(ret));
+
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+errno_t _dp_req_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ const char *output_dtype,
+ void **_output_data)
+{
+ struct dp_req_state *state;
+
+ state = tevent_req_data(req, struct dp_req_state);
+
+ if (state->dp_req != NULL) {
+ DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name,
+ "Receiving request data.");
+ } else {
+ /* dp_req may be NULL in case we error when filing request */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Receiving data of prematurely interrupted request!\n");
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (!check_data_type(output_dtype, "output", state->output_data)) {
+ return ERR_INVALID_DATA_TYPE;
+ }
+
+ *_output_data = talloc_steal(mem_ctx, state->output_data);
+
+ return EOK;
+}
+
+static void dp_terminate_request(struct dp_req *dp_req)
+{
+ if (dp_req->handler_req == NULL) {
+ /* This may occur when the handler already finished but the caller
+ * of dp request did not yet recieved data/free dp_req. We just
+ * return here. */
+ return;
+ }
+
+ /* We will end the handler request and mark dp request as terminated. */
+
+ DP_REQ_DEBUG(SSSDBG_TRACE_ALL, dp_req->name, "Terminating.");
+
+ talloc_zfree(dp_req->handler_req);
+ tevent_req_error(dp_req->req, ERR_TERMINATED);
+}
+
+static void dp_terminate_request_list(struct data_provider *provider,
+ const char *domain)
+{
+ struct dp_req *next;
+ struct dp_req *cur;
+
+ if (provider == NULL || provider->requests.active == NULL) {
+ return;
+ }
+
+ for (cur = provider->requests.active; cur != NULL; cur = next) {
+ next = cur->next;
+ if (domain == NULL || strcmp(cur->domain->name, domain) == 0) {
+ dp_terminate_request(cur);
+ }
+ }
+}
+
+void dp_terminate_active_requests(struct data_provider *provider)
+{
+ DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests\n");
+
+ dp_terminate_request_list(provider, NULL);
+}
+
+void dp_terminate_domain_requests(struct data_provider *provider,
+ const char *domain)
+{
+ DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests "
+ "for domain [%s]\n", domain);
+
+ if (domain == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: domain is NULL!\n");
+ return;
+ }
+
+ dp_terminate_request_list(provider, domain);
+}