summaryrefslogtreecommitdiffstats
path: root/src/responder/common/responder_dp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/common/responder_dp.c')
-rw-r--r--src/responder/common/responder_dp.c590
1 files changed, 590 insertions, 0 deletions
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
new file mode 100644
index 000000000..782befb17
--- /dev/null
+++ b/src/responder/common/responder_dp.c
@@ -0,0 +1,590 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 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 <sys/time.h>
+#include <time.h>
+#include "util/util.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/responder.h"
+#include "providers/data_provider.h"
+#include "sbus/sbus_client.h"
+
+hash_table_t *dp_requests = NULL;
+
+struct sss_dp_req;
+
+struct sss_dp_callback {
+ struct sss_dp_callback *prev;
+ struct sss_dp_callback *next;
+
+ struct sss_dp_req *sdp_req;
+
+ sss_dp_callback_t callback;
+ void *callback_ctx;
+};
+
+struct sss_dp_req {
+ struct tevent_context *ev;
+ DBusPendingCall *pending_reply;
+
+ char *key;
+
+ struct tevent_timer *tev;
+ struct sss_dp_callback *cb_list;
+
+ dbus_uint16_t err_maj;
+ dbus_uint32_t err_min;
+ char *err_msg;
+};
+
+static int sss_dp_callback_destructor(void *ptr)
+{
+ struct sss_dp_callback *cb = talloc_get_type(ptr, struct sss_dp_callback);
+
+ DLIST_REMOVE(cb->sdp_req->cb_list, cb);
+
+ return EOK;
+}
+
+static int sss_dp_req_destructor(void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+ struct sss_dp_callback *cb, *next;
+ hash_key_t key;
+
+ /* Cancel Dbus pending reply if still pending */
+ if (sdp_req->pending_reply) {
+ dbus_pending_call_cancel(sdp_req->pending_reply);
+ sdp_req->pending_reply = NULL;
+ }
+
+ /* Destroy the hash entry */
+ key.type = HASH_KEY_STRING;
+ key.str = sdp_req->key;
+ int hret = hash_delete(dp_requests, &key);
+ if (hret != HASH_SUCCESS) {
+ /* This should never happen */
+ DEBUG(0, ("Could not clear entry from request queue\n"));
+ }
+
+ /* Free any remaining callback */
+ if (sdp_req->err_maj == DP_ERR_OK) {
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = EIO;
+ sdp_req->err_msg = discard_const_p(char, "Internal Error");
+ }
+
+ cb = sdp_req->cb_list;
+ while (cb) {
+ cb->callback(sdp_req->err_maj,
+ sdp_req->err_min,
+ sdp_req->err_msg,
+ cb->callback_ctx);
+ next = cb->next;
+ talloc_free(cb);
+ cb = next;
+ }
+
+ return 0;
+}
+
+static void sdp_req_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = ETIMEDOUT;
+ sdp_req->err_msg = discard_const_p(char, "Timed out");
+
+ /* steal te on NULL because it will be freed as soon as the handler
+ * returns. Causing a double free if we don't, as te is allocated on
+ * sdp_req and we are just going to free it */
+ talloc_steal(NULL, te);
+
+ talloc_free(sdp_req);
+}
+
+static int sss_dp_get_reply(DBusPendingCall *pending,
+ dbus_uint16_t *err_maj,
+ dbus_uint32_t *err_min,
+ char **err_msg);
+
+static void sss_dp_invoke_callback(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+ struct sss_dp_callback *cb;
+ struct timeval tv;
+ struct tevent_timer *tev;
+
+ cb = sdp_req->cb_list;
+ /* Remove the callback from the list, the caller may free it, within the
+ * callback. */
+ talloc_set_destructor((TALLOC_CTX *)cb, NULL);
+ DLIST_REMOVE(sdp_req->cb_list, cb);
+
+ cb->callback(sdp_req->err_maj,
+ sdp_req->err_min,
+ sdp_req->err_msg,
+ cb->callback_ctx);
+
+ /* Call the next callback if needed */
+ if (sdp_req->cb_list != NULL) {
+ tv = tevent_timeval_current();
+ tev = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sss_dp_invoke_callback, sdp_req);
+ if (!te) {
+ /* Out of memory or other serious error */
+ goto done;
+ }
+
+ return;
+ }
+
+ /* No more callbacks to invoke. Destroy the request */
+done:
+ /* steal te on NULL because it will be freed as soon as the handler
+ * returns. Causing a double free if we don't, as te is allocated on
+ * sdp_req and we are just going to free it */
+ talloc_steal(NULL, te);
+
+ talloc_zfree(sdp_req);
+}
+
+static void sss_dp_send_acct_callback(DBusPendingCall *pending, void *ptr)
+{
+ int ret;
+ struct sss_dp_req *sdp_req;
+ struct sss_dp_callback *cb;
+ struct timeval tv;
+ struct tevent_timer *te;
+
+ sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+
+ /* prevent trying to cancel a reply that we already received */
+ sdp_req->pending_reply = NULL;
+
+ ret = sss_dp_get_reply(pending,
+ &sdp_req->err_maj,
+ &sdp_req->err_min,
+ &sdp_req->err_msg);
+ if (ret != EOK) {
+ if (ret == ETIME) {
+ sdp_req->err_maj = DP_ERR_TIMEOUT;
+ sdp_req->err_min = ret;
+ sdp_req->err_msg = talloc_strdup(sdp_req, "Request timed out");
+ }
+ else {
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = ret;
+ sdp_req->err_msg =
+ talloc_strdup(sdp_req,
+ "Failed to get reply from Data Provider");
+ }
+ }
+
+ /* Check whether we need to issue any callbacks */
+ cb = sdp_req->cb_list;
+ if (sdp_req->cb_list == NULL) {
+ if (cb == NULL) {
+ /* No callbacks to invoke. Destroy the hash entry */
+ talloc_zfree(sdp_req);
+ return;
+ }
+ }
+
+ /* Queue up all callbacks */
+ tv = tevent_timeval_current();
+ te = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sss_dp_invoke_callback, sdp_req);
+ if (!te) {
+ /* Out of memory or other serious error */
+ goto error;
+ }
+
+ return;
+
+error:
+ talloc_zfree(sdp_req);
+}
+
+static int sss_dp_send_acct_req_create(struct resp_ctx *rctx,
+ TALLOC_CTX *callback_memctx,
+ const char *domain,
+ uint32_t be_type,
+ char *filter,
+ int timeout,
+ sss_dp_callback_t callback,
+ void *callback_ctx,
+ struct sss_dp_req **ndp);
+
+int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx,
+ sss_dp_callback_t callback, void *callback_ctx,
+ int timeout, const char *domain,
+ bool fast_reply, int type,
+ const char *opt_name, uint32_t opt_id)
+{
+ int ret, hret;
+ uint32_t be_type;
+ char *filter;
+ hash_key_t key;
+ hash_value_t value;
+ TALLOC_CTX *tmp_ctx;
+ struct timeval tv;
+ struct sss_dp_req *sdp_req = NULL;
+ struct sss_dp_callback *cb;
+
+ /* either, or, not both */
+ if (opt_name && opt_id) {
+ return EINVAL;
+ }
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ switch (type) {
+ case SSS_DP_USER:
+ be_type = BE_REQ_USER;
+ break;
+ case SSS_DP_GROUP:
+ be_type = BE_REQ_GROUP;
+ break;
+ case SSS_DP_INITGROUPS:
+ be_type = BE_REQ_INITGROUPS;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (fast_reply) {
+ be_type |= BE_REQ_FAST;
+ }
+
+ if (dp_requests == NULL) {
+ /* Create a hash table to handle queued update requests */
+ ret = hash_create(10, &dp_requests, NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ fprintf(stderr, "cannot create hash table (%s)\n", hash_error_string(ret));
+ return EIO;
+ }
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = NULL;
+
+ if (opt_name) {
+ filter = talloc_asprintf(tmp_ctx, "name=%s", opt_name);
+ key.str = talloc_asprintf(tmp_ctx, "%d%s@%s", type, opt_name, domain);
+ } else if (opt_id) {
+ filter = talloc_asprintf(tmp_ctx, "idnumber=%u", opt_id);
+ key.str = talloc_asprintf(tmp_ctx, "%d%d@%s", type, opt_id, domain);
+ } else {
+ filter = talloc_strdup(tmp_ctx, "name=*");
+ key.str = talloc_asprintf(tmp_ctx, "%d*@%s", type, domain);
+ }
+ if (!filter || !key.str) {
+ talloc_zfree(tmp_ctx);
+ return ENOMEM;
+ }
+
+ /* Check whether there's already a request in progress */
+ hret = hash_lookup(dp_requests, &key, &value);
+ switch (hret) {
+ case HASH_SUCCESS:
+ /* Request already in progress
+ * Add an additional callback if needed and return
+ */
+ DEBUG(2, ("Identical request in progress\n"));
+
+ if (callback) {
+ /* We have a new request asking for a callback */
+ sdp_req = talloc_get_type(value.ptr, struct sss_dp_req);
+ if (!sdp_req) {
+ DEBUG(0, ("Could not retrieve DP request context\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ cb = talloc_zero(callback_memctx, struct sss_dp_callback);
+ if (!cb) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cb->callback = callback;
+ cb->callback_ctx = callback_ctx;
+ cb->sdp_req = sdp_req;
+
+ DLIST_ADD_END(sdp_req->cb_list, cb, struct sss_dp_callback *);
+ talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor);
+ }
+
+ ret = EOK;
+ break;
+
+ case HASH_ERROR_KEY_NOT_FOUND:
+ /* No such request in progress
+ * Create a new request
+ */
+ ret = sss_dp_send_acct_req_create(rctx, callback_memctx, domain,
+ be_type, filter, timeout,
+ callback, callback_ctx,
+ &sdp_req);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ value.type = HASH_VALUE_PTR;
+ value.ptr = sdp_req;
+ hret = hash_enter(dp_requests, &key, &value);
+ if (hret != HASH_SUCCESS) {
+ DEBUG(0, ("Could not store request query (%s)",
+ hash_error_string(hret)));
+ talloc_zfree(sdp_req);
+ ret = EIO;
+ goto done;
+ }
+
+ sdp_req->key = talloc_strdup(sdp_req, key.str);
+
+ tv = tevent_timeval_current_ofs(timeout, 0);
+ sdp_req->tev = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sdp_req_timeout, sdp_req);
+ if (!sdp_req->tev) {
+ DEBUG(0, ("Out of Memory!?"));
+ talloc_zfree(sdp_req);
+ ret = ENOMEM;
+ goto done;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *)sdp_req, sss_dp_req_destructor);
+
+ ret = EOK;
+ break;
+
+ default:
+ DEBUG(0,("Could not query request list (%s)\n",
+ hash_error_string(hret)));
+ talloc_zfree(sdp_req);
+ ret = EIO;
+ }
+
+done:
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
+static int sss_dp_send_acct_req_create(struct resp_ctx *rctx,
+ TALLOC_CTX *callback_memctx,
+ const char *domain,
+ uint32_t be_type,
+ char *filter,
+ int timeout,
+ sss_dp_callback_t callback,
+ void *callback_ctx,
+ struct sss_dp_req **ndp)
+{
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ DBusPendingCall *pending_reply;
+ dbus_bool_t dbret;
+ struct sss_dp_callback *cb;
+ struct sss_dp_req *sdp_req;
+ uint32_t attrs = BE_ATTR_CORE;
+ struct be_conn *be_conn;
+ int ret;
+
+ /* double check dp_ctx has actually been initialized.
+ * in some pathological cases it may happen that nss starts up before
+ * dp connection code is actually able to establish a connection.
+ */
+ ret = sss_dp_get_domain_conn(rctx, domain, &be_conn);
+ if (ret != EOK) {
+ DEBUG(1, ("The Data Provider connection for %s is not available!"
+ " This maybe a bug, it shouldn't happen!\n", domain));
+ return EIO;
+ }
+ dbus_conn = sbus_get_connection(be_conn->conn);
+
+ /* create the message */
+ msg = dbus_message_new_method_call(NULL,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_GETACCTINFO);
+ if (msg == NULL) {
+ DEBUG(0,("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ DEBUG(4, ("Sending request for [%s][%u][%d][%s]\n",
+ domain, be_type, attrs, filter));
+
+ dbret = dbus_message_append_args(msg,
+ DBUS_TYPE_UINT32, &be_type,
+ DBUS_TYPE_UINT32, &attrs,
+ DBUS_TYPE_STRING, &filter,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1,("Failed to build message\n"));
+ return EIO;
+ }
+
+ dbret = dbus_connection_send_with_reply(dbus_conn, msg,
+ &pending_reply, timeout);
+ if (!dbret || pending_reply == NULL) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ sdp_req = talloc_zero(rctx, struct sss_dp_req);
+ if (!sdp_req) {
+ dbus_message_unref(msg);
+ return ENOMEM;
+ }
+ sdp_req->ev = rctx->ev;
+ sdp_req->pending_reply = pending_reply;
+
+ if (callback) {
+ cb = talloc_zero(callback_memctx, struct sss_dp_callback);
+ if (!cb) {
+ dbus_message_unref(msg);
+ talloc_zfree(sdp_req);
+ return ENOMEM;
+ }
+ cb->callback = callback;
+ cb->callback_ctx = callback_ctx;
+ cb->sdp_req = sdp_req;
+
+ DLIST_ADD(sdp_req->cb_list, cb);
+ talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor);
+ }
+
+ /* Set up the reply handler */
+ dbret = dbus_pending_call_set_notify(pending_reply,
+ sss_dp_send_acct_callback,
+ sdp_req, NULL);
+ if (!dbret) {
+ DEBUG(0, ("Could not queue up pending request!"));
+ talloc_zfree(sdp_req);
+ dbus_pending_call_cancel(pending_reply);
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ dbus_message_unref(msg);
+
+ *ndp = sdp_req;
+
+ return EOK;
+}
+
+static int sss_dp_get_reply(DBusPendingCall *pending,
+ dbus_uint16_t *err_maj,
+ dbus_uint32_t *err_min,
+ char **err_msg)
+{
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t ret;
+ int type;
+ int err = EOK;
+
+ dbus_error_init(&dbus_error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n"));
+
+ /* FIXME: Destroy this connection ? */
+ err = EIO;
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dbus_message_get_args(reply, &dbus_error,
+ DBUS_TYPE_UINT16, err_maj,
+ DBUS_TYPE_UINT32, err_min,
+ DBUS_TYPE_STRING, err_msg,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1,("Failed to parse message\n"));
+ /* FIXME: Destroy this connection ? */
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ err = EIO;
+ goto done;
+ }
+
+ DEBUG(4, ("Got reply (%u, %u, %s) from Data Provider\n",
+ (unsigned int)*err_maj, (unsigned int)*err_min, *err_msg));
+
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+ if (strcmp(dbus_message_get_error_name(reply),
+ DBUS_ERROR_NO_REPLY) == 0) {
+ err = ETIME;
+ goto done;
+ }
+ DEBUG(0,("The Data Provider returned an error [%s]\n",
+ dbus_message_get_error_name(reply)));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+
+ /* FIXME: Destroy this connection ? */
+ err = EIO;
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+
+ return err;
+}
+