/* $Id: prefs_report.c,v 1.9 2007-02-02 00:34:54 jan Exp $
 *
 * Copyright (C) 2004 by Intevation GmbH
 * Author(s):
 * Jan-Oliver Wagner <jan@intevation.de> (2004)
 *
 * This program is free software under the GNU GPL (>=v2)
 * Read the file COPYING coming with the software for details.
 *
 * In addition, as a special exception, Intevation GmbH gives
 * permission to link the code of this program with the OpenSSL
 * library (or with modified versions of OpenSSL that use the same
 * license as OpenSSL), and distribute linked combinations including
 * the two. You must obey the GNU General Public License in all
 * respects for all of the code used other than OpenSSL. If you
 * modify this file, you may extend this exception to your version
 * of the file, but you are not obligated to do so. If you do not
 * wish to do so, delete this exception statement from your version.
 */

/**
 * @file
 * Displays details of a report (right hand side of the gui if a report is
 * selected).
 * 
 * Therefore, holds all reported issues in a tree. The order and semantics
 * of the tree are changeable (Host/Port/Severity | Port/Host/Severity).
 * In each case the tree has a depth of 3, where the leafs are issues that
 * got a integer-mapped severity assigned (function severity_level).
 * 
 * A row selection triggers a textview on the right to display the messages and
 * list details ().
 * 
 * To fill the tree multiple queries have to be sent to the backend, one for
 * each layer in the tree (fill_tree_store).
 * 
 */


#include <includes.h>

#include <gtk/gtk.h>

#include "error_dlg.h"
#include "listnotebook.h"
#include "preferences.h"
#include "nessus_i18n.h"
#include "severity_filter.h"
#include "report_save.h"
#include "data_mining.h"
#include "treeview_support.h"
#include "nessus_plugin.h"
#include "nvt_pref_sshlogin.h" /* For convenience function text_combobox_set_active_text only */
#include "parser.h" /* For message types (MSG_HOLE ...) */
#include "severity_override_form.h"

extern struct arglist* MainDialog;

/** @brief The columns of the tree store we use. */
enum {
  /** What will be displayed in the treeview (depending on layer. port/host/...). */
  COL_NAME,

  /** The keyword for the report query (value stored in COL_NAME). */
  COL_KEY,

  /** The severity level. */
  COL_SEVERITY,

  /** If current row is an issue, has the severity been modified? */
  COL_BOOL_SEVERITY_MODIFIED,

  /** If issues are selected, store their line numbers to query backend. */
  COL_ISSUE_LINE_NR_LIST,

  /** Number of columns. */
  NUM_COLS
};


/** @brief The various ways to order the host/port/severity tree. */
static const char * tree_orders[][3] = {
  {"host", "port", "severity"},
  {"port", "host", "severity"},
};


/**
 * @brief Convert the severity string to an integer level.
 *
 * Mapping:
 *    "Security Hole"    -> 6
 *    "Security Warning" -> 5
 *    "Security Note"    -> 4
 *    "False positive"   -> 3
 *    "Log Message"      -> 2
 *    "Debug Message"    -> 1
 *    anything else      -> 0
 * 
 * @param severity String describing the severity (e.g. "Security Hole").
 * 
 * @return 'Integer level' that matches the severity string (0 if no match).
 */
static int
severity_level (const char *severity)
{
  static const char * levels[] = {"Debug", "Log", "False", "Note", "Warning", "Hole"};
  int i;

  for (i = 6; i >= 1; i--)
  {
    if (strstr(severity, levels[i - 1]))
      return i;
  }

  return 0;
}


/**
 * @brief Our comparison function, compare hosts.
 */
static int
cmp_hosts(char * a, char * b)
{
  struct in_addr ia, ib;

  if(!a && !b)return 0;

  if(!a)
    return 1;
  if(!b)
    return -1;

  if(inet_aton(a, &ia) == 0)
    return strcmp(a, b);

  if(inet_aton(b, &ib) == 0)
  {
    return strcmp(a, b);
  }

  return -(ntohl(ia.s_addr) - ntohl(ib.s_addr));
}


/**
 * @brief Our sort functions. Compare severity levels from severity_level
 * @brief (const char*).
 * 
 * ("Security Hole" > "Security Warning" > "Security Note" > anything else )
 */
static int
cmp_vulns (char * a, char * b)
{
  if(!a)
    return -1;
  else if(!b)
    return 1;

  return severity_level(a) - severity_level(b);
}

/*
 * Utilities
 */

