/** * @file * Implementation of the view for a libseaudit model. * * @author Jeremy A. Mowery jmowery@tresys.com * @author Jason Tang jtang@tresys.com * @author Jeremy Solt jsolt@tresys.com * * Copyright (C) 2003-2007 Tresys Technology, LLC * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "message_view.h" #include "modify_view.h" #include "utilgui.h" #include #include #include #include #include #include /** * A custom model that implements the interfaces GtkTreeModel and * GtkTreeSortable. */ typedef struct message_view_store { /** this must be the first field, to satisfy glib */ GObject parent; /** pointer to the store's controller */ message_view_t *view; /** vector of seaudit_message_t, as returned by * seaudit_model_get_messages() */ apol_vector_t *messages; /** column that is currently being sorted; use OTHER_FIELD to * indicate no sorting */ gint sort_field; /** current sort direction, either 1 or ascending or -1 for * descending */ int sort_dir; /** unique integer for each instance of a model */ gint stamp; } message_view_store_t; typedef struct message_view_store_class { GObjectClass parent_class; } message_view_store_class_t; static GType message_view_store_get_type(void); #define SEAUDIT_TYPE_MESSAGE_VIEW_STORE (message_view_store_get_type()) #define SEAUDIT_IS_MESSAGE_VIEW_STORE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAUDIT_TYPE_MESSAGE_VIEW_STORE)) struct message_view { seaudit_model_t *model; toplevel_t *top; /** toplevel of the view, currently a scrolled_window */ GtkWidget *w; /** actual GTK+ tree view widget that displays the rows and * columns of message data */ GtkTreeView *view; /** GTK+ store that models messages within the tree */ message_view_store_t *store; /** filename for when this view was saved (could be NULL) */ char *filename; /** most recent filename for exported messages (could be NULL) */ char *export_filename; }; typedef seaudit_sort_t *(*sort_generator_fn_t) (int direction); struct view_column_record { preference_field_e id; const char *name; const char *sample_text; sort_generator_fn_t sort; }; static const struct view_column_record column_data[] = { {HOST_FIELD, "Hostname", "Hostname", seaudit_sort_by_host}, {MESSAGE_FIELD, "Message", "Message", seaudit_sort_by_message_type}, {DATE_FIELD, "Date", "Jan 01 00:00:00", seaudit_sort_by_date}, {SUSER_FIELD, "Source\nUser", "Source", seaudit_sort_by_source_user}, {SROLE_FIELD, "Source\nRole", "Source", seaudit_sort_by_source_role}, {STYPE_FIELD, "Source\nType", "unlabeled_t", seaudit_sort_by_source_type}, {SMLS_LVL_FIELD, "Source\nMLS Level", "MLS Level", seaudit_sort_by_source_mls_lvl}, {SMLS_CLR_FIELD, "Source\nMLS Clearance", "MLS Clearance", seaudit_sort_by_source_mls_clr}, {TUSER_FIELD, "Target\nUser", "Target", seaudit_sort_by_target_user}, {TROLE_FIELD, "Target\nRole", "Target", seaudit_sort_by_target_role}, {TTYPE_FIELD, "Target\nType", "unlabeled_t", seaudit_sort_by_target_type}, {TMLS_LVL_FIELD, "Target\nMLS Level", "MLS Level", seaudit_sort_by_target_mls_lvl}, {TMLS_CLR_FIELD, "Target\nMLS Clearance", "MLS Clearance", seaudit_sort_by_target_mls_clr}, {OBJCLASS_FIELD, "Object\nClass", "Object", seaudit_sort_by_object_class}, {PERM_FIELD, "Permission", "Permission", seaudit_sort_by_permission}, {EXECUTABLE_FIELD, "Executable", "/usr/bin/cat", seaudit_sort_by_executable}, {COMMAND_FIELD, "Command", "/usr/bin/cat", seaudit_sort_by_command}, {NAME_FIELD, "Name", "iceweasel", seaudit_sort_by_name}, {PID_FIELD, "PID", "12345", seaudit_sort_by_pid}, {INODE_FIELD, "Inode", "123456", seaudit_sort_by_inode}, {PATH_FIELD, "Path", "/home/gburdell/foo", seaudit_sort_by_path}, {OTHER_FIELD, "Other", "Lorem ipsum dolor sit amet, consectetur", NULL} }; static const size_t num_columns = sizeof(column_data) / sizeof(column_data[0]); /** * (Re)sort the view based upon which column is clicked. If already * sorting on this column, then reverse the sort direction. Also * update the sort indicator for this column. */ static gboolean message_view_on_column_click(GtkTreeViewColumn * column, gpointer user_data) { gint column_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column id")); message_view_t *view = (message_view_t *) user_data; int dir = 0; seaudit_sort_t *sort; GtkTreeViewColumn *prev_column; if (column_id == view->store->sort_field) { dir = view->store->sort_dir * -1; } else { dir = 1; } if ((sort = column_data[(preference_field_e) column_id].sort(dir)) == NULL) { toplevel_ERR(view->top, "%s", strerror(errno)); return TRUE; } seaudit_model_clear_sorts(view->model); if (seaudit_model_append_sort(view->model, sort) < 0) { seaudit_sort_destroy(&sort); toplevel_ERR(view->top, "%s", strerror(errno)); } prev_column = gtk_tree_view_get_column(view->view, view->store->sort_field); if (prev_column != NULL) { gtk_tree_view_column_set_sort_indicator(prev_column, FALSE); } gtk_tree_view_column_set_sort_indicator(column, TRUE); if (dir > 0) { gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING); } else { gtk_tree_view_column_set_sort_order(column, GTK_SORT_DESCENDING); } view->store->sort_field = column_id; view->store->sort_dir = dir; message_view_update_rows(view); return TRUE; } /*************** implementation of a custom GtkTreeModel ***************/ static GObjectClass *parent_class = NULL; static void message_view_store_init(message_view_store_t * m); static void message_view_store_class_init(message_view_store_class_t * c); static void message_view_store_tree_init(GtkTreeModelIface * iface); static void message_view_store_finalize(GObject * object); static GtkTreeModelFlags message_view_store_get_flags(GtkTreeModel * tree_model); static gint message_view_store_get_n_columns(GtkTreeModel * tree_model); static GType message_view_store_get_column_type(GtkTreeModel * tree_model, gint index); static gboolean message_view_store_get_iter(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreePath * path); static GtkTreePath *message_view_store_get_path(GtkTreeModel * tree_model, GtkTreeIter * iter); static void message_view_store_get_value(GtkTreeModel * tree_model, GtkTreeIter * iter, gint column, GValue * value); static gboolean message_view_store_iter_next(GtkTreeModel * tree_model, GtkTreeIter * iter); static gboolean message_view_store_iter_children(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreeIter * parent); static gboolean message_view_store_iter_has_child(GtkTreeModel * tree_model, GtkTreeIter * iter); static gint message_view_store_iter_n_children(GtkTreeModel * tree_model, GtkTreeIter * iter); static gboolean message_view_store_iter_nth_child(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreeIter * parent, gint n); static gboolean message_view_store_iter_parent(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreeIter * child); static GType message_view_store_get_type(void) { static GType store_type = 0; static const GTypeInfo store_info = { sizeof(message_view_store_class_t), NULL, NULL, (GClassInitFunc) message_view_store_class_init, NULL, NULL, sizeof(message_view_store_t), 0, (GInstanceInitFunc) message_view_store_init }; static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc) message_view_store_tree_init, NULL, NULL }; if (store_type) return store_type; store_type = g_type_register_static(G_TYPE_OBJECT, "message_view_store", &store_info, (GTypeFlags) 0); g_type_add_interface_static(store_type, GTK_TYPE_TREE_MODEL, &tree_model_info); return store_type; } static void message_view_store_init(message_view_store_t * m) { static int next_stamp = 0; m->messages = NULL; m->sort_field = OTHER_FIELD; m->sort_dir = 1; m->stamp = next_stamp++; } static void message_view_store_class_init(message_view_store_class_t * c) { GObjectClass *object_class; parent_class = g_type_class_peek_parent(c); object_class = (GObjectClass *) c; object_class->finalize = message_view_store_finalize; } static void message_view_store_tree_init(GtkTreeModelIface * iface) { iface->get_flags = message_view_store_get_flags; iface->get_n_columns = message_view_store_get_n_columns; iface->get_column_type = message_view_store_get_column_type; iface->get_iter = message_view_store_get_iter; iface->get_path = message_view_store_get_path; iface->get_value = message_view_store_get_value; iface->iter_next = message_view_store_iter_next; iface->iter_children = message_view_store_iter_children; iface->iter_has_child = message_view_store_iter_has_child; iface->iter_n_children = message_view_store_iter_n_children; iface->iter_nth_child = message_view_store_iter_nth_child; iface->iter_parent = message_view_store_iter_parent; } static void message_view_store_finalize(GObject * object) { (*parent_class->finalize) (object); } static GtkTreeModelFlags message_view_store_get_flags(GtkTreeModel * tree_model) { g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), 0); return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY; } static gint message_view_store_get_n_columns(GtkTreeModel * tree_model __attribute__ ((unused))) { return OTHER_FIELD + 1; } static GType message_view_store_get_column_type(GtkTreeModel * tree_model, gint idx __attribute__ ((unused))) { g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), G_TYPE_INVALID); /* everything is a string for now */ return G_TYPE_STRING; } static gboolean message_view_store_get_iter(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreePath * path) { gint i; message_view_store_t *store = (message_view_store_t *) tree_model; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), FALSE); g_return_val_if_fail(gtk_tree_path_get_depth(path) > 0, FALSE); i = gtk_tree_path_get_indices(path)[0]; if (i >= apol_vector_get_size(store->messages)) return FALSE; iter->stamp = store->stamp; iter->user_data = apol_vector_get_element(store->messages, i); iter->user_data2 = GINT_TO_POINTER(i); iter->user_data3 = store->view; return TRUE; } static GtkTreePath *message_view_store_get_path(GtkTreeModel * tree_model, GtkTreeIter * iter) { GtkTreePath *retval; message_view_store_t *store = (message_view_store_t *) tree_model; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), NULL); g_return_val_if_fail(iter->stamp == store->stamp, NULL); retval = gtk_tree_path_new(); gtk_tree_path_append_index(retval, GPOINTER_TO_INT(iter->user_data2)); return retval; } /** * Given a string, check that it is UTF8 legal. If not, or if the * string is NULL, then return an empty string. Otherwise return the * original string. */ static void message_view_to_utf8(GValue * value, const char *s) { if (s == NULL || !g_utf8_validate(s, -1, NULL)) { g_value_set_string(value, ""); } g_value_set_string(value, s); } static void message_view_store_get_value(GtkTreeModel * tree_model, GtkTreeIter * iter, gint column, GValue * value) { message_view_store_t *store; message_view_t *view; seaudit_message_t *m; seaudit_message_type_e type; void *data; seaudit_avc_message_t *avc; g_return_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model)); g_return_if_fail(iter != NULL); g_return_if_fail(column <= OTHER_FIELD); g_value_init(value, G_TYPE_STRING); store = (message_view_store_t *) tree_model; view = store->view; m = (seaudit_message_t *) iter->user_data; data = seaudit_message_get_data(m, &type); preference_field_e field = column; switch (field) { case HOST_FIELD: { message_view_to_utf8(value, seaudit_message_get_host(m)); return; } case MESSAGE_FIELD: { char *message = "Invalid"; switch (type) { case SEAUDIT_MESSAGE_TYPE_BOOL: { message = "Boolean"; break; } case SEAUDIT_MESSAGE_TYPE_LOAD: { message = "Load"; break; } case SEAUDIT_MESSAGE_TYPE_AVC: { avc = (seaudit_avc_message_t *) data; seaudit_avc_message_type_e avc_type; avc_type = seaudit_avc_message_get_message_type(avc); switch (avc_type) { case SEAUDIT_AVC_DENIED: { message = "Denied"; break; } case SEAUDIT_AVC_GRANTED: { message = "Granted"; break; } default: { /* should never get here */ toplevel_ERR(view->top, "Got an invalid AVC message type %d!", avc_type); assert(0); return; } } break; } default: { /* should never get here */ toplevel_ERR(view->top, "Got an invalid message type %d!", type); assert(0); return; } } message_view_to_utf8(value, message); return; } case DATE_FIELD: { const struct tm *tm = seaudit_message_get_time(m); char date[256]; /* check to see if we have been given a valid year, if * so display, otherwise no year displayed */ if (tm->tm_year == 0) { strftime(date, 256, "%b %d %H:%M:%S", tm); } else { strftime(date, 256, "%b %d %H:%M:%S %Y", tm); } message_view_to_utf8(value, date); return; } case OTHER_FIELD: { char *other = seaudit_message_to_misc_string(m);; if (other == NULL) { toplevel_ERR(view->top, "%s", strerror(errno)); return; } message_view_to_utf8(value, other); free(other); return; } default: /* FALLTHROUGH */ break; } if (type != SEAUDIT_MESSAGE_TYPE_AVC) { /* the rest of the columns are blank for non-AVC * messages */ message_view_to_utf8(value, ""); return; } avc = (seaudit_avc_message_t *) data; switch (field) { case SUSER_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_source_user(avc)); return; } case SROLE_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_source_role(avc)); return; } case STYPE_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_source_type(avc)); return; } case SMLS_LVL_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_source_mls_lvl(avc)); return; } case SMLS_CLR_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_source_mls_clr(avc)); return; } case TUSER_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_target_user(avc)); return; } case TROLE_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_target_role(avc)); return; } case TTYPE_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_target_type(avc)); return; } case TMLS_LVL_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_target_mls_lvl(avc)); return; } case TMLS_CLR_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_target_mls_clr(avc)); return; } case OBJCLASS_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_object_class(avc)); return; } case PERM_FIELD: { const apol_vector_t *perms = seaudit_avc_message_get_perm(avc); char *perm = NULL; size_t i, len = 0; for (i = 0; perms != NULL && i < apol_vector_get_size(perms); i++) { char *p = apol_vector_get_element(perms, i); if (apol_str_appendf(&perm, &len, "%s%s", (i > 0 ? "," : ""), p) < 0) { toplevel_ERR(view->top, "%s", strerror(errno)); return; } } message_view_to_utf8(value, perm); free(perm); return; } case EXECUTABLE_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_exe(avc)); return; } case COMMAND_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_comm(avc)); return; } case NAME_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_name(avc)); return; } case PID_FIELD: { char *s; if (asprintf(&s, "%u", seaudit_avc_message_get_pid(avc)) < 0) { toplevel_ERR(view->top, "%s", strerror(errno)); return; } message_view_to_utf8(value, s); free(s); return; } case INODE_FIELD: { char *s; if (asprintf(&s, "%lu", seaudit_avc_message_get_inode(avc)) < 0) { toplevel_ERR(view->top, "%s", strerror(errno)); return; } message_view_to_utf8(value, s); free(s); return; } case PATH_FIELD: { message_view_to_utf8(value, seaudit_avc_message_get_path(avc)); return; } default: /* FALLTHROUGH */ break; } /* should never get here */ toplevel_ERR(view->top, "Got an invalid column %d!", field); assert(0); } static gboolean message_view_store_iter_next(GtkTreeModel * tree_model, GtkTreeIter * iter) { gint i; message_view_store_t *store = (message_view_store_t *) tree_model; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), FALSE); g_return_val_if_fail(iter->stamp == store->stamp, FALSE); if (iter == NULL || iter->user_data == NULL) return FALSE; i = GPOINTER_TO_INT(iter->user_data2) + 1; if (i >= apol_vector_get_size(store->messages)) { return FALSE; } iter->user_data = apol_vector_get_element(store->messages, i); iter->user_data2 = GINT_TO_POINTER(i); iter->user_data3 = store->view; return TRUE; } static gboolean message_view_store_iter_children(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreeIter * parent) { message_view_store_t *store; g_return_val_if_fail(parent == NULL || parent->user_data != NULL, FALSE); if (parent) return FALSE; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), FALSE); /* set iterator to first row, if possible */ store = (message_view_store_t *) tree_model; if (store->messages == NULL || apol_vector_get_size(store->messages) == 0) return FALSE; iter->stamp = store->stamp; iter->user_data = apol_vector_get_element(store->messages, 0); iter->user_data2 = GINT_TO_POINTER(0); iter->user_data3 = store->view; return TRUE; } static gboolean message_view_store_iter_has_child(GtkTreeModel * tree_model __attribute__ ((unused)), GtkTreeIter * iter __attribute__ ((unused))) { return FALSE; } static gint message_view_store_iter_n_children(GtkTreeModel * tree_model, GtkTreeIter * iter) { message_view_store_t *store; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), -1); g_return_val_if_fail(iter == NULL || iter->user_data != NULL, 0); store = (message_view_store_t *) tree_model; /* return the number of rows, if iterator is at the top; * otherwise return 0 because this store is just a list */ if (iter != NULL || store->messages == NULL) { return 0; } return apol_vector_get_size(store->messages); } static gboolean message_view_store_iter_nth_child(GtkTreeModel * tree_model, GtkTreeIter * iter, GtkTreeIter * parent, gint n) { message_view_store_t *store; g_return_val_if_fail(SEAUDIT_IS_MESSAGE_VIEW_STORE(tree_model), FALSE); store = (message_view_store_t *) tree_model; if (store->messages == NULL || parent != NULL) { return FALSE; } if (n >= apol_vector_get_size(store->messages)) { return FALSE; } iter->stamp = store->stamp; iter->user_data = apol_vector_get_element(store->messages, n); iter->user_data2 = GINT_TO_POINTER(n); iter->user_data3 = store->view; return TRUE; } static gboolean message_view_store_iter_parent(GtkTreeModel * tree_model __attribute__ ((unused)), GtkTreeIter * iter __attribute__ ((unused)), GtkTreeIter * child __attribute__ ((unused))) { return FALSE; } /*************** end of custom GtkTreeModel implementation ***************/ /*************** message_view_messages_vector() callbacks ******************/ #define LBACK 1 #define LFORWARD 2 #define LNOOP 255 typedef struct _msg_user_data { message_view_t *view; apol_vector_t *messages; GtkDialog *dialog; GtkTextBuffer *buffer; gint handle_id; } _msg_user_data_t; static void message_view_dialog_change(GtkTreeModel * tree_model, GtkTreePath * path __attribute__ ((unused)), GtkTreeIter * iter __attribute__ ((unused)), gpointer user_data) { _msg_user_data_t *d = (_msg_user_data_t *) user_data; /* Disconnect this signal handler after it fires. It's one * shot, nothing should be able to bring the next and previous * buttons back from the dead if the view ever changes. */ g_signal_handler_disconnect(tree_model, d->handle_id); d->view = NULL; gtk_dialog_set_response_sensitive(d->dialog, LBACK, FALSE); gtk_dialog_set_response_sensitive(d->dialog, LFORWARD, FALSE); } static void message_view_dialog_response(GtkDialog * dialog, gint response, gpointer user_data) { _msg_user_data_t *d = (_msg_user_data_t *) user_data; GtkTreePath *p; GtkTreeIter tree_iter; GtkTextIter text_iter; size_t i; gboolean go_back = FALSE; gboolean go_forward = FALSE; gtk_text_buffer_set_text(d->buffer, "", -1); p = apol_vector_get_element(d->messages, 0); assert(p != NULL); switch (response) { case LNOOP: break; /* no-op response, display and test only */ case LBACK: gtk_tree_path_prev(p); break; case LFORWARD: gtk_tree_path_next(p); break; case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CLOSE: apol_vector_destroy(&d->messages); if (d->view != NULL) { g_signal_handler_disconnect(d->view->store, d->handle_id); } free(d); gtk_widget_destroy(GTK_WIDGET(dialog)); return; default: /* should never get here */ toplevel_ERR(d->view->top, "Unhandled response type (%d).\n", response); assert(0); return; } assert(d->view != NULL); /* determine if the forward and backward buttons should be enabled or not */ if (apol_vector_get_size(d->messages) == 1) { GtkTreePath *dupe_p = gtk_tree_path_copy(p); if (dupe_p == NULL) { toplevel_ERR(d->view->top, "%s", strerror(errno)); return; } go_back = gtk_tree_path_prev(dupe_p); gtk_tree_path_free(dupe_p); message_view_store_get_iter(GTK_TREE_MODEL(d->view->store), &tree_iter, p); go_forward = gtk_tree_model_iter_next(GTK_TREE_MODEL(d->view->store), &tree_iter); } gtk_dialog_set_response_sensitive(dialog, LBACK, go_back); gtk_dialog_set_response_sensitive(dialog, LFORWARD, go_forward); gtk_text_buffer_get_start_iter(d->buffer, &text_iter); for (i = 0; i < apol_vector_get_size(d->messages); i++) { char *s; p = apol_vector_get_element(d->messages, i); message_view_store_get_iter(GTK_TREE_MODEL(d->view->store), &tree_iter, p); if ((s = seaudit_message_to_string(tree_iter.user_data)) == NULL) { toplevel_ERR(d->view->top, "%s", strerror(errno)); continue; } gtk_text_buffer_insert(d->buffer, &text_iter, s, -1); gtk_text_buffer_insert(d->buffer, &text_iter, "\n", -1); free(s); } } /** * Show all messages within the messages vector (vector of * GtkTreePaths into the view's store). * * Callback function cb_view_message takes ownership of messages * vector and state. */ static void message_view_messages_vector(message_view_t * view, apol_vector_t * messages) { GtkWidget *window = NULL; GtkWidget *scroll; GtkWidget *text_view; GtkTextBuffer *buffer; _msg_user_data_t *state; state = malloc(sizeof(_msg_user_data_t)); if (state == NULL) { toplevel_ERR(view->top, "%s", strerror(errno)); apol_vector_destroy(&messages); return; } window = gtk_dialog_new_with_buttons("View Messages", toplevel_get_window(view->top), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_GO_BACK, LBACK, GTK_STOCK_GO_FORWARD, LFORWARD, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); gtk_dialog_set_default_response(GTK_DIALOG(window), GTK_RESPONSE_CLOSE); gtk_window_set_modal(GTK_WINDOW(window), FALSE); scroll = gtk_scrolled_window_new(NULL, NULL); text_view = gtk_text_view_new(); gtk_window_set_default_size(GTK_WINDOW(window), 480, 300); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), scroll); gtk_container_add(GTK_CONTAINER(scroll), text_view); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view), GTK_WRAP_WORD); gtk_widget_show(text_view); gtk_widget_show(scroll); gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT); buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); state->view = view; state->messages = messages; state->dialog = GTK_DIALOG(window); state->buffer = buffer; g_signal_connect(window, "response", G_CALLBACK(message_view_dialog_response), state); state->handle_id = g_signal_connect(view->store, "row-changed", G_CALLBACK(message_view_dialog_change), state); message_view_dialog_response(GTK_DIALOG(window), LNOOP, state); gtk_widget_show_all(GTK_WIDGET(window)); } /******************** handlers for right click menu ********************/ static void message_view_popup_on_view_message_activate(GtkMenuItem * menuitem, gpointer user_data __attribute__ ((unused))) { message_view_t *v = g_object_get_data(G_OBJECT(menuitem), "view-object"); message_view_entire_message(v); } static void message_view_popup_on_find_terules_activate(GtkMenuItem * menuitem, gpointer user_data) { message_view_t *v = g_object_get_data(G_OBJECT(menuitem), "view-object"); toplevel_find_terules(v->top, (seaudit_message_t *) user_data); } static void message_view_popup_on_export_selected_messages_activate(GtkMenuItem * menuitem, gpointer user_data __attribute__ ((unused))) { message_view_t *v = g_object_get_data(G_OBJECT(menuitem), "view-object"); message_view_export_selected_messages(v); } static void message_view_popup_menu(GtkWidget * treeview, GdkEventButton * event, message_view_t * view, seaudit_message_t * message) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); gint num_selected_rows = gtk_tree_selection_count_selected_rows(selection); GtkWidget *menu, *menuitem, *menuitem2, *menuitem3; int button, event_time; menu = gtk_menu_new(); if (num_selected_rows == 1) { menuitem = gtk_menu_item_new_with_label("View Selected Message"); menuitem3 = gtk_menu_item_new_with_label("Export Selected Message..."); } else { menuitem = gtk_menu_item_new_with_label("View Selected Messages"); menuitem3 = gtk_menu_item_new_with_label("Export Selected Messages..."); } menuitem2 = gtk_menu_item_new_with_label("Find TERules using Message..."); g_signal_connect(menuitem, "activate", (GCallback) message_view_popup_on_view_message_activate, message); g_signal_connect(menuitem2, "activate", (GCallback) message_view_popup_on_find_terules_activate, message); g_signal_connect(menuitem3, "activate", (GCallback) message_view_popup_on_export_selected_messages_activate, NULL); g_object_set_data(G_OBJECT(menuitem), "view-object", view); g_object_set_data(G_OBJECT(menuitem2), "view-object", view); g_object_set_data(G_OBJECT(menuitem3), "view-object", view); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem2); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem3); gtk_widget_show_all(menu); if (toplevel_get_policy(view->top) == NULL) { gtk_widget_set_sensitive(menuitem2, FALSE); } if (event) { button = event->button; event_time = event->time; } else { button = 0; event_time = gtk_get_current_event_time(); } gtk_menu_attach_to_widget(GTK_MENU(menu), treeview, NULL); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, event_time); } static gboolean message_view_delayed_selection_menu_item(gpointer data) { message_view_t *view = (message_view_t *) data; toplevel_update_selection_menu_item(view->top); return FALSE; } static gboolean message_view_on_button_press(GtkWidget * treeview, GdkEventButton * event, gpointer user_data) { message_view_t *view = (message_view_t *) user_data; if (event->type == GDK_BUTTON_PRESS && event->button == 3) { GtkTreePath *path = NULL; GtkTreeIter iter; GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y, &path, NULL, NULL, NULL)) { return FALSE; } /* if the right click occurred on an unselected row, remove * all selections and select the item under the pointer */ if (!gtk_tree_selection_path_is_selected(selection, path)) { gtk_tree_selection_unselect_all(selection); gtk_tree_selection_select_path(selection, path); } message_view_store_get_iter(GTK_TREE_MODEL(view->store), &iter, path); /* popup a menu for the row that was clicked */ message_view_popup_menu(treeview, event, view, (seaudit_message_t *) iter.user_data); return TRUE; } else if (event->type == GDK_BUTTON_PRESS && event->button == 1) { /* n.b.: rows can be selected but never deselected. * delay updating the menu, for upon the first click * there is not a selection yet */ g_idle_add(&message_view_delayed_selection_menu_item, view); return FALSE; } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1){ /* Show message on double click */ message_view_entire_message(view); } return FALSE; } static void message_view_gtk_tree_path_free(gpointer data, gpointer user_data __attribute__ ((unused))) { gtk_tree_path_free((GtkTreePath *) data); } static gboolean message_view_on_popup_menu(GtkWidget * treeview, gpointer user_data) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); GList *glist = gtk_tree_selection_get_selected_rows(selection, NULL); message_view_t *view = (message_view_t *) user_data; GtkTreePath *path; GtkTreeIter iter; if (glist == NULL) { return FALSE; } path = g_list_nth_data(glist, 0); message_view_store_get_iter(GTK_TREE_MODEL(view->store), &iter, path); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); message_view_popup_menu(treeview, NULL, view, (seaudit_message_t *) iter.user_data); return TRUE; } static void message_view_on_row_activate(GtkTreeView * tree_view __attribute__ ((unused)), GtkTreePath * path __attribute__ ((unused)), GtkTreeViewColumn * column __attribute__ ((unused)), gpointer user_data) { message_view_t *view = (message_view_t *) user_data; toplevel_update_selection_menu_item(view->top); } /******************** other public functions below ********************/ message_view_t *message_view_create(toplevel_t * top, seaudit_model_t * model, const char *filename) { message_view_t *view; GtkTreeSelection *selection; GtkCellRenderer *renderer; size_t i; if ((view = calloc(1, sizeof(*view))) == NULL || (filename != NULL && (view->filename = strdup(filename)) == NULL)) { int error = errno; toplevel_ERR(top, "%s", strerror(error)); message_view_destroy(&view); errno = error; return NULL; } view->top = top; view->model = model; view->store = (message_view_store_t *) g_object_new(SEAUDIT_TYPE_MESSAGE_VIEW_STORE, NULL); view->store->view = view; view->store->sort_field = OTHER_FIELD; view->store->sort_dir = 1; view->w = gtk_scrolled_window_new(NULL, NULL); view->view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(view->store))); selection = gtk_tree_view_get_selection(view->view); gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); gtk_container_add(GTK_CONTAINER(view->w), GTK_WIDGET(view->view)); gtk_widget_show(GTK_WIDGET(view->view)); gtk_widget_show(view->w); renderer = gtk_cell_renderer_text_new(); for (i = 0; i < num_columns; i++) { struct view_column_record r = column_data[i]; PangoLayout *layout = gtk_widget_create_pango_layout(GTK_WIDGET(view->view), r.sample_text); gint width; GtkTreeViewColumn *column; pango_layout_get_pixel_size(layout, &width, NULL); g_object_unref(G_OBJECT(layout)); width += 12; column = gtk_tree_view_column_new_with_attributes(r.name, renderer, "text", r.id, NULL); gtk_tree_view_column_set_clickable(column, TRUE); gtk_tree_view_column_set_resizable(column, TRUE); if (r.sort != NULL) { g_object_set_data(G_OBJECT(column), "column id", GINT_TO_POINTER(r.id)); g_signal_connect_after(G_OBJECT(column), "clicked", G_CALLBACK(message_view_on_column_click), view); } gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_fixed_width(column, width); gtk_tree_view_append_column(view->view, column); } g_signal_connect(G_OBJECT(view->view), "button-press-event", G_CALLBACK(message_view_on_button_press), view); g_signal_connect(G_OBJECT(view->view), "popup-menu", G_CALLBACK(message_view_on_popup_menu), view); g_signal_connect(G_OBJECT(view->view), "row-activated", G_CALLBACK(message_view_on_row_activate), view); message_view_update_visible_columns(view); message_view_update_rows(view); return view; } void message_view_destroy(message_view_t ** view) { if (view != NULL && *view != NULL) { /* emit a signal to force all message view dialogs to disable their scroll buttons. need to pass a non-NULL path in the signal handler to make GTK shut up */ GtkTreePath *path = gtk_tree_path_new_first(); GtkTreeIter iter; gtk_tree_model_get_iter(GTK_TREE_MODEL((*view)->store), &iter, path); g_signal_emit_by_name((*view)->store, "row-changed", path, &iter); gtk_tree_path_free(path); seaudit_model_destroy(&(*view)->model); apol_vector_destroy(&((*view)->store->messages)); g_free((*view)->filename); g_free((*view)->export_filename); /* let glib handle destruction of object */ g_object_unref((*view)->store); free(*view); *view = NULL; } } seaudit_model_t *message_view_get_model(message_view_t * view) { return view->model; } void message_view_set_model(message_view_t * view, seaudit_model_t * model) { seaudit_model_destroy(&view->model); view->model = model; toplevel_update_tabs(view->top); message_view_update_rows(view); } GtkWidget *message_view_get_view(message_view_t * view) { return view->w; } size_t message_view_get_num_log_messages(message_view_t * view) { if (view->store->messages == NULL) { return 0; } return apol_vector_get_size(view->store->messages); } gboolean message_view_is_message_selected(message_view_t * view) { GtkTreeSelection *selection = gtk_tree_view_get_selection(view->view); GList *glist = gtk_tree_selection_get_selected_rows(selection, NULL); if (glist == NULL) { return FALSE; } g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); return TRUE; } void message_view_vector_gtk_tree_path_free(void *elem) { GtkTreePath *path = (GtkTreePath *) elem; gtk_tree_path_free(path); } void message_view_entire_message(message_view_t * view) { GtkTreeSelection *selection = gtk_tree_view_get_selection(view->view); GList *glist = gtk_tree_selection_get_selected_rows(selection, NULL); GList *l; apol_vector_t *messages; if (glist == NULL) { return; } if ((messages = apol_vector_create(message_view_vector_gtk_tree_path_free)) == NULL) { toplevel_ERR(view->top, "%s", strerror(errno)); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); return; } for (l = glist; l != NULL; l = l->next) { GtkTreePath *path = gtk_tree_path_copy((GtkTreePath *) l->data); if (path == NULL || apol_vector_append(messages, path) < 0) { toplevel_ERR(view->top, "%s", strerror(errno)); gtk_tree_path_free(path); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); apol_vector_destroy(&messages); return; } } /* the following function takes ownership of messages vector */ message_view_messages_vector(view, messages); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); } void message_view_save(message_view_t * view) { if (view->filename == NULL) { GtkWindow *parent = toplevel_get_window(view->top); char *path = util_save_file(parent, "Save View", NULL); if (path == NULL) { return; } view->filename = path; } if (seaudit_model_save_to_file(view->model, view->filename) < 0) { toplevel_ERR(view->top, "Error saving view: %s", strerror(errno)); } } void message_view_saveas(message_view_t * view) { GtkWindow *parent = toplevel_get_window(view->top); char *path = util_save_file(parent, "Save View As", view->filename); if (path == NULL) { return; } g_free(view->filename); view->filename = path; if (seaudit_model_save_to_file(view->model, view->filename) < 0) { toplevel_ERR(view->top, "Error saving view: %s", strerror(errno)); } } void message_view_modify(message_view_t * view) { if (modify_view_run(view->top, view)) { toplevel_update_status_bar(view->top); } } void message_view_clear(message_view_t * view) { size_t i; for (i = 0; i < apol_vector_get_size(view->store->messages); i++) { seaudit_message_t *m = apol_vector_get_element(view->store->messages, i); seaudit_model_hide_message(view->model, m); } message_view_update_rows(view); } /** * Write to a file all messages in the given vector. Upon success, * update the view object's export filename. * * @param view View containing messages to write. * @param path Destination to write file, overwriting existing files * as necessary. * @param messages Vector of seaudit_message_t. */ static void message_view_export_messages_vector(message_view_t * view, char *path, apol_vector_t * messages) { FILE *f = NULL; size_t i; g_free(view->export_filename); view->export_filename = path; if ((f = fopen(path, "w")) == NULL) { toplevel_ERR(view->top, "Could not open %s for writing.", path); goto cleanup; } for (i = 0; i < apol_vector_get_size(messages); i++) { seaudit_message_t *m = apol_vector_get_element(messages, i); char *s = seaudit_message_to_string(m); if (s == NULL || fprintf(f, "%s\n", s) < 0) { toplevel_ERR(view->top, "Error writing string: %s", strerror(errno)); goto cleanup; } free(s); } cleanup: if (f != NULL) { fclose(f); } } void message_view_export_all_messages(message_view_t * view) { GtkWindow *parent = toplevel_get_window(view->top); char *path = util_save_file(parent, "Export Messages", view->export_filename); apol_vector_t *messages = view->store->messages; if (path == NULL) { return; } message_view_export_messages_vector(view, path, messages); } void message_view_export_selected_messages(message_view_t * view) { GtkWindow *parent = toplevel_get_window(view->top); char *path; GtkTreeSelection *selection = gtk_tree_view_get_selection(view->view); GList *glist = gtk_tree_selection_get_selected_rows(selection, NULL); GList *l; apol_vector_t *messages; if (glist == NULL) { return; } path = util_save_file(parent, "Export Selected Messages", view->export_filename); if (path == NULL) { return; } if ((messages = apol_vector_create(NULL)) == NULL) { toplevel_ERR(view->top, "%s", strerror(errno)); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); return; } for (l = glist; l != NULL; l = l->next) { GtkTreePath *tree_path = (GtkTreePath *) l->data; GtkTreeIter iter; message_view_store_get_iter(GTK_TREE_MODEL(view->store), &iter, tree_path); if (apol_vector_append(messages, iter.user_data) < 0) { toplevel_ERR(view->top, "%s", strerror(errno)); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); apol_vector_destroy(&messages); return; } } message_view_export_messages_vector(view, path, messages); g_list_foreach(glist, message_view_gtk_tree_path_free, NULL); g_list_free(glist); apol_vector_destroy(&messages); } /** * Given the name of a column, return its column record data. */ static const struct view_column_record *get_record(const char *name) { size_t i; for (i = 0; i < num_columns; i++) { const struct view_column_record *r = column_data + i; if (strcmp(r->name, name) == 0) { return r; } } return NULL; } void message_view_update_visible_columns(message_view_t * view) { GList *columns, *c; preferences_t *prefs = toplevel_get_prefs(view->top); columns = gtk_tree_view_get_columns(view->view); c = columns; while (c != NULL) { GtkTreeViewColumn *vc = GTK_TREE_VIEW_COLUMN(c->data); const gchar *title = gtk_tree_view_column_get_title(vc); const struct view_column_record *r = get_record(title); if (preferences_is_column_visible(prefs, r->id)) { gtk_tree_view_column_set_visible(vc, TRUE); } else { gtk_tree_view_column_set_visible(vc, FALSE); } c = g_list_next(c); } g_list_free(columns); } void message_view_update_rows(message_view_t * view) { /* remove all existing rows, then insert them back into the * view according to the model. automatically scroll to the * same seleceted row(s). */ GtkTreeSelection *selection; GList *rows, *r, *selected = NULL; GtkTreePath *path; GtkTreeIter iter; seaudit_log_t *log; size_t i, num_old_messages = 0, num_new_messages = 0, num_changed; int first_scroll = 0; if (!seaudit_model_is_changed(view->model)) { return; } /* convert the current selection into a GList of message * pointers */ selection = gtk_tree_view_get_selection(view->view); rows = gtk_tree_selection_get_selected_rows(selection, NULL); for (r = rows; r != NULL; r = r->next) { path = (GtkTreePath *) r->data; message_view_store_get_iter(GTK_TREE_MODEL(view->store), &iter, path); selected = g_list_prepend(selected, iter.user_data); } g_list_foreach(rows, message_view_gtk_tree_path_free, NULL); g_list_free(rows); log = toplevel_get_log(view->top); if (view->store->messages != NULL) { num_old_messages = apol_vector_get_size(view->store->messages); } apol_vector_destroy(&view->store->messages); if (log != NULL) { view->store->messages = seaudit_model_get_messages(log, view->model); num_new_messages = apol_vector_get_size(view->store->messages); } gtk_tree_selection_unselect_all(selection); /* mark which rows have been changed/removed/inserted. do * this as a single pass, rather than a two pass * mark-and-sweep, for GTK+ tree views can be somewhat slow */ num_changed = num_old_messages; if (num_new_messages < num_changed) { num_changed = num_new_messages; } for (i = 0; i < num_changed; i++) { path = gtk_tree_path_new(); gtk_tree_path_append_index(path, i); iter.user_data = apol_vector_get_element(view->store->messages, i); iter.user_data2 = GINT_TO_POINTER(i); iter.user_data3 = view; gtk_tree_model_row_changed(GTK_TREE_MODEL(view->store), path, &iter); for (r = selected; r != NULL; r = r->next) { if (r->data == iter.user_data) { gtk_tree_selection_select_iter(selection, &iter); if (!first_scroll) { gtk_tree_view_scroll_to_cell(view->view, path, NULL, FALSE, 0.0, 0.0); first_scroll = 1; } break; } } gtk_tree_path_free(path); } if (num_old_messages > num_changed) { /* delete in reverse order, else indices get renumbered */ for (i = num_old_messages; i > num_changed; i--) { path = gtk_tree_path_new(); gtk_tree_path_append_index(path, i - 1); gtk_tree_model_row_deleted(GTK_TREE_MODEL(view->store), path); gtk_tree_path_free(path); } } else { for (; i < num_new_messages; i++) { path = gtk_tree_path_new(); gtk_tree_path_append_index(path, i); iter.user_data = apol_vector_get_element(view->store->messages, i); iter.user_data2 = GINT_TO_POINTER(i); iter.user_data3 = view; gtk_tree_model_row_inserted(GTK_TREE_MODEL(view->store), path, &iter); for (r = selected; r != NULL; r = r->next) { if (r->data == iter.user_data) { gtk_tree_selection_select_iter(selection, &iter); if (!first_scroll) { gtk_tree_view_scroll_to_cell(view->view, path, NULL, FALSE, 0.0, 0.0); first_scroll = 1; } break; } } gtk_tree_path_free(path); } } g_list_free(selected); }