/* 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 #include "util/util.h" #include "util/dlinklist.h" #include "util/sss_chain_id.h" #include "sbus/sbus_private.h" struct sbus_message_meta { int type; const char *destination; const char *interface; const char *member; const char *sender; const char *path; }; static void sbus_message_meta_read(DBusMessage *message, struct sbus_message_meta *meta) { meta->type = dbus_message_get_type(message); meta->destination = dbus_message_get_destination(message); meta->interface = dbus_message_get_interface(message); meta->member = dbus_message_get_member(message); meta->sender = dbus_message_get_sender(message); meta->path = dbus_message_get_path(message); } struct sbus_issue_request_state { struct sbus_connection *conn; DBusMessageIter message_iter; DBusMessage *message; enum sbus_request_type type; }; static void sbus_issue_request_done(struct tevent_req *subreq); static errno_t sbus_issue_request(TALLOC_CTX *mem_ctx, struct sbus_message_meta *meta, struct sbus_connection *conn, DBusMessage *message, enum sbus_request_type type, const struct sbus_invoker *invoker, const struct sbus_handler *handler) { struct sbus_issue_request_state *state; struct sbus_request *request; struct tevent_req *subreq; errno_t ret; state = talloc_zero(mem_ctx, struct sbus_issue_request_state); if (state == NULL) { return ENOMEM; } state->conn = conn; state->message = dbus_message_ref(message); state->type = type; ret = sbus_message_bound(state, state->message); if (ret != EOK) { dbus_message_unref(state->message); goto done; } dbus_message_iter_init(message, &state->message_iter); request = sbus_request_create(state, conn, type, meta->destination, meta->interface, meta->member, meta->path); if (request == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create request data!\n"); ret = ENOMEM; goto done; } subreq = sbus_incoming_request_send(state, conn->ev, conn, request, invoker, handler, meta->sender, &state->message_iter, message); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create request!\n"); ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sbus_issue_request_done, state); ret = EOK; done: if (ret != EOK) { talloc_free(state); } return ret; } static void sbus_issue_request_done(struct tevent_req *subreq) { struct sbus_issue_request_state *state; struct sbus_message_meta meta; const char *error_name; const char *error_msg; uint64_t old_chain_id; DBusMessage *reply; errno_t ret; /* This is a top level request and a place where we loose tracking of the * correct chain id. We got here from sbus_incoming_request_done * which may finish multiple identical requests at once but we know chain * id only of the one requests that actually run its handler. * * Therefore we need to set the id to 0 since it is not known at this * moment, but it is ok. */ old_chain_id = sss_chain_id_set(0); state = tevent_req_callback_data(subreq, struct sbus_issue_request_state); sbus_message_meta_read(state->message, &meta); ret = sbus_incoming_request_recv(state, subreq, &reply); talloc_zfree(subreq); if (ret == EOK) { DEBUG(SSSDBG_TRACE_FUNC, "%s.%s: Success\n", meta.interface, meta.member); } else { int msg_level = SSSDBG_OP_FAILURE; if (ret == ERR_MISSING_DP_TARGET) msg_level = SSSDBG_FUNC_DATA; if (ret == EACCES) msg_level = SSSDBG_MINOR_FAILURE; /* IFP ACL */ /* ERR_DOMAIN_NOT_FOUND: 'ad_enabled_domains' option can exclude domain */ if (ret == ERR_DOMAIN_NOT_FOUND) msg_level = SSSDBG_CONF_SETTINGS; DEBUG(msg_level, "%s.%s: Error [%d]: %s\n", meta.interface, meta.member, ret, sss_strerror(ret)); } /* Signals do not send a reply. */ if (state->type == SBUS_REQUEST_SIGNAL) { goto done; } if (ret == EOK) { /* sbus_reply decreases the refcount of @reply. This usuall means that * refcount drops to zero and the message is freed. However, under * special circumstances the refcount is increased inside libdbus, * the refcount will be 1 when we leave the function and we drop it * to zero in talloc_free(state) later in this function. This will * leave an invalid message to be send inside dbus connection and * eventually crash. * * Increasing the refcount here makes sure that the refcount is always * correct. */ dbus_message_ref(reply); sbus_reply(state->conn, reply); } else { sbus_errno_to_error(state, ret, &error_name, &error_msg); sbus_reply_error(state->conn, state->message, error_name, error_msg); } done: if (ret == ERR_SBUS_KILL_CONNECTION) { DEBUG(SSSDBG_TRACE_FUNC, "Handler requested to kill the connection!\n"); sbus_connection_free(state->conn); } talloc_free(state); sss_chain_id_set(old_chain_id); } DBusHandlerResult sbus_method_handler(struct sbus_connection *conn, struct sbus_router *router, struct sbus_message_meta *meta, DBusMessage *message) { const struct sbus_method *method; struct sbus_interface *iface; TALLOC_CTX *error_ctx; const char *error_name; const char *error_msg; errno_t ret; DEBUG(SSSDBG_TRACE_INTERNAL, "Received D-Bus method %s.%s on %s\n", meta->interface, meta->member, meta->path); /* Mark this connection as active. */ sbus_connection_mark_active(conn); iface = sbus_router_paths_lookup(router->paths, meta->path, meta->interface); if (iface == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unknown interface!\n"); sbus_reply_error(conn, message, DBUS_ERROR_UNKNOWN_INTERFACE, meta->interface); return DBUS_HANDLER_RESULT_HANDLED; } method = sbus_interface_find_method(iface, meta->member); if (method == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unknown method!\n"); sbus_reply_error(conn, message, DBUS_ERROR_UNKNOWN_METHOD, meta->member); return DBUS_HANDLER_RESULT_HANDLED; } sbus_annotation_warn(iface, method); ret = sbus_issue_request(conn, meta, conn, message, SBUS_REQUEST_METHOD, &method->invoker, &method->handler); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue request [%d]: %s\n", ret, sss_strerror(ret)); if (ret == ENOMEM) { return DBUS_HANDLER_RESULT_NEED_MEMORY; } error_ctx = talloc_new(NULL); if (error_ctx == NULL) { return DBUS_HANDLER_RESULT_NEED_MEMORY; } sbus_errno_to_error(error_ctx, ret, &error_name, &error_msg); sbus_reply_error(conn, message, error_name, error_msg); talloc_free(error_ctx); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_HANDLED; } DBusHandlerResult sbus_signal_handler(struct sbus_connection *conn, struct sbus_router *router, struct sbus_message_meta *meta, DBusMessage *message) { struct sbus_listener_list *list; struct sbus_listener_list *item; errno_t ret; DEBUG(SSSDBG_TRACE_INTERNAL, "Received D-Bus signal %s.%s on %s\n", meta->interface, meta->member, meta->path); list = sbus_router_listeners_lookup(router->listeners, meta->interface, meta->member); if (list == NULL) { /* Most probably not fully initialized yet */ DEBUG(SSSDBG_FUNC_DATA, "We do not listen to this signal!\n"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DLIST_FOR_EACH(item, list) { ret = sbus_issue_request(conn, meta, conn, message, SBUS_REQUEST_SIGNAL, &item->listener->invoker, &item->listener->handler); if (ret != EOK) { /* Nothing to do, try the next one. */ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue request [%d]: %s\n", ret, sss_strerror(ret)); } } return DBUS_HANDLER_RESULT_HANDLED; } DBusHandlerResult sbus_router_filter(struct sbus_connection *conn, struct sbus_router *router, DBusMessage *message) { struct sbus_message_meta meta; sbus_message_meta_read(message, &meta); switch (meta.type) { case DBUS_MESSAGE_TYPE_SIGNAL: return sbus_signal_handler(conn, router, &meta, message); case DBUS_MESSAGE_TYPE_METHOD_CALL: return sbus_method_handler(conn, router, &meta, message); case DBUS_MESSAGE_TYPE_METHOD_RETURN: case DBUS_MESSAGE_TYPE_ERROR: /* This will be processed by the caller. */ return DBUS_HANDLER_RESULT_HANDLED; default: DEBUG(SSSDBG_CRIT_FAILURE, "Invalid message type: %d\n", meta.type); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DBusHandlerResult sbus_connection_filter(DBusConnection *dbus_conn, DBusMessage *message, void *handler_data) { struct sbus_connection *conn; conn = talloc_get_type(handler_data, struct sbus_connection); return sbus_router_filter(conn, conn->router, message); }