/* Nessus
 * Copyright (C) 1998, 1999, 2000 Renaud Deraison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * In addition, as a special exception, Renaud Deraison
 * gives permission to link the code of this program with any
 * version of the OpenSSL library which is distributed under a
 * license identical to that listed in the included COPYING.OpenSSL
 * file, 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.
 */

#include <includes.h>

#include "../nessus_i18n.h"
#include "context.h"

#include "error_dlg.h"

#ifdef USE_GTK
#include <gtk/gtk.h>
#include "readonly.h"

/**
 * @brief Reset the rules tree to the three default groups
 */
static void
reset_rules_tree (struct arglist * ctrls)
{
  GtkWidget * tree;
  GtkTreeStore * store;
  GtkTreeIter iter;

  tree = arg_get_value(ctrls, "RULES_TREE");
  store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));

  gtk_tree_store_clear(store);
  /* fill the tree with the 3 default groups */
  gtk_tree_store_append(store, &iter, NULL);  /* Acquire a top-level iterator */
  gtk_tree_store_set(store, &iter, 0,
    _("Server rules (priority over user rules): can not be shown"), -1);
  gtk_tree_store_append(store, &iter, NULL);  /* Acquire a top-level iterator */
  gtk_tree_store_set(store, &iter, 0,
    _("Serverside user rules (priority over clientside user rules)"), -1);
  gtk_tree_store_append(store, &iter, NULL);  /* Acquire a top-level iterator */
  gtk_tree_store_set(store, &iter, 0,
    _("Clientside user rules"), -1);
}

/**
 * @brief Fill the treelist widget of the rules page with the serverside user 
 * @brief rules and with the clientside user rules.
 */
void
fill_rules_list (struct context *context, struct arglist *ctrls)
{
  GtkWidget * tree;
  GtkTreeStore * store;
  GtkTreePath * path;
  GtkTreeIter iter, parent;
  struct arglist * p = arg_get_value(context->prefs, "SERVER_PREFS");
  char * rule;

  /* first of all, reset the tree */
  reset_rules_tree(ctrls);

  if (!p)
    return; /* something is not in order */

  /* Get the objects */
  tree = arg_get_value(ctrls, "RULES_TREE");
  store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));

  /* go to the second entry */
  path = gtk_tree_path_new_from_string("1");
  gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent, path);

  /* Append the serverside user rules */
  p = arg_get_value(p, "RULES");
  while (p && p->next)
    {
      rule = emalloc(strlen((char *)p->value)+1);
      strcpy(rule, p->value);
      gtk_tree_store_append(store, &iter, &parent);
      gtk_tree_store_set(store, &iter, 0, rule, -1);
      p = p->next;
    }
  gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, FALSE);
  gtk_tree_path_free(path);

  /* go to the third entry */
  path = gtk_tree_path_new_from_string("2");
  gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent, path);
  gtk_tree_path_free(path);

  /* append the clientside user rules */
  p = arg_get_value(context->prefs, "CLIENTSIDE_USERRULES");
  while (p && p->next)
    {
      rule = emalloc(strlen((char *)p->value)+1);
      strcpy(rule, p->value);
      gtk_tree_store_append(store, &iter, &parent);
      gtk_tree_store_set(store, &iter, 0, rule, -1);
      p = p->next;
    }

  path = gtk_tree_path_new_from_string("2");
  gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, FALSE);
  gtk_tree_path_free(path);
}

/**
 * @brief Check whether the given string is a syntactically correct
 * @brief representation of a subnet.
 *
 * Subnets are of these forms:
 * n1.n2.n3.n4    (equals to n1.n2.n3.n4/32)
 * n1.n2.n3.n4/x
 * n1.n2.n3.n4/n5.n6.n7.n8
 * with each n in 0..255 and x in 0..32
 * 
 * @return The function returns 1 if it is valid and 0 if not.
 */