/**
 * @brief Converts a multiple subset (with field AND severity) to a sorted,
 * @brief uniq'ed one.
 *
 * @return The sorted and uniq'ed subset. (Sorted by hosts, severity).
 */
static struct subset *
sort_uniq (struct subset * subset)
{
  cmp_func_t cmps1[] = {cmp_hosts, cmp_vulns};
  cmp_func_t cmps2[] = {cmp_vulns};
  return subset_sort(
      subset_uniq(subset_sort(subset, 0, 1, cmps1), 0), 1, 1, cmps2);
}


/**
 * @brief Update the label at the bottom of the report frame with
 * @brief information about when the scan happened.
 */
void
prefs_report_update_timestamp (struct arglist *ctrls)
{
  GtkWidget *scan_timestamps = arg_get_value(ctrls, "SCAN_TIMESTAMPS");
  int be = GPOINTER_TO_SIZE(arg_get_value(ctrls, "BE"));
  char *str;

  if(be < 0)
    str = g_strdup("");
  else
  {
    struct subset *start_subset = query_backend(be,
	"SELECT date FROM timestamps WHERE type = 'scan_start'");
    struct subset *end_subset = query_backend(be,
	"SELECT date FROM timestamps WHERE type = 'scan_end'");
    char *start = NULL, *end = NULL;

    if(subset_size(start_subset))
      start = subset_value(start_subset);
    if(subset_size(end_subset))
      end = subset_value(end_subset);

    if(start && end)
      str = g_strdup_printf(_("Scan took place from %s to %s"), start, end);
    else if(start)
      str = g_strdup_printf(_("Scan started on %s"), start);
    else if(end)
      str = g_strdup_printf(_("Scan finished on %s"), end);
    else
      str = g_strdup_printf(_("Time of scan not available."));

    subset_free(start_subset);
    subset_free(end_subset);
  }
  gtk_label_set_text(GTK_LABEL(scan_timestamps), str);
  gtk_misc_set_alignment((GtkMisc *)scan_timestamps, 0, 1);
  g_free(str);
}

/**
 * @brief Returns overridden severity for a certain reported issue.
 * 
 * @return If a filter applies, the severity that the displayed one was mapped
 *         from, NULL otherwise (has to be freed with g_free if non-NULL).
 */
static gchar*
mapped_from (int be, unsigned int line_nr)
{
  if (Context->is_severity_mapped == FALSE)
    return NULL;

  gchar* result_str = NULL;
  Context->is_severity_mapped = FALSE;

  struct subset* result = report_query_single_by_line (be, line_nr);
  const char* mapped_to = severity_filter_apply (subset_nth_value (result, 0), // host
                                                  subset_nth_value (result, 1), // port
                                                  subset_nth_value (result, 2), // oid
                                                  subset_nth_value (result, 3));// orig. severity
  if (mapped_to != NULL)
    result_str = g_strdup (subset_nth_value (result, 3));

  subset_free (result);
  Context->is_severity_mapped = TRUE;

  return result_str;
}

/**
 * @brief Fill the tree_store with the data from a report.
 * 
 * @param tree_store The tree_store to fill.
 * @param be         Backend-index for backend to query.
 */
