/* Authors: Simo Sorce Stephen Gallagher 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 . */ #include #include #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) { next = cb->next; /* It is the responsibility of the callback to free cb */ cb->callback(sdp_req->err_maj, sdp_req->err_min, sdp_req->err_msg, cb->callback_ctx); cb = next; } return 0; } void handle_requests_after_reconnect(void) { int ret; hash_value_t *values; unsigned long count, i; struct sss_dp_req *sdp_req; if (!dp_requests) { DEBUG(7, ("No requests to handle after reconnect\n")); return; } ret = hash_values(dp_requests, &count, &values); if (ret != HASH_SUCCESS) { DEBUG(1, ("hash_values failed, " "not all request might be handled after reconnect.\n")); return; } DEBUG(7, ("Will handle %lu requests after reconnect\n", count)); for (i=0; ierr_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 (!tev) { /* 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; case SSS_DP_NETGR: be_type = BE_REQ_NETGROUP; 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, ENUM_INDICATOR); 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)\n", 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!?\n")); 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) { 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; } /* 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; } sdp_req = talloc_zero(rctx, struct sss_dp_req); if (!sdp_req) { dbus_message_unref(msg); return ENOMEM; } ret = sbus_conn_send(be_conn->conn, msg, timeout, sss_dp_send_acct_callback, sdp_req, &pending_reply); dbus_message_unref(msg); if (ret != EOK) { /* * Critical Failure * We can't communicate on this connection * We'll drop it using the default destructor. */ DEBUG(0, ("D-BUS send failed.\n")); return EIO; } sdp_req->ev = rctx->ev; sdp_req->pending_reply = pending_reply; if (callback) { cb = talloc_zero(callback_memctx, struct sss_dp_callback); if (!cb) { 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); } *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; }