int
is_valid_subnet_str (char * subnet)
{
  unsigned int n1, n2, n3, n4, n5, n6, n7, n8, x;
  char c;

  if (sscanf(subnet, "%u.%u.%u.%u%c", &n1, &n2, &n3, &n4, &c) == 4)
  {
    if (n1 <= 255 && n2 <= 255 && n3 <= 255 && n4 <= 255)
      return 1;
  }

  if (sscanf(subnet, "%u.%u.%u.%u/%u%c", &n1, &n2, &n3, &n4, &x, &c) == 5)
  {
    if (n1 <= 255 && n2 <= 255 && n3 <= 255 && n4 <= 255 && x <= 32)
      return 1;
  }

  if (sscanf(subnet, "%u.%u.%u.%u/%u.%u.%u.%u%c", &n1, &n2, &n3, &n4,
             &n5, &n6, &n7, &n8, &c) == 8)
  {
    if (n1 <= 255 && n2 <= 255 && n3 <= 255 && n4 <= 255 &&
        n5 <= 255 && n6 <= 255 && n7 <= 255 && n8 <= 255)
      return 1;
  }

  return 0;
}

/**
 * @brief Add a rule as specified through the combo and entry widget to the
 * @brief treestore and treelist.
 */
static void
add_rule_callback (GtkWidget * w, struct arglist * ctrls)
{
  GtkComboBox * action_combo;
  GtkEntry * target_entry;
  GtkWidget * tree;
  GtkTreeStore * store;
  GtkTreeIter iter, parent;
  char * action = NULL;
  char * target = NULL;
  gchar * rule = NULL;
  GtkTreePath * path;

  action_combo = arg_get_value(ctrls, "ACTION");
  action = (char *)gtk_combo_box_get_active_text(action_combo);
  /* Before the GTK+ minimum version was 2.6,  looked like:
    GtkTreeModel * model;
    if (gtk_combo_box_get_active_iter (action_combo, &iter)) {
    model = gtk_combo_box_get_model(action_combo);
    gtk_tree_model_get(model, &iter, 0, &action, -1);
  }*/
  target_entry = arg_get_value(ctrls, "TARGET");
  target = (char*)gtk_entry_get_text(target_entry);
  tree = arg_get_value(ctrls, "RULES_TREE");
  store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));

  if (!strcmp(action, "default accept") ||
      !strcmp(action, "default deny") ||
      !strcmp(action, "default reject"))
    {
      rule = g_strdup_printf ("%s", action);
    }
  else
    {
      if (strlen(target) == 0 || ! is_valid_subnet_str(target))
        {
          show_warning(_("The target for this rule must be a valid IP or Subnet.\n"));
          return;
        }
      rule = g_strdup_printf ("%s %s", action, target);
    }

  /* add the rule to the list store */
  path = gtk_tree_path_new_from_string("2");
  gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent, path);
  gtk_tree_store_append(store, &iter, &parent); 
  gtk_tree_store_set(store, &iter, 0, rule, -1);

  gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, FALSE);
  gtk_tree_path_free(path);
}

/**
 * @brief Remove the rule that is currently selected (provided the selected
 * @brief line is a removable rule).
 */
static void
remove_rule_callback (GtkWidget * w, struct arglist * ctrls)
{
  GtkWidget * tree;
  GtkTreeStore * store;
  GtkTreePath * path;
  GtkTreeViewColumn *focus_column;
  GtkTreeIter iter, parent;

  tree = arg_get_value(ctrls, "RULES_TREE");
  store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));

  /* Get the selected entry */
  gtk_tree_view_get_cursor(GTK_TREE_VIEW(tree), &path, &focus_column);

  if (!path)
    return; /* no line selected */

  gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path);

  /* Check whether this is a line that is allowed to be removed */
  gtk_tree_path_free(path);
  path = gtk_tree_path_new_from_string("2"); /* must be under third group */
  gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent, path);
  if (!gtk_tree_store_is_ancestor(store, &parent, &iter))
    return; /* no, its not */

  /* Remove the line */
  gtk_tree_store_remove(store, &iter);

  gtk_tree_path_free(path);
}

/**
 * @brief Creates the part of the GUI concerned with (user access) rules.
 * 
 * @return Arglist with widgets hooked in.
 */