static void
fill_tree_model (GtkTreeModel * tree_store, int be, const char** sort_keys,
                 int max_depth, int depth, GtkTreeIter * parent,
                 const char ** restriction_keys, const char ** restriction_values)
{
  struct subset * subset = NULL;
  struct subset * walk   = NULL;
  gchar* last_added_sev  = NULL;
  GSList* msg_id_list;
  GtkTreeIter iter;

  static char * query_patterns[] = {
    "SELECT %s,severity FROM results",
    "SELECT %s,severity FROM results WHERE %s = '%s'",
    "SELECT %s,severity,plugin_oid FROM results WHERE %s = '%s' AND %s = '%s'",
    "SELECT %s,severity FROM results WHERE %s = '%s' AND %s = '%s' AND %s = '%s'",
  };

  subset = query_backend (be, query_patterns[depth], sort_keys[depth],
                          restriction_keys[0], restriction_values[0],
                          restriction_keys[1], restriction_values[1],
                          restriction_keys[2], restriction_values[2]);
  if (depth != 2)
    subset = sort_uniq (subset);
  else
    {
      cmp_func_t cmps[] = {cmp_vulns};
      subset = subset_sort (subset, 0, 0, cmps);
    }

  walk = subset;
  while (walk)
    {
      char * name     = subset_nth_value (walk, 0);
      char * severity = subset_nth_value (walk, 1);

      // Leaves are uniqed here by hand, to allow inspection
      if (depth != 2 || last_added_sev == NULL || strcmp (last_added_sev, name) )
        {
          gtk_tree_store_append (GTK_TREE_STORE(tree_store), &iter, parent);
          gtk_tree_store_set    (GTK_TREE_STORE(tree_store), &iter,
                                 COL_NAME, name,
                                 COL_KEY, sort_keys[depth],
                                 COL_SEVERITY, severity ? severity_level(severity) : 0,
                                 -1);
          last_added_sev = name;
        }

      if (depth == 2)
        {
          // Add the index to the list of contained severities for this leave in the tree. 
          gtk_tree_model_get (tree_store, &iter,
                              COL_ISSUE_LINE_NR_LIST, &msg_id_list, -1);
          GSList* list = g_slist_prepend (msg_id_list, GUINT_TO_POINTER (walk->id_line) );
          gtk_tree_store_set (GTK_TREE_STORE(tree_store), &iter,
                              COL_ISSUE_LINE_NR_LIST, list, -1);
          // Check if severity_override was applied  and 
          // set COL_BOOL_SEVERITY_MODIFIED accordingly
          if (mapped_from (be, walk->id_line) != NULL)
            gtk_tree_store_set (GTK_TREE_STORE(tree_store), &iter,
                                  COL_BOOL_SEVERITY_MODIFIED, TRUE,
                                  -1);
        }

      // Go down, fill the rest
      if (depth < max_depth)
        {
          restriction_keys[depth]   = sort_keys[depth];
          restriction_values[depth] = name;
          fill_tree_model (tree_store, be, sort_keys, max_depth, depth + 1,
                           &iter, restriction_keys, restriction_values);
        }

      walk = subset_next (walk);
    }

  subset_free(subset);
}


/**
 * @brief Clear the widget that displays the report text (set empty string).
 */
static void
clear_report_text(struct arglist *ctrls)
{
  GtkWidget * textview  = arg_get_value ((struct arglist*)ctrls, "REPORT");
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(textview));

  gtk_text_buffer_set_text (buffer, "", -1);
}


/**
 * @brief Update the report view.
 *
 * Call this function when the user selects a new report. This function
 * is relatively expensive because it rebuilds the entire
 * host/port/severity tree.
 * 
 * @param override_global If TRUE, the order that is selected in the order-combox
 *                        is used. If FALSE the is taken from the preferences.
 */
void
prefs_report_update (struct arglist *ctrls, gboolean override_global)
{
  GtkTreeView * tree_view;
  GtkTreeModel * tree_store;
  GtkWidget * order_combobox;
  int order_index;
  int be = GPOINTER_TO_SIZE(arg_get_value(ctrls, "BE"));
  const char *restriction_keys[3];
  const char *restriction_values[3];

  if (be < 0)
    return;

  clear_report_text(ctrls);

  tree_view  = GTK_TREE_VIEW  (arg_get_value(ctrls, "REPORT_TREE_VIEW"));
  tree_store = GTK_TREE_MODEL (gtk_tree_store_new (NUM_COLS,
                                                   G_TYPE_STRING,  // COL_NAME
                                                   G_TYPE_STRING,  // COL_KEY
                                                   G_TYPE_INT,     // COL_SEVERITY
                                                   G_TYPE_BOOLEAN, // COL_BOOL_SEVERITY_MODIFIED
                                                   G_TYPE_POINTER  // COL_ISSUE_LINE_NR_LIST
                                                   ));
  order_combobox = arg_get_value(ctrls, "ORDER_COMBO_BOX");
  if (override_global == TRUE)
    {
      order_index = gtk_combo_box_get_active(GTK_COMBO_BOX(order_combobox));
    }
  else
    {
      order_index = prefs_get_int(Global, "sort_order");
      gtk_combo_box_set_active(GTK_COMBO_BOX(order_combobox), order_index);
    }

  fill_tree_model (tree_store, be, tree_orders[order_index], 2, 0, NULL,
                   restriction_keys, restriction_values);

  gtk_tree_view_set_model(tree_view, tree_store);

  prefs_report_update_timestamp(ctrls);
}


/**
 * @brief Handler for the row-activated signal from the tree view.
 *
 * If the activated row is one at level 3 where host, port and severity
 * are known, display the corresponding rgtk_tree_view_column_set_attributeseport in the report text
 * widget.
 */
