#include #include #include #include "app.h" /* * The manager connection for the selection manager. * Responds to requests by the user to expose selections across levels. * Handles taking ownership of selections, passing data, and updating * the "Exposure in Progress" dialog box. */ static xcb_connection_t *conn; static xcb_window_t window; static uint8_t xfixes_type; static GtkWidget *appwin; static GtkWidget *expwin; static GtkListStore *store; static GtkTextBuffer *buffer; static GHashTable *transfers; static GSList *ownership; static char expose_in_progress; static security_context_t cur_level; static xcb_atom_t cur_name; static const char *cur_string; static xcb_window_t cur_owner; struct transfer { xcb_atom_t name; xcb_atom_t target; xcb_atom_t property; xcb_window_t requestor; xcb_window_t owner; security_context_t level; }; static void free_transfer(void *p) { struct transfer *ptr = p; freecon(ptr->level); free(ptr); } static void setup_x(const gchar *display) { const xcb_setup_t *setup; xcb_screen_iterator_t iter; xcb_window_t root; make_connection(display, &conn, NULL); xfixes_type = xcb_get_extension_data(conn, &xcb_xfixes_id)->first_event; setup = xcb_get_setup(conn); iter = xcb_setup_roots_iterator(setup); root = iter.data->root; window = xcb_generate_id(conn); xcb_create_window(conn, 0, window, root, 0, 0, 100, 100, 0, XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); atom_init(conn); selection_register(conn, window); xcb_flush(conn); } static void expose_stop(xcb_timestamp_t time) { security_context_t octx; GSList *elt; if (expose_in_progress) { free(cur_level); cur_level = NULL; expose_in_progress = FALSE; for (elt = ownership; elt; elt = g_slist_next(elt)) { octx = elt->data; xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); xcb_set_selection_owner(conn, XCB_NONE, cur_name, time); } } xcb_flush(conn); } static void expose_stop_msg(xcb_timestamp_t time, const char *msg) { GtkWidget *dialog; expose_stop(time); gtk_dialog_response(GTK_DIALOG(expwin), GTK_RESPONSE_REJECT); dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, msg); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } static int expose_item_matches(GtkTreeIter *iter, xcb_atom_t name, security_context_t ctx) { GtkTreeModel *tree = GTK_TREE_MODEL(store); xcb_atom_t iname; security_context_t ictx; int result; gtk_tree_model_get(tree, iter, COL_ATOM, &iname, COL_OCTX, &ictx, -1); result = (name == iname) && strcmp(ctx, ictx); g_free(ictx); return result; } static void expose(xcb_atom_t name, xcb_window_t owner, security_context_t level, xcb_timestamp_t time) { GtkTreeModel *tree = GTK_TREE_MODEL(store); GtkTreeIter iter; GtkWidget *dialog; GtkTextIter text; security_context_t octx; char msg[256]; GSList *elt; int i = 0; cur_string = selection_lookup(conn, window, name); snprintf(msg, sizeof(msg), "Starting expose operation for %s at %s\n", cur_string, level); gtk_text_buffer_set_text(buffer, msg, -1); for (elt = ownership; elt; elt = g_slist_next(elt)) free(elt->data); g_slist_free(ownership); ownership = NULL; if (gtk_tree_model_get_iter_first(tree, &iter)) { do { if (expose_item_matches(&iter, name, level)) { gtk_tree_model_get(tree, &iter, COL_OCTX, &octx, -1); xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); xcb_set_selection_owner(conn, window, name, time); ownership = g_slist_append(ownership, strdup(octx)); snprintf(msg, sizeof(msg), "Took control of %s at %s\n", cur_string, octx); gtk_text_buffer_get_end_iter(buffer, &text); gtk_text_buffer_insert(buffer, &text, msg, -1); g_free(octx); i++; } } while (gtk_tree_model_iter_next(tree, &iter)); } if (i > 0) { free(cur_level); cur_level = strdup(level); cur_name = name; cur_owner = owner; expose_in_progress = TRUE; gtk_widget_show_all(expwin); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "There are no other instances of %s.\n", cur_string); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } xcb_flush(conn); } static xcb_window_t make_window_at_level(security_context_t level) { xcb_window_t result; xcb_selinux_set_window_create_context(conn, strlen(level) + 1, level); result = xcb_generate_id(conn); xcb_create_window(conn, 0, result, window, 0, 0, 100, 100, 0, XCB_WINDOW_CLASS_INPUT_ONLY, 0, 0, NULL); return result; } static void send_convert_request(xcb_atom_t name, xcb_atom_t target, xcb_timestamp_t time, struct transfer *transfer) { xcb_selinux_get_window_context_cookie_t cookie; xcb_selinux_get_window_context_reply_t *reply; xcb_generic_error_t *error; xcb_window_t requestor; security_context_t wctx; cookie = xcb_selinux_get_window_context(conn, cur_owner); reply = xcb_selinux_get_window_context_reply(conn, cookie, &error); if (error) APP_ERR(error, "An error was received during GetWindowContext.\n"); wctx = xcb_selinux_get_window_context_context(reply); requestor = make_window_at_level(wctx); g_hash_table_insert(transfers, (void *)requestor, transfer); xcb_selinux_set_selection_use_context(conn, strlen(cur_level) + 1, cur_level); xcb_convert_selection(conn, requestor, cur_name, target, xa_prop, time); free(reply); } static gboolean update_check(xcb_atom_t name) { xcb_get_selection_owner_cookie_t cookie; xcb_get_selection_owner_reply_t *reply; xcb_generic_error_t *error; xcb_window_t owner; /* check if the current owner is still the selection owner, and that the current level is still the current owner's context */ xcb_selinux_set_selection_use_context(conn, strlen(cur_level) + 1, cur_level); cookie = xcb_get_selection_owner(conn, name); reply = xcb_get_selection_owner_reply(conn, cookie, &error); if (error) APP_ERR(error, "An error was received during GetSelectionOwner.\n"); owner = reply->owner; free(reply); return (owner == cur_owner); } static gboolean update(xcb_atom_t selection, xcb_timestamp_t time) { security_context_t octx; xcb_get_selection_owner_cookie_t *cookies; xcb_get_selection_owner_reply_t *owner; xcb_generic_error_t *error; GSList *elt, *newlist = NULL; guint i, len; GtkTextIter text; char msg[256]; if (!expose_in_progress || selection != cur_name) return FALSE; if (!update_check(selection)) { snprintf(msg, sizeof(msg), "%s changed owners at the exposed level.\n" "Canceling expose operation.", cur_string); expose_stop_msg(time, msg); return FALSE; } len = g_slist_length(ownership); cookies = malloc(len * sizeof(*cookies)); if (!cookies) APP_ERR(NULL, "Out of memory\n"); for (elt = ownership, i = 0; elt; elt = g_slist_next(elt), i++) { octx = elt->data; xcb_selinux_set_selection_use_context(conn, strlen(octx) + 1, octx); cookies[i] = xcb_get_selection_owner(conn, cur_name); } for (elt = ownership, i = 0; elt; elt = g_slist_next(elt), i++) { octx = elt->data; owner = xcb_get_selection_owner_reply(conn, cookies[i], &error); if (error) APP_ERR(error, "An error was received during GetSelectionOwner.\n"); if (owner->owner != window) { snprintf(msg, sizeof(msg), "Lost ownership of %s at %s\n", cur_string, octx); gtk_text_buffer_get_end_iter(buffer, &text); gtk_text_buffer_insert(buffer, &text, msg, -1); free(octx); } else { newlist = g_slist_append(newlist, octx); } } g_slist_free(ownership); ownership = newlist; if (!ownership) { snprintf(msg, sizeof(msg), "Selection manager lost ownership of %s.\n" "Canceling expose operation.", cur_string); expose_stop_msg(time, msg); return FALSE; } return TRUE; } static void handle_request(xcb_selection_request_event_t *event) { struct transfer *transfer; xcb_selection_notify_event_t response; /* do a complete update to avoid race conditions */ if (!update(event->selection, event->time)) { response.response_type = XCB_SELECTION_NOTIFY; response.time = event->time; response.requestor = event->requestor; response.selection = event->selection; response.target = event->target; response.property = XCB_NONE; xcb_send_event(conn, FALSE, event->requestor, 0, (const char *)&response); return; } /* check if the request is allowed by policy */ if (event->target != xa_targets) { ; /* security check here */ } transfer = malloc(sizeof(struct transfer)); if (!transfer) APP_ERR(NULL, "Out of memory\n"); transfer->name = event->selection; transfer->target = event->target; transfer->property = event->property; transfer->owner = event->owner; transfer->requestor = event->requestor; transfer->level = strdup(cur_level); send_convert_request(event->selection, event->target, event->time, transfer); } static void handle_notify(xcb_selection_notify_event_t *event) { xcb_selinux_get_client_context_cookie_t cookie; xcb_selinux_get_client_context_reply_t *reply; xcb_generic_error_t *error; security_context_t cctx; xcb_atom_t type; unsigned format; void *data; size_t len; struct transfer *transfer; xcb_window_t tmpwin = event->requestor; GtkTextIter text; char msg[256]; transfer = g_hash_table_lookup(transfers, (void *)event->requestor); if (!transfer) return; if (event->property != XCB_NONE) { fetch_property_data(conn, event->requestor, event->property, FALSE, &type, &format, &data, &len); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, transfer->requestor, transfer->property, type, format, len, data); free(data); } event->response_type &= 0x7f; event->requestor = transfer->requestor; xcb_send_event(conn, FALSE, transfer->requestor, 0, (const char *)event); g_hash_table_remove(transfers, (void *)tmpwin); xcb_destroy_window(conn, tmpwin); cookie = xcb_selinux_get_client_context(conn, transfer->requestor); reply = xcb_selinux_get_client_context_reply(conn, cookie, &error); if (error) APP_ERR(error, "An error was received during GetClientContext.\n"); cctx = xcb_selinux_get_client_context_context(reply); snprintf(msg, sizeof(msg), "Transferred content to domain %s\n", cctx); gtk_text_buffer_get_end_iter(buffer, &text); gtk_text_buffer_insert(buffer, &text, msg, -1); free(reply); } static void handle_clear(xcb_selection_clear_event_t *event) { update(event->selection, event->time); } static void handle_xfixes(xcb_xfixes_selection_notify_event_t *event) { update(event->selection, event->timestamp); } static void handle_client(xcb_client_message_event_t *event) { fprintf(stderr, "Manager: got a %s ClientMessage\n", atom_lookup(conn, event->type)); } static gboolean handle_event(GIOChannel *source, GIOCondition condition, gpointer data) { xcb_generic_event_t *event; while ((event = xcb_poll_for_event(conn))) { switch (event->response_type) { case XCB_MAPPING_NOTIFY: /* do nothing */ break; case XCB_SELECTION_CLEAR: handle_clear((xcb_selection_clear_event_t *)event); break; case XCB_SELECTION_REQUEST: handle_request((xcb_selection_request_event_t *)event); break; case XCB_SELECTION_NOTIFY: case XCB_SELECTION_NOTIFY | 0x80: handle_notify((xcb_selection_notify_event_t *)event); break; case XCB_CLIENT_MESSAGE: case XCB_CLIENT_MESSAGE | 0x80: handle_client((xcb_client_message_event_t *)event); break; case 0: APP_ERR((xcb_generic_error_t *)event, "Manager: Error in main loop!\n"); default: if (event->response_type == xfixes_type) handle_xfixes((xcb_xfixes_selection_notify_event_t *)event); else fprintf(stderr, "Manager: %d event\n", event->response_type); break; break; } free(event); xcb_flush(conn); } return TRUE; } int sel_manager_init(struct thread_data *data) { int fd; GIOChannel *channel; store = GTK_LIST_STORE(data->store); appwin = data->appwin; expwin = data->expwin; buffer = g_object_get_data(G_OBJECT(expwin), "textbuf"); transfers = g_hash_table_new_full(NULL, NULL, NULL, free_transfer); setup_x(data->display); fd = xcb_get_file_descriptor(conn); channel = g_io_channel_unix_new(fd); g_io_channel_set_encoding(channel, NULL, NULL); g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, handle_event, NULL); return 0; } void sel_expose(GtkWidget *widget, GtkTreeView *view) { GtkTreeSelection *sel; GtkTreeIter iter; GtkWidget *dialog; xcb_atom_t atom; xcb_window_t owner; gchar *name, *octx; sel = gtk_tree_view_get_selection(view); if (gtk_tree_selection_get_selected(sel, NULL, &iter)) { gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, COL_ATOM, &atom, COL_WIN, &owner, COL_NAME, &name, COL_OCTX, &octx, -1); dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Really make clipboard %s,\n" "at level %s\n" "available to all levels?", name, octx); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) expose(atom, owner, octx, timestamp); gtk_widget_destroy(dialog); g_free(name); g_free(octx); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(appwin), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "No clipboard is currently selected."); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } } void sel_expose_stop(GtkDialog *dialog, gint response_id, gpointer user_data) { gtk_widget_hide_all(expwin); expose_stop(timestamp); }