struct arglist *
prefs_dialog_user ()
{
  struct arglist * ctrls = emalloc(sizeof(struct arglist));
  GtkWidget * frame;
  GtkWidget * vbox;
  GtkWidget * hbox;
  GtkWidget * label;
  GtkWidget * action_combobox;
  GtkWidget * scrolledwindow;
  GtkTreeStore *store;
  GtkWidget *tree;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkWidget * button;
  GtkWidget * entry;

  /* The overall frame */
  frame = gtk_frame_new(_("User Access Rules"));
  gtk_container_border_width(GTK_CONTAINER(frame), 10);
  gtk_widget_show(frame);
  arg_add_value(ctrls, "FRAME", ARG_PTR, -1, frame);
  read_only_set_recurse(frame);

  /* The overall vbox that will contain all widgets */
  vbox = gtk_vbox_new(FALSE, 4);
  read_only_set_recurse(vbox);
  gtk_container_add(GTK_CONTAINER(frame), vbox);
  gtk_widget_show(vbox);

  /* The hbox that contains the selection elements for the rule */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show(hbox);

  /* The label for action entry */
  label = gtk_label_new(_("Action:"));
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);

  /* A combo selection for the action */
  action_combobox = gtk_combo_box_new_text();
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "accept");
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "reject");
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "deny");
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "default accept");
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "default reject");
  gtk_combo_box_append_text(GTK_COMBO_BOX(action_combobox), "default deny");
  gtk_combo_box_set_active(GTK_COMBO_BOX(action_combobox), 0);
  gtk_widget_show(action_combobox);
  gtk_box_pack_start(GTK_BOX(hbox), action_combobox, FALSE, FALSE, 0);
  arg_add_value(ctrls, "ACTION", ARG_PTR, -1, action_combobox);

  /* The label for target entry */
  label = gtk_label_new(_("Target:"));
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);

  /* The entry for the target */
  entry = gtk_entry_new();
  gtk_entry_set_width_chars(GTK_ENTRY(entry), 10);
  gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
  gtk_widget_show(entry);
  arg_add_value(ctrls, "TARGET", ARG_PTR, -1, entry);

  /* The button to add a rule */
  button = gtk_button_new_with_label(_("Add rule"));
  gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
  g_signal_connect(GTK_OBJECT(button), "clicked",
                   GTK_SIGNAL_FUNC(add_rule_callback), ctrls);
  gtk_widget_show(button);

  /* The scrolled window where to have the treelist */
  scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
  read_only_set_active(scrolledwindow);
  gtk_box_pack_start(GTK_BOX(vbox), scrolledwindow, TRUE, TRUE, 5);
  gtk_widget_show(scrolledwindow);

  /* A tree store for the specified rules */
  store = gtk_tree_store_new(1, G_TYPE_STRING);

  /* A treelist with the rules */
  tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
  g_object_unref(G_OBJECT(store));
  gtk_container_add(GTK_CONTAINER(scrolledwindow), tree);
  gtk_widget_show(tree);
  arg_add_value(ctrls, "RULES_TREE", ARG_PTR, -1, tree);

  /* Fill the tree with defaults */
  reset_rules_tree(ctrls);

  /* The renderer for the treelist */
  renderer = gtk_cell_renderer_text_new();

  /* The column with the rules */
  column = gtk_tree_view_column_new_with_attributes (_("Rules"), renderer,
                                                     "text", 0,
                                                     NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

  /* The hbox that contains the remove button */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show(hbox);

  /* The button to remove a rule */
  button = gtk_button_new_with_label(_("Remove rule"));
  gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
  g_signal_connect(GTK_OBJECT(button), "clicked",
                   GTK_SIGNAL_FUNC(remove_rule_callback), ctrls);
  gtk_widget_show(button);

  return(ctrls);
}

void
prefs_dialog_user_read_only (struct arglist * ctrls, gboolean readonly)
{
  read_only_set_read_only(GTK_WIDGET(arg_get_value(ctrls, "FRAME")),
      readonly);
}

#endif