static void
row_activated (GtkTreeView *treeview, GtkTreePath *path,
               GtkTreeViewColumn *column, gpointer user_data)
{
  GtkTreeModel * model = gtk_tree_view_get_model(treeview);
  GtkTreeIter iter, parent;
  const char *keys[3];
  const char *values[3];
  int depth = 0;
  int has_parent;
  struct context *context = arg_get_value((struct arglist*)user_data, "REPORT_CONTEXT");
  char * plugin_oid = NULL;
  struct nessus_plugin *plugin;
  GSList* issues    = NULL;
  GSList* issue_ids;

  // Pick up list with "ids" of reported security messages
  if (TRUE)
  {
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get (model, &iter, COL_ISSUE_LINE_NR_LIST, &issue_ids, -1);
  }

  if (!gtk_tree_model_get_iter (model, &iter, path))
    {
      fprintf(stderr, "row_activated: could not set iter from path\n");
      return;
    }

  // Pick up parents values
  do
    {
      gtk_tree_model_get (model, &iter, COL_NAME, values + depth,
                          COL_KEY, keys + depth, -1);

      /* the parent iter and the child iter passed to
      * gtk_tree_model_iter_parent must not be the same (that would work
      * in GTK 2.0 but not in e.g. 2.6) */
      has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
      iter = parent;

      depth += 1;
    }
  while (depth < 3 && has_parent);

  /* the user has to click on an item on the deepest level so that all
   * necessary parameters are defined and the tree must not be deeper
   * than expected.  This means that depth has to be 3 now and the last,
   * i.e. topmost, node we looked must not have a parent. */
  if (depth == 3 && !has_parent)
  {
    struct subset * subset;
    struct subset * walk;
    GtkWidget * textview  = arg_get_value ((struct arglist*) user_data, "REPORT");
    GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(textview));
    int be = GPOINTER_TO_SIZE (arg_get_value((struct arglist*)user_data, "BE"));
    GtkTextIter iter;

#ifdef DEBUG
    GSList* walker = issue_ids;
    while (walker)
      {
        //printf ("Query id_line %d for this row.\n", GPOINTER_TO_UINT (walker->data));
        walker = walker->next;
      }
#endif

    if (be < 0) return;

    clear_report_text(user_data);

    subset = report_query_all_by_line (be, issue_ids);

    walk = subset;

    while(walk)
    {
      plugin_oid = subset_nth_value (walk, 2);
      // Get reference to plugin
      plugin = nessus_plugin_get_by_oid (context->plugins, plugin_oid);
      if (plugin == NULL)
        plugin = nessus_plugin_get_by_oid (context->scanners, plugin_oid);
      if (plugin == NULL)
        fprintf (stderr, "prefs_report.row_activated: no plugin with id %s in cache!\n",
                          plugin_oid);

      // Add NVT name to report view text if preference is set so.
      if (((prefs_get_int(Global, "show_nvt_name_and_oid")) == 1) &&
            ((prefs_get_int(Global, "reports_use_plugin_cache")) == 1)
           && plugin != NULL)
        {
          gtk_text_buffer_get_end_iter (buffer, &iter);
          gchar* text = g_strdup_printf (_("Reported by NVT \"%s\" (%s):\n\n"), plugin->name, plugin->oid);
          gtk_text_buffer_insert (buffer, &iter, text, -1);
          g_free (text);
        }

      gchar* mappedfrom = mapped_from (be, walk->id_line);
      if (mappedfrom != NULL)
        {
          gtk_text_buffer_get_end_iter (buffer, &iter);
          gchar* text = g_strdup_printf (_("The severity of this NVT has been mapped from %s!\n\n"), mappedfrom);
          gtk_text_buffer_insert (buffer, &iter, text, -1);
          g_free (text);
          g_free (mappedfrom);
        }

      gtk_text_buffer_get_end_iter (buffer, &iter);
      /* Field "report" */
      gtk_text_buffer_insert (buffer, &iter, subset_nth_value (walk, 4), -1);

      // Add selected issue to a list, so that we can always find out which
      // items are viewed by the user
      severity_override_t* issue = emalloc (sizeof (severity_override_t));
      // Set host, port, severity
      int i;
      for (i = 0 ; i < 3 ; i ++)
        {
          if (!strcmp (keys[i], "host"))
            issue->host = g_strdup (values[i]);
          else if (!strcmp (keys[i], "port"))
            issue->port = g_strdup (values[i]);
          else if (!strcmp (keys[i], "severity"))
            issue->severity_from = g_strdup (values[i]);
        }
      issue->severity_to = g_strdup ("");
      issue->reason  = g_strdup ("");
      issue->name    = g_strdup (plugin->name);
      issue->OID     = g_strdup (plugin_oid);

      issues = g_slist_prepend (issues, issue);

      walk = subset_next(walk);

      if (walk)
        {
          gtk_text_buffer_get_end_iter (buffer, &iter);
          gtk_text_buffer_insert (buffer, &iter, "\n" "=================================="
                                                      "==================================" "\n", -1);
        }
    }
    subset_free(subset);
  }
  else
    {
      clear_report_text(user_data);
    }

  GSList *old_issues = arg_get_value((struct arglist*)user_data, "SELECTED_ISSUES");

  GSList* walk = old_issues;
  while (walk)
    {
      severity_override_free (walk->data);
      walk = g_slist_next (walk);
    }
  g_slist_free (old_issues);

  // Set new list as currently selected issues
  arg_set_value ((struct arglist*) user_data , "SELECTED_ISSUES", -1, issues);
}


