/* Authors: Pavel Březina Copyright (C) 2017 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 "util/util.h" #include "util/dlinklist.h" #include "util/sss_chain_id.h" #include "sbus/sbus_request.h" #include "sbus/sbus_private.h" typedef errno_t (*sbus_request_messages_fn)(struct tevent_req *req, TALLOC_CTX **_reply_mem_ctx, DBusMessage **_client_message, DBusMessage ***_reply); struct sbus_active_requests * sbus_active_requests_init(TALLOC_CTX *mem_ctx) { struct sbus_active_requests *requests; requests = talloc_zero(mem_ctx, struct sbus_active_requests); if (requests == NULL) { return NULL; } requests->incoming = sbus_requests_init(requests); if (requests->incoming == NULL) { goto fail; } requests->outgoing = sbus_requests_init(requests); if (requests->outgoing == NULL) { goto fail; } return requests; fail: talloc_free(requests); return NULL; } struct sbus_request * sbus_request_create(TALLOC_CTX *mem_ctx, struct sbus_connection *conn, enum sbus_request_type type, const char *destination, const char *interface, const char *member, const char *path) { struct sbus_request *request; request = talloc_zero(mem_ctx, struct sbus_request); if (request == NULL) { return NULL; } request->conn = conn; request->type = type; request->sender = NULL; request->destination = talloc_strdup(request, destination); if (destination != NULL && request->destination == NULL) { goto fail; } request->interface = talloc_strdup(request, interface); if (request->interface == NULL) { goto fail; } request->member = talloc_strdup(request, member); if (request->member == NULL) { goto fail; } request->path = talloc_strdup(request, path); if (request->path == NULL) { goto fail; } return request; fail: talloc_free(request); return NULL; } static errno_t sbus_request_prepare_reply(TALLOC_CTX *mem_ctx, enum sbus_request_type type, DBusMessage *msg, DBusMessage **_reply, DBusMessageIter **_reply_iterator) { DBusMessageIter *iterator; DBusMessage *reply; errno_t ret; if (type == SBUS_REQUEST_SIGNAL) { /* Signals don't send reply*/ *_reply = NULL; *_reply_iterator = NULL; return EOK; } iterator = talloc_zero(mem_ctx, DBusMessageIter); if (iterator == NULL) { return ENOMEM; } reply = dbus_message_new_method_return(msg); if (reply == NULL) { talloc_free(iterator); return ENOMEM; } ret = sbus_message_bound(mem_ctx, reply); if (ret != EOK) { talloc_free(iterator); dbus_message_unref(reply); return ret; } dbus_message_iter_init_append(reply, iterator); *_reply = reply; *_reply_iterator = iterator; return EOK; } static errno_t sbus_request_switch_reply(DBusMessage *reply, struct tevent_req *req, sbus_request_messages_fn messages_fn) { DBusMessage *client_message; DBusMessage **reply_pointer; TALLOC_CTX *reply_mem_ctx; const char *sender; dbus_uint32_t serial; dbus_bool_t dbret; errno_t ret; ret = messages_fn(req, &reply_mem_ctx, &client_message, &reply_pointer); if (ret != EOK) { return ret; } /* Copy reply to location in a state of this request. */ *reply_pointer = dbus_message_copy(reply); if (*reply_pointer == NULL) { return ENOMEM; } ret = sbus_message_bound(reply_mem_ctx, *reply_pointer); if (ret != EOK) { goto done; } if (client_message == NULL) { ret = EOK; goto done; } /* We set destination and serial in reply to point to the original * client request. */ sender = dbus_message_get_sender(client_message); serial = dbus_message_get_serial(client_message); dbret = dbus_message_set_destination(*reply_pointer, sender); if (dbret == false) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set reply sender!\n"); ret = EIO; goto done; } dbret = dbus_message_set_reply_serial(*reply_pointer, serial); if (dbret == false) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set reply serial!\n"); ret = EIO; goto done; } ret = EOK; done: if (ret != EOK) { dbus_message_unref(*reply_pointer); *reply_pointer = NULL; } return ret; } static void sbus_request_notify_error(hash_table_t *table, const char *key, struct tevent_req *req, errno_t error) { struct sbus_request_list *mainreq = NULL; struct sbus_request_list *list; struct sbus_request_list *item; list = sbus_requests_lookup(table, key); if (list == NULL) { /* This was the only request with no key generator available. */ tevent_req_error(req, error); return; } /* First notify all chained D-Bus requests. */ DLIST_FOR_EACH(item, list) { /* Remember the main request. */ if (item->req == req) { mainreq = item; continue; } /* We don't want to notify current, invalid or non D-Bus request. */ if (!item->is_dbus || item->is_invalid) { continue; } sbus_requests_finish(item, error); } /* Now we finish the main request. */ sbus_requests_finish(mainreq, error); /* And as last, we notify all await requests. */ DLIST_FOR_EACH(item, list) { if (item->is_dbus) { continue; } sbus_requests_finish(item, error); } sbus_requests_delete(list); } static void sbus_request_notify_success(hash_table_t *table, const char *key, struct tevent_req *req, sbus_request_messages_fn messages_fn, DBusMessage *reply) { struct sbus_request_list *mainreq = NULL; struct sbus_request_list *list; struct sbus_request_list *item; errno_t ret; list = sbus_requests_lookup(table, key); if (list == NULL) { /* This was the only request with no key generator available. */ tevent_req_done(req); return; } /* First notify all chained D-Bus requests so we can steal the reply. */ DLIST_FOR_EACH(item, list) { /* Remember the main request. */ if (item->req == req) { mainreq = item; continue; } /* We don't want to notify current, invalid or non D-Bus request. */ if (!item->is_dbus || item->is_invalid) { continue; } ret = sbus_request_switch_reply(reply, item->req, messages_fn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to switch reply for %p, " "terminating this request!\n", item->req); sbus_requests_finish(item, ret); continue; } sbus_requests_finish(item, EOK); } /* Now we finish the main request. */ sbus_requests_finish(mainreq, EOK); /* And as last, we notify all await requests. */ DLIST_FOR_EACH(item, list) { if (item->is_dbus) { continue; } sbus_requests_finish(item, EOK); } sbus_requests_delete(list); } struct sbus_incoming_request_state { struct tevent_context *ev; const struct sbus_invoker *invoker; const struct sbus_handler *handler; struct sbus_connection *conn; struct sbus_request *request; DBusMessageIter *read_iter; DBusMessage *reply; DBusMessage *msg; const char *key; }; static errno_t sbus_request_messages(struct tevent_req *req, TALLOC_CTX **_reply_mem_ctx, DBusMessage **_client_message, DBusMessage ***_reply) { struct sbus_incoming_request_state *state; state = tevent_req_data(req, struct sbus_incoming_request_state); if (state == NULL) { return ERR_INTERNAL; } *_reply_mem_ctx = state; *_client_message = state->msg; *_reply = &state->reply; return EOK; } static void sbus_incoming_request_sender_done(struct tevent_req *subreq); static void sbus_incoming_request_done(struct tevent_req *subreq); struct tevent_req * sbus_incoming_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sbus_connection *conn, struct sbus_request *request, const struct sbus_invoker *invoker, const struct sbus_handler *handler, const char *sender_name, DBusMessageIter *read_iter, DBusMessage *msg) { struct sbus_incoming_request_state *state; struct tevent_req *subreq; struct tevent_req *req; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sbus_incoming_request_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); return NULL; } if (invoker->issue == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "There is no invoker set!\n"); ret = ERR_INTERNAL; goto done; } switch (handler->type) { case SBUS_HANDLER_SYNC: if (handler->sync == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "There is no handler set!\n"); ret = ERR_INTERNAL; goto done; } break; case SBUS_HANDLER_ASYNC: if (handler->async_send == NULL || handler->async_recv == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "There is no handler set!\n"); ret = ERR_INTERNAL; goto done; } break; } state->ev = ev; state->msg = msg; state->conn = conn; state->request = request; state->invoker = invoker; state->handler = handler; state->read_iter = read_iter; state->reply = NULL; subreq = sbus_sender_resolve_send(mem_ctx, ev, conn, request->type, request->destination, request->path, request->interface, request->member, sender_name); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sbus_incoming_request_sender_done, req); ret = EAGAIN; done: if (ret != EAGAIN) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; } static void sbus_incoming_request_sender_done(struct tevent_req *subreq) { struct sbus_incoming_request_state *state; DBusMessageIter *write_iter = NULL; struct sbus_sender *sender; struct tevent_req *req; bool key_exists; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sbus_incoming_request_state); ret = sbus_sender_resolve_recv(state, subreq, &sender); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } state->request->sender = talloc_steal(state->request, sender); ret = sbus_check_access(state->conn, state->request); if (ret != EOK) { goto done; } ret = sbus_request_prepare_reply(state, state->request->type, state->msg, &state->reply, &write_iter); if (ret != EOK) { goto done; } /** * Invoke a read invoker. This function will read method arguments * from message. Then it will use these arguments to create a key * describing this method and at last, it will schedule method handler * to be issued in next loop. */ subreq = state->invoker->issue(state, state->ev, state->request, state->invoker->keygen, state->handler, state->read_iter, write_iter, &state->key); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); tevent_req_error(req, ENOMEM); return; } /** * Now when we have the key, we will search table to see if the same * request is not already in progress. If it is, we cancel this one * and register ourselves for notification when it is finished. * * Otherwise we add ourselves as the first request of this type and * set a tevent callback that is triggered when the method handler is done. */ ret = sbus_requests_add(state->conn->requests->incoming, state->key, state->conn, req, true, &key_exists); if (ret != EOK || key_exists) { /* Cancel the sub request. Since there was either an error or the * sub request was chained. */ talloc_zfree(subreq); goto done; } tevent_req_set_callback(subreq, sbus_incoming_request_done, req); return; done: talloc_zfree(write_iter); if (state->reply != NULL) { dbus_message_unref(state->reply); state->reply = NULL; } if (ret != EOK) { tevent_req_error(req, ret); } } static void sbus_incoming_request_done(struct tevent_req *subreq) { struct sbus_incoming_request_state *state; struct tevent_req *req; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sbus_incoming_request_state); /* state->reply is filled through iterator in the subrequest. */ ret = sbus_invoker_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { sbus_request_notify_error(state->conn->requests->incoming, state->key, req, ret); return; } sbus_request_notify_success(state->conn->requests->incoming, state->key, req, sbus_request_messages, state->reply); } errno_t sbus_incoming_request_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, DBusMessage **_reply) { struct sbus_incoming_request_state *state; errno_t ret; state = tevent_req_data(req, struct sbus_incoming_request_state); TEVENT_REQ_RETURN_ON_ERROR(req); /* Signals have no reply so this is ok. */ if (state->reply == NULL) { *_reply = NULL; return EOK; } ret = sbus_message_bound_steal(mem_ctx, state->reply); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to steal message [%d]: %s\n", ret, sss_strerror(ret)); return ret; } *_reply = state->reply; return EOK; } struct sbus_outgoing_request_state { const char *key; struct sbus_connection *conn; DBusMessage *reply; uint64_t chain_id; }; static errno_t sbus_outgoing_request_messages(struct tevent_req *req, TALLOC_CTX **_reply_mem_ctx, DBusMessage **_client_message, DBusMessage ***_reply) { struct sbus_outgoing_request_state *state; state = tevent_req_data(req, struct sbus_outgoing_request_state); if (state == NULL) { return ERR_INTERNAL; } *_reply_mem_ctx = state; *_client_message = NULL; *_reply = &state->reply; return EOK; } static void sbus_outgoing_request_done(struct tevent_req *subreq); struct tevent_req * sbus_outgoing_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sbus_connection *conn, const char *key, DBusMessage *msg) { struct sbus_outgoing_request_state *state; struct tevent_req *subreq; struct tevent_req *req; bool key_exists; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sbus_outgoing_request_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); return NULL; } state->conn = conn; /* * The message is sent over top level dbus tevent code. This means that * the chain id information is lost and is not restored when we get reply * from dbus. Therefore we need to remember it and restore it manually * when this request is done. */ state->chain_id = sss_chain_id_get(); if (key != NULL) { state->key = talloc_strdup(state, key); if (state->key == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed!\n"); ret = ENOMEM; goto done; } } /** * We will search table to see if the same request is not already * in progress. If it is, we register ourselves for notification * when it is finished. * * Otherwise we add ourselves as the first request of this type and * set a tevent callback that is triggered when the method handler is done. */ ret = sbus_requests_add(conn->requests->outgoing, key, conn, req, true, &key_exists); if (ret != EOK) { goto done; } if (key_exists) { return req; } subreq = sbus_message_send(state, conn, msg, SBUS_MESSAGE_TIMEOUT); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sbus_outgoing_request_done, req); ret = EAGAIN; done: if (ret != EAGAIN) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; } static void sbus_outgoing_request_done(struct tevent_req *subreq) { struct sbus_outgoing_request_state *state; struct tevent_req *req; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sbus_outgoing_request_state); sss_chain_id_set(state->chain_id); ret = sbus_message_recv(state, subreq, &state->reply); talloc_zfree(subreq); if (ret != EOK) { sbus_request_notify_error(state->conn->requests->outgoing, state->key, req, ret); return; } sbus_request_notify_success(state->conn->requests->outgoing, state->key, req, sbus_outgoing_request_messages, state->reply); } errno_t sbus_outgoing_request_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, DBusMessage **_reply) { struct sbus_outgoing_request_state *state; errno_t ret; state = tevent_req_data(req, struct sbus_outgoing_request_state); TEVENT_REQ_RETURN_ON_ERROR(req); ret = sbus_message_bound_steal(mem_ctx, state->reply); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to steal message [%d]: %s\n", ret, sss_strerror(ret)); return ret; } *_reply = state->reply; return EOK; } struct sbus_request_await_state { int dummy; }; struct tevent_req * sbus_request_await_send(TALLOC_CTX *mem_ctx, struct sbus_connection *conn, enum sbus_request_type type, const char *object_path, const char *interface, const char *member, const char *additional_key) { struct sbus_request_await_state *state; struct sbus_request_list *list; struct tevent_req *req; const char *key; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sbus_request_await_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); return NULL; } key = talloc_asprintf(state, "-:%u:%s.%s:%s%s%s", type, interface, member, object_path, additional_key == NULL ? "" : ":", additional_key == NULL ? "" : additional_key); if (key == NULL) { ret = ENOMEM; goto done; } list = sbus_requests_lookup(conn->requests->outgoing, key); if (list == NULL) { /* No active request with this key exists. */ ret = EOK; goto done; } /* Otherwise attach to this request. */ ret = sbus_requests_add(conn->requests->outgoing, key, conn, req, false, NULL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to attach to the request list " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } ret = EAGAIN; done: if (ret == EOK) { tevent_req_done(req); tevent_req_post(req, conn->ev); } else if (ret != EAGAIN) { tevent_req_error(req, ret); tevent_req_post(req, conn->ev); } return req; } errno_t sbus_request_await_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static errno_t sbus_unwanted_reply_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } void sbus_unwanted_reply(struct tevent_req *subreq) { errno_t ret; ret = sbus_unwanted_reply_recv(subreq); talloc_zfree(subreq); if (ret != EOK && ret != ERR_SBUS_UNKNOWN_SERVICE) { DEBUG(SSSDBG_OP_FAILURE, "Error sending sbus message [%d]: %s\n", ret, sss_strerror(ret)); } }