#include #include "events.h" #include "util/util.h" #include "dbus/dbus.h" #include "dbus/sssd_dbus.h" #include "dbus/sssd_dbus_private.h" /* Types */ struct dbus_ctx_list; struct sbus_conn_ctx { DBusConnection *conn; struct event_context *ev; int connection_type; int disconnect; struct sbus_method_ctx *method_ctx_list; sbus_conn_destructor_fn destructor; void *private; /* Private data for this connection */ }; struct sbus_conn_watch_ctx { DBusWatch *watch; int fd; struct fd_event *fde; struct sbus_conn_ctx *top; }; struct sbus_conn_timeout_ctx { DBusTimeout *timeout; struct timed_event *te; struct sbus_conn_ctx *top; }; static int _method_list_contains_path(struct sbus_method_ctx *list, struct sbus_method_ctx *method); static void sbus_unreg_object_paths(struct sbus_conn_ctx *dct_ctx); static void sbus_dispatch(struct event_context *ev, struct timed_event *te, struct timeval tv, void *data) { struct timed_event *new_event; struct sbus_conn_ctx *dct_ctx; DBusConnection *conn; int ret; if (data == NULL) { return; } dct_ctx = talloc_get_type(data, struct sbus_conn_ctx); conn = dct_ctx->conn; DEBUG(3, ("conn: %lX\n", conn)); if((dct_ctx->disconnect) || (!dbus_connection_get_is_connected(conn))) { DEBUG(0,("Connection is not open for dispatching.\n")); /* * Free the connection object. * This will invoke the destructor for the connection */ talloc_free(dct_ctx); dct_ctx = NULL; return; } /* Dispatch only once each time through the mainloop to avoid * starving other features */ ret = dbus_connection_get_dispatch_status(conn); if (ret != DBUS_DISPATCH_COMPLETE) { DEBUG(2,("Dispatching.\n")); dbus_connection_dispatch(conn); } /* If other dispatches are waiting, queue up the do_dispatch function * for the next loop. */ ret = dbus_connection_get_dispatch_status(conn); if (ret != DBUS_DISPATCH_COMPLETE) { new_event = event_add_timed(ev, dct_ctx, tv, sbus_dispatch, dct_ctx); if (new_event == NULL) { DEBUG(0,("Could not add dispatch event!\n")); /* TODO: Calling exit here is bad */ exit(1); } } } /* * dbus_connection_read_write_handler * Callback for D-BUS to handle messages on a file-descriptor */ static void sbus_conn_read_write_handler(struct event_context *ev, struct fd_event *fde, uint16_t flags, void *data) { struct sbus_conn_watch_ctx *conn_w_ctx; conn_w_ctx = talloc_get_type(data, struct sbus_conn_watch_ctx); DEBUG(0,("Connection is open for read/write.\n")); dbus_connection_ref(conn_w_ctx->top->conn); if (flags & EVENT_FD_READ) { dbus_watch_handle(conn_w_ctx->watch, DBUS_WATCH_READABLE); } if (flags & EVENT_FD_WRITE) { dbus_watch_handle(conn_w_ctx->watch, DBUS_WATCH_WRITABLE); } dbus_connection_unref(conn_w_ctx->top->conn); } /* * add_connection_watch * Set up hooks into the libevents mainloop for * D-BUS to add file descriptor-based events */ static dbus_bool_t sbus_add_conn_watch(DBusWatch *watch, void *data) { unsigned int flags; unsigned int event_flags; struct sbus_conn_ctx *dt_ctx; struct sbus_conn_watch_ctx *conn_w_ctx; if (!dbus_watch_get_enabled(watch)) { return TRUE; } dt_ctx = talloc_get_type(data, struct sbus_conn_ctx); conn_w_ctx = talloc_zero(dt_ctx, struct sbus_conn_watch_ctx); conn_w_ctx->top = dt_ctx; conn_w_ctx->watch = watch; flags = dbus_watch_get_flags(watch); conn_w_ctx->fd = dbus_watch_get_unix_fd(watch); event_flags = 0; if (flags & DBUS_WATCH_READABLE) event_flags |= EVENT_FD_READ; if (flags & DBUS_WATCH_WRITABLE) event_flags |= EVENT_FD_WRITE; if (event_flags == 0) return FALSE; DEBUG(2,("%lX: %d, %d=%s\n", watch, conn_w_ctx->fd, event_flags, event_flags==EVENT_FD_READ?"READ":"WRITE")); /* Add the file descriptor to the event loop */ conn_w_ctx->fde = event_add_fd(conn_w_ctx->top->ev, conn_w_ctx, conn_w_ctx->fd, event_flags, sbus_conn_read_write_handler, conn_w_ctx); /* Save the event to the watch object so it can be removed later */ dbus_watch_set_data(conn_w_ctx->watch,conn_w_ctx->fde,NULL); return TRUE; } /* * toggle_connection_watch * Hook for D-BUS to toggle the enabled/disabled state of * an event in the mainloop */ static void sbus_toggle_conn_watch(DBusWatch *watch, void *data) { if (dbus_watch_get_enabled(watch)) { sbus_add_conn_watch(watch, data); } else { sbus_remove_watch(watch, data); } } /* * dbus_connection_timeout_handler * Callback for D-BUS to handle timed events */ static void sbus_conn_timeout_handler(struct event_context *ev, struct timed_event *te, struct timeval t, void *data) { struct sbus_conn_timeout_ctx *conn_t_ctx; conn_t_ctx = talloc_get_type(data, struct sbus_conn_timeout_ctx); dbus_timeout_handle(conn_t_ctx->timeout); } /* * add_connection_timeout * Hook for D-BUS to add time-based events to the mainloop */ static dbus_bool_t sbus_add_conn_timeout(DBusTimeout *timeout, void *data) { struct sbus_conn_ctx *dt_ctx; struct sbus_conn_timeout_ctx *conn_t_ctx; struct timeval tv; if (!dbus_timeout_get_enabled(timeout)) return TRUE; dt_ctx = talloc_get_type(data, struct sbus_conn_ctx); conn_t_ctx = talloc_zero(dt_ctx,struct sbus_conn_timeout_ctx); conn_t_ctx->top = dt_ctx; conn_t_ctx->timeout = timeout; tv = _dbus_timeout_get_interval_tv(dbus_timeout_get_interval(timeout)); struct timeval rightnow; gettimeofday(&rightnow, NULL); conn_t_ctx->te = event_add_timed(conn_t_ctx->top->ev, conn_t_ctx, tv, sbus_conn_timeout_handler, conn_t_ctx); /* Save the event to the watch object so it can be removed later */ dbus_timeout_set_data(conn_t_ctx->timeout,conn_t_ctx->te,NULL); return TRUE; } /* * sbus_toggle_conn_timeout * Hook for D-BUS to toggle the enabled/disabled state of a mainloop * event */ void sbus_toggle_conn_timeout(DBusTimeout *timeout, void *data) { if (dbus_timeout_get_enabled(timeout)) { sbus_add_conn_timeout(timeout, data); } else { sbus_remove_timeout(timeout, data); } } /* dbus_connection_wakeup_main * D-BUS makes a callback to the wakeup_main function when * it has data available for dispatching. * In order to avoid blocking, this function will create a now() * timed event to perform the dispatch during the next iteration * through the mainloop */ static void sbus_conn_wakeup_main(void *data) { struct sbus_conn_ctx *dct_ctx; struct timeval tv; struct timed_event *te; dct_ctx = talloc_get_type(data, struct sbus_conn_ctx); gettimeofday(&tv, NULL); /* D-BUS calls this function when it is time to do a dispatch */ te = event_add_timed(dct_ctx->ev, dct_ctx, tv, sbus_dispatch, dct_ctx); if (te == NULL) { DEBUG(0,("Could not add dispatch event!\n")); exit(1); } } /* * integrate_connection_with_event_loop * Set up a D-BUS connection to use the libevents mainloop * for handling file descriptor and timed events */ int sbus_add_connection(TALLOC_CTX *ctx, struct event_context *ev, DBusConnection *dbus_conn, struct sbus_conn_ctx **dct_ctx, int connection_type) { dbus_bool_t dbret; struct sbus_conn_ctx *dt_ctx; DEBUG(0,("Adding connection %lX\n", dbus_conn)); dt_ctx = talloc_zero(ctx, struct sbus_conn_ctx); dt_ctx->ev = ev; dt_ctx->conn = dbus_conn; dt_ctx->connection_type = connection_type; dt_ctx->disconnect = 0; /* This will be replaced on the first call to sbus_conn_add_method_ctx() */ dt_ctx->method_ctx_list = NULL; /* * Set the default destructor * Connections can override this with * sbus_conn_set_destructor */ sbus_conn_set_destructor(dt_ctx, NULL); /* Set up DBusWatch functions */ dbret = dbus_connection_set_watch_functions(dt_ctx->conn, sbus_add_conn_watch, sbus_remove_watch, sbus_toggle_conn_watch, dt_ctx, NULL); if (!dbret) { DEBUG(0,("Error setting up D-BUS connection watch functions\n")); return EIO; } /* Set up DBusTimeout functions */ dbret = dbus_connection_set_timeout_functions(dt_ctx->conn, sbus_add_conn_timeout, sbus_remove_timeout, sbus_toggle_conn_timeout, dt_ctx, NULL); if (!dbret) { DEBUG(0,("Error setting up D-BUS server timeout functions\n")); /* FIXME: free resources ? */ return EIO; } /* Set up dispatch handler */ dbus_connection_set_wakeup_main_function(dt_ctx->conn, sbus_conn_wakeup_main, dt_ctx, NULL); /* Set up any method_contexts passed in */ /* Attempt to dispatch immediately in case of opportunistic * services connecting before the handlers were all up. * If there are no messages to be dispatched, this will do * nothing. */ sbus_conn_wakeup_main(dt_ctx); /* Return the new toplevel object */ *dct_ctx = dt_ctx; return EOK; } /*int sbus_new_connection(struct sbus_method_ctx *ctx, const char *address, DBusConnection **connection, sbus_conn_destructor_fn destructor)*/ int sbus_new_connection(TALLOC_CTX *ctx, struct event_context *ev, const char *address, struct sbus_conn_ctx **dct_ctx, sbus_conn_destructor_fn destructor) { DBusConnection *dbus_conn; DBusError dbus_error; int ret; dbus_error_init(&dbus_error); /* Open a shared D-BUS connection to the address */ dbus_conn = dbus_connection_open(address, &dbus_error); if (!dbus_conn) { DEBUG(0, ("Failed to open connection: name=%s, message=%s\n", dbus_error.name, dbus_error.message)); return EIO; } ret = sbus_add_connection(ctx, ev, dbus_conn, dct_ctx, SBUS_CONN_TYPE_SHARED); if (ret != EOK) { /* FIXME: release resources */ } dbus_connection_set_exit_on_disconnect((*dct_ctx)->conn, FALSE); /* Set connection destructor */ sbus_conn_set_destructor(*dct_ctx, destructor); return ret; } /* * sbus_conn_set_destructor * Configures a callback to clean up this connection when it * is finalized. * @param dct_ctx The sbus_conn_ctx created * when this connection was established * @param destructor The destructor function that should be * called when the connection is finalized. If passed NULL, * this will reset the connection to the default destructor. */ void sbus_conn_set_destructor(struct sbus_conn_ctx *dct_ctx, sbus_conn_destructor_fn destructor) { if (!dct_ctx) { return; } dct_ctx->destructor = destructor; /* TODO: Should we try to handle the talloc_destructor too? */ } int sbus_default_connection_destructor(void *ctx) { struct sbus_conn_ctx *dct_ctx; dct_ctx = talloc_get_type(ctx, struct sbus_conn_ctx); DEBUG(3, ("Invoking default destructor on connection %lX\n", dct_ctx->conn)); if (dct_ctx->connection_type == SBUS_CONN_TYPE_PRIVATE) { /* Private connections must be closed explicitly */ dbus_connection_close(dct_ctx->conn); } else if (dct_ctx->connection_type == SBUS_CONN_TYPE_SHARED) { /* Shared connections are destroyed when their last reference is removed */ } else { /* Critical Error! */ DEBUG(0,("Critical Error, connection_type is neither shared nor private!\n")); return -1; } /* Remove object path */ /* TODO: Remove object paths */ dbus_connection_unref(dct_ctx->conn); return 0; } /* * sbus_get_connection * Utility function to retreive the DBusConnection object * from a sbus_conn_ctx */ DBusConnection *sbus_get_connection(struct sbus_conn_ctx *dct_ctx) { return dct_ctx->conn; } void sbus_disconnect (struct sbus_conn_ctx *dct_ctx) { if (dct_ctx == NULL) { return; } DEBUG(2,("Disconnecting %lX\n", dct_ctx->conn)); dbus_connection_ref(dct_ctx->conn); dct_ctx->disconnect = 1; /* Invoke the custom destructor, if it exists */ if(dct_ctx->destructor) { dct_ctx->destructor(dct_ctx); } /* Unregister object paths */ sbus_unreg_object_paths(dct_ctx); /* Disable watch functions */ dbus_connection_set_watch_functions(dct_ctx->conn, NULL, NULL, NULL, NULL, NULL); /* Disable timeout functions */ dbus_connection_set_timeout_functions(dct_ctx->conn, NULL, NULL, NULL, NULL, NULL); /* Disable dispatch status function */ dbus_connection_set_dispatch_status_function(dct_ctx->conn, NULL, NULL, NULL); /* Disable wakeup main function */ dbus_connection_set_wakeup_main_function(dct_ctx->conn, NULL, NULL, NULL); /* Finalize the connection */ sbus_default_connection_destructor(dct_ctx); dbus_connection_unref(dct_ctx->conn); DEBUG(2,("Disconnected %lX\n", dct_ctx->conn)); } /* messsage_handler * Receive messages and process them */ static DBusHandlerResult message_handler(DBusConnection *conn, DBusMessage *message, void *user_data) { struct sbus_method_ctx *ctx; const char *method; const char *path; const char *msg_interface; DBusMessage *reply = NULL; int i, ret; ctx = talloc_get_type(user_data, struct sbus_method_ctx); method = dbus_message_get_member(message); path = dbus_message_get_path(message); msg_interface = dbus_message_get_interface(message); if (!method || !path || !msg_interface) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* Validate the method interface */ if (strcmp(msg_interface, ctx->interface) != 0) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* Validate the D-BUS path */ if (strcmp(path, ctx->path) == 0) { for (i = 0; ctx->methods[i].method != NULL; i++) { if (strcmp(method, ctx->methods[i].method) == 0) { ret = ctx->methods[i].fn(message, ctx, &reply); /* FIXME: check error */ break; } } /* FIXME: check if we didn't find any matching method */ } DEBUG(2, ("Method %s complete. Reply was %srequested.\n", method, reply?"":"not ")); if (reply) { dbus_connection_send(conn, reply, NULL); dbus_message_unref(reply); } return reply ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* Adds a new D-BUS path message handler to the connection * Note: this must be a unique path. */ int sbus_conn_add_method_ctx(struct sbus_conn_ctx *dct_ctx, struct sbus_method_ctx *method_ctx) { DBusObjectPathVTable *connection_vtable; dbus_bool_t dbret; if (!method_ctx) { return EINVAL; } if (_method_list_contains_path(dct_ctx->method_ctx_list, method_ctx)) { return EINVAL; } DLIST_ADD(dct_ctx->method_ctx_list, method_ctx); /* Set up the vtable for the object path */ connection_vtable = talloc_zero(dct_ctx, DBusObjectPathVTable); if (method_ctx->message_handler) { connection_vtable->message_function = method_ctx->message_handler; } else { connection_vtable->message_function = message_handler; } dbret = dbus_connection_register_object_path(dct_ctx->conn, method_ctx->path, connection_vtable, method_ctx); if (!dbret) { return ENOMEM; } return EOK; } static int _method_list_contains_path(struct sbus_method_ctx *list, struct sbus_method_ctx *method) { struct sbus_method_ctx *iter; if (!list || !method) { return 0; /* FALSE */ } iter = list; while (iter != NULL) { if (strcmp(iter->path, method->path) == 0) return 1; /* TRUE */ iter = iter->next; } return 0; /* FALSE */ } static void sbus_unreg_object_paths(struct sbus_conn_ctx *dct_ctx) { struct sbus_method_ctx *iter = dct_ctx->method_ctx_list; struct sbus_method_ctx *purge; while(iter != NULL) { dbus_connection_unregister_object_path(dct_ctx->conn, iter->path); DLIST_REMOVE(dct_ctx->method_ctx_list, iter); purge = iter; iter = iter->next; talloc_free(purge); } } void sbus_conn_set_private_data(struct sbus_conn_ctx *dct_ctx, void *private) { dct_ctx->private = private; }