/**
 * @brief Called whenever the selection changes.
 * 
 * If the row is to be selected call row_activated so that the corresponding
 * report is shown.
 * 
 * @return TRUE, as all rows are OK to be selected.
 */
static gboolean
selection_func (GtkTreeSelection *selection, GtkTreeModel *model,
                GtkTreePath *path, gboolean path_currently_selected, gpointer user_data)
{
  GtkTreeIter iter;

  if(!path_currently_selected &&
      !gtk_tree_selection_get_selected(selection, NULL, NULL))
    {
      gtk_tree_model_get_iter(model, &iter, path);
      row_activated (gtk_tree_selection_get_tree_view(selection), path,
          NULL, user_data);
    }

  /* Allow selection state to change */
  return TRUE;
}

/**
 * @brief Sets ap ixmap of the cell renderer if severity for any issue in this
 * @brief row has been overriden.
 * 
 * @param cell The cellrenderer whose pixbuf to set.
 */
static void
isoverriden_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
                    GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
  GdkPixbuf *pixbuf;
  gboolean override;

  gtk_tree_model_get (model, iter, COL_BOOL_SEVERITY_MODIFIED, &override, -1);

  // Show "severity overriden" icon
  if (override)
    pixbuf = gtk_widget_render_icon (GTK_WIDGET(data), GTK_STOCK_CONVERT,
                                     GTK_ICON_SIZE_MENU, NULL);
  // No icon
  else
      pixbuf = NULL;

  g_object_set(G_OBJECT(cell), "pixbuf", pixbuf, NULL);

  if (pixbuf != NULL)
    g_object_unref(G_OBJECT(pixbuf));
}


/**
 * @brief Set the pixmap of the cell renderer from the severity of the current
 * @brief cell (e.g. warning icon).
 * 
 * @param cell The cellrenderer whose pixbuf to set.
 */
static void
severity_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
                    GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
  static const char* severity_stock_ids[] = {NULL, "gtk-find", "gtk-edit",     // debug, log
                                             "gtk-about", "nessus-info",        // false pos, note
                                             "nessus-warning", "nessus-error"}; // warning, Hole
  GdkPixbuf *pixbuf;
  int level;

  gtk_tree_model_get (model, iter, COL_SEVERITY, &level, -1);

  // Show icon of that severity level
  if (level >= 1)
      pixbuf = gtk_widget_render_icon(GTK_WIDGET(data),
                  severity_stock_ids[level], GTK_ICON_SIZE_MENU, NULL);
  // No icon
  else
      pixbuf = NULL;

  g_object_set(G_OBJECT(cell), "pixbuf", pixbuf, NULL);

  if (pixbuf != NULL)
    g_object_unref(G_OBJECT(pixbuf));
}


/**
 * @brief Signal handler for the "changed" signal of the tree order combobox.
 * 
 * Simply calls prefs_report_update to update the tree.
 */
static void
order_combobox_changed(GtkComboBox *combobox, gpointer user_data)
{
  prefs_report_update (user_data, TRUE);
}


/**
 * @brief Creates a form showing and allowing modification of details of
 * @brief a severity_override.
 * 
 * @param override Override to show.
 * 
 * @return The override form (use its vbox to display).
 */
static severity_override_form_t*
create_override_page (severity_override_t* override)
{
  severity_override_form_t* form = severity_override_form_from_override (override, SOF_ADD);
  return form;
}


/**
 * @brief Opens the dialog to select, edit and add severity_overrides from
 * @brief the list of selected issues (in the ctrls).
 */
static void
add_override_click (GtkMenuItem* widget, gpointer user_data)
{
  struct arglist* report = arg_get_value (MainDialog, "REPORT");
  GtkWindow *window      = GTK_WINDOW (arg_get_value (MainDialog, "WINDOW"));
  GSList* issues = arg_get_value (report, "SELECTED_ISSUES");
  GSList* issue  = NULL;
  GSList* form   = NULL;
  GSList* forms  = NULL;
  severity_override_t* override;

  GtkWidget* dialog = NULL;
  gint response;
  GtkWidget* listnotebook;

  if (issues == NULL)
    {
      show_warning (_("Nothing selected that could be filtered!"));
      return;
    }

  dialog = gtk_dialog_new_with_buttons(_("Add severity override"),
                                         window, GTK_DIALOG_DESTROY_WITH_PARENT,
                                         GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
                                         NULL);

  listnotebook = listnotebook_new (TRUE, TRUE);
  issue = issues;
  while (issue != NULL)
    {
      override = issue->data;

      severity_override_form_t* curform = create_override_page (override);
      forms = g_slist_prepend (forms, curform);

      listnotebook_add_page (listnotebook, curform->vbox, override->name, NULL);
      issue = g_slist_next (issue);
    }

  gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), listnotebook, FALSE, FALSE, 0);

  gtk_widget_show_all (dialog);

  // Run the dialog
  response = gtk_dialog_run (GTK_DIALOG (dialog));

  // Free the forms
  form = forms;
  while (form)
    {
      severity_override_form_free (form->data);
      form = g_slist_next (form);
    }

  gtk_widget_destroy (dialog);
}

/**
 * @brief Asks the user to select a file and exports the textviews current
 * @brief context.
 * 
 * @param widget    Ignored (callback).
 * @param user_data Ignored (callback).
 * 
 * Does hard checking for certain strings, usable just in a very specific
 * setting.
 * 
 * @TODO Consider implementation as TextBufferSerializeFunc
 */
static void
gshb_export_click (GtkMenuItem* widget, gpointer user_data)
{
  struct arglist* guiargs = arg_get_value (MainDialog, "REPORT");
  GtkWidget * textview    = arg_get_value (guiargs, "REPORT");
  GtkTextBuffer* textbuffer = NULL;
  GtkWidget *file_dialog;
  GtkTextIter startiter;
  GtkTextIter enditer;
  int fd;
  gsize text_length;
  int written = 0;
  gchar* buffer;
  gchar* before_header;
  gchar* proposed_filename;
  gchar* filename = NULL;
  severity_override_t* issue = NULL;
  GSList* overrides = NULL;

  // Find out which target was scanned
  overrides = arg_get_value (guiargs, "SELECTED_ISSUES");
  if (overrides == NULL 
      || (issue = (severity_override_t*) overrides->data) == NULL)
    {
      show_error (_("Could not export the selected report item as a GSHB report."));
      return;
    }

  proposed_filename = g_strdup_printf ("openvas-gshbt-%s.txt", issue->host);

  // Get the user-defined file name from a filechooser
  file_dialog = gtk_file_chooser_dialog_new (_("Export to"),
                                              GTK_WINDOW(arg_get_value(MainDialog, "WINDOW")),
                                              GTK_FILE_CHOOSER_ACTION_SAVE,
                                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                              GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                              NULL);
  gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (file_dialog), TRUE);
  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_dialog), proposed_filename);

  if (gtk_dialog_run (GTK_DIALOG (file_dialog)) != GTK_RESPONSE_ACCEPT)
    {
      gtk_widget_destroy (file_dialog);
      // NTBD
      g_free (proposed_filename);
      return;
    }
  g_free (proposed_filename);

  filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_dialog));

  textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(textview));
  gtk_text_buffer_get_start_iter (textbuffer, &startiter);
  gtk_text_buffer_get_end_iter   (textbuffer, &enditer);
  buffer = gtk_text_buffer_get_text (textbuffer, &startiter, &enditer, TRUE);

  // We expect some garbage, then: "ID"|"Status"|"Description"|
  before_header = g_strstr_len (buffer, strlen(buffer),
                                "\"ID\"|\"Status\"|\"Description\"|");

  if (before_header == NULL)
    {
      show_error (_("Message does not contain a valid GSHB report."));
      gtk_widget_destroy (file_dialog);
      g_free (filename);
      g_free (buffer);
      return;
    }

  // Open a file to write content to.
  // (with GLIB >= 2.8 we can use file_set_contents)
  fd = open (filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
  if (!fd)
    {
      gtk_widget_destroy (file_dialog);
      show_error (_("Export failed, could not open file %s."), filename);
      g_free (buffer);
      g_free (filename);
      return;
    }

  // Write content
  text_length = strlen (before_header);
  written = write (fd, before_header, text_length);

  // Clean up
  close (fd);
  if (written != text_length)
    {
      show_error (_("Export failed, could write only incomplete data (%d/%d)."), written, text_length);
    }

  gtk_widget_destroy (file_dialog);
  g_free (buffer);
  g_free (filename);
  return ;
}

/**
 * @brief Create and show a popup menu.
 */
void
show_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
  GtkWidget *menu;
  GtkWidget *menuitem;
  GtkTreeSelection *selection;
  GtkTreeModel     *model;
  GtkTreeIter       iter;
  GtkTreeIter       parent;
  GtkTreePath*      treepath;
  gboolean is_gshb_exportable = FALSE;

  // Find out whether we can gshb-export any of the selected data
  // use gtk_tree_path_get_depth // gtk_tree_model_get_path
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
  gtk_tree_selection_get_selected (selection, &model, &iter);
  treepath = gtk_tree_model_get_path (model, &iter);
  if (gtk_tree_path_get_depth (treepath) == 3)
    {
      gchar *name;
      gchar* key;
      gtk_tree_model_iter_parent (model, &parent, &iter);

      // Ordering matters, we need to know the "port" (general/GSHB)
      gtk_tree_model_get (model, &parent, COL_KEY, &key, -1);
      if (!strcmp (key, "port"))
        ;
      else
        {
          // Cannot assign parent 'in-place' (gtk_tree_model_iter_parent (model, &parent, &parent);)
          GtkTreeIter tmp;
          gtk_tree_model_iter_parent (model, &tmp, &parent);
          parent = tmp;
        }

      gtk_tree_model_get (model, &parent, COL_NAME, &name, -1);
      is_gshb_exportable = (g_strstr_len (name, strlen(name), "general/GSHB") == NULL) ? FALSE
                                                                                : TRUE;
      gtk_tree_model_get (model, &parent, COL_KEY, &name, -1);

      g_free (name);
      g_free (key);
    }

  menu = gtk_menu_new();

  // Severities item
  menuitem = gtk_menu_item_new_with_label(_("Severities..."));
  gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem);
  gtk_widget_set_sensitive (menuitem, !Context->is_severity_mapped);
  g_signal_connect(G_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(add_override_click), NULL);

  // Export GSHB item
  if (is_gshb_exportable)
    {
      menuitem = gtk_menu_item_new_with_label(_("Export GSHB Table..."));
      gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem);
      gtk_widget_set_sensitive (menuitem, is_gshb_exportable);
      g_signal_connect(G_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(gshb_export_click), NULL);
    }

  gtk_widget_show_all(menu);

  /* Note: event can be NULL here when called from view_onPopupMenu;
    *  gdk_event_get_time() accepts a NULL argument */
  gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL,
                  (event != NULL) ? event->button : 0,
                  gdk_event_get_time((GdkEvent*)event));
}


/**
 * @brief Shows a popup menu.
 */
static gboolean
show_popup (GtkWidget *treeview, gpointer userdata)
{
  show_popup_menu(treeview, NULL, userdata);
  return TRUE; /* we handled this */
}

/**
 * @brief Shows a popup menu if registered a right mouse button click.
 */
static gboolean
mouse_button_press (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
  /* Single click with the right mouse button? */
  if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
    {
        GtkTreeSelection *selection;

        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
        GtkTreePath *path;

        /* Get tree path for row that was clicked */
        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
                                          (gint) event->x,
                                          (gint) event->y,
                                          &path, NULL, NULL, NULL))
          {
            gtk_tree_selection_unselect_all(selection);
            gtk_tree_selection_select_path(selection, path);
            gtk_tree_path_free(path);
            show_popup_menu(treeview, event, userdata);
          }
      return TRUE; /* We did handle this -> we did a selection via rmb ourselves */
    }

  return FALSE; /* we did not handle this */
}

/**
 * @brief Create the report widgets.
 */
struct arglist *
prefs_dialog_report (void)
{
  GtkWidget *vbox;
  GtkWidget *tree;
  GtkWidget *tree_window;
  GtkWidget *report_window;
  GtkWidget *scan_timestamps;
  GtkWidget *hpaned;
  GtkWidget *report;
  GtkWidget *tree_box;
  GtkWidget *order_combobox;
  GtkTreeSelection *selection;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  struct arglist * ctrls = emalloc(sizeof(struct arglist));

  arg_add_value(ctrls, "BE", ARG_INT, sizeof(int), (void *)-1);
  arg_add_value(ctrls, "REPORT_CONTEXT", ARG_PTR, -1, (void *)NULL);

  vbox = gtk_vbox_new(FALSE, FALSE);
  arg_add_value(ctrls, "VBOX", ARG_PTR, -1, vbox);
  gtk_widget_show(vbox);

  hpaned = gtk_hpaned_new();
  gtk_paned_set_position(GTK_PANED(hpaned), 200);
  gtk_widget_show(hpaned);
  gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);

  tree_box = gtk_vbox_new(FALSE, FALSE);
  gtk_widget_show(tree_box);
  gtk_container_add(GTK_CONTAINER(hpaned), tree_box);

  /* The tree order combobox
   * NOTE: the order of the menu items should match the order of the
   * elements of tree_orders */
  order_combobox = gtk_combo_box_new_text ();
  arg_add_value(ctrls, "ORDER_COMBO_BOX", ARG_PTR, -1, order_combobox);
  gtk_widget_show(order_combobox);
  gtk_box_pack_start(GTK_BOX(tree_box), order_combobox, FALSE, FALSE, 0);
  gtk_combo_box_append_text(GTK_COMBO_BOX(order_combobox),
                            _("Host/Port/Severity"));
  gtk_combo_box_append_text(GTK_COMBO_BOX(order_combobox),
                            _("Port/Host/Severity"));
  g_signal_connect(G_OBJECT(order_combobox), "changed",
                   G_CALLBACK(order_combobox_changed), ctrls);
  gtk_combo_box_set_active(GTK_COMBO_BOX(order_combobox), prefs_get_int(Global, "sort_order"));

  /* Scrolled window for the tree */
  tree_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tree_window),
      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(tree_window),
      GTK_SHADOW_IN);
  gtk_box_pack_start(GTK_BOX(tree_box), tree_window, TRUE, TRUE, 0);
  gtk_widget_show(tree_window);

  /* The host/ports/severities tree and its columns and renderers */
  tree = gtk_tree_view_new();
  gtk_widget_show(tree);
  arg_add_value(ctrls, "REPORT_TREE_VIEW", ARG_PTR, -1, tree);
  gtk_container_add(GTK_CONTAINER(tree_window), tree);
  gtk_widget_show(tree);
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);

  // Create Column
  column = gtk_tree_view_column_new();
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

  // Create and register the various renderers.
  // Render icon if severity is overriden
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(column, renderer, FALSE);
  gtk_tree_view_column_set_cell_data_func (column, renderer, isoverriden_data_func,
                                           tree, NULL);

  // Severity icon
  renderer = gtk_cell_renderer_pixbuf_new();
  gtk_tree_view_column_pack_start(column, renderer, FALSE);
  gtk_tree_view_column_set_cell_data_func(column, renderer, severity_data_func,
      tree, NULL);

  // Text renderer
  renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_set_attributes(column, renderer, "text", COL_NAME, NULL);
  gtk_tree_view_column_set_resizable(column, TRUE);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
  gtk_tree_selection_set_select_function(selection,
      selection_func, ctrls, NULL);

  g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated),
      ctrls);
  g_signal_connect(G_OBJECT(tree), "key-press-event",
                   G_CALLBACK(onKeypressed), NULL);

  /* Add popup-menu support */
  g_signal_connect (G_OBJECT(tree), "button-press-event", (GCallback) mouse_button_press, NULL);
  g_signal_connect (G_OBJECT(tree), "popup-menu", (GCallback) show_popup, NULL);

  /* The "report widget" (contains the text) in a scolled window */
  report_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_widget_ref(report_window);
  gtk_widget_show(report_window);
  gtk_container_add(GTK_CONTAINER(hpaned), report_window);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(report_window),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  report = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(report), FALSE);
  gtk_widget_show(report);
  gtk_scrolled_window_add_with_viewport(
    GTK_SCROLLED_WINDOW(report_window), report);
  arg_add_value(ctrls, "REPORT", ARG_PTR, -1, report);

  // List holding currently selected issues (~ security messages), if any.
  GSList* selected_issues = NULL;
  arg_add_value(ctrls, "SELECTED_ISSUES", ARG_PTR, -1, selected_issues);

  /* Timestamp label */
  scan_timestamps = gtk_label_new("");
  gtk_widget_show(scan_timestamps);
  gtk_box_pack_start(GTK_BOX(vbox), scan_timestamps, FALSE, FALSE, 2);
  arg_add_value(ctrls, "SCAN_TIMESTAMPS", ARG_PTR, -1, scan_timestamps);

  return ctrls;
}
