/* Nessus
 * Copyright (C) 1998 - 2001 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.
 *
 */

/*
 * XXX: For GTK > 2.6.0 the monitor dialog uses a modern
 * design based on TreeView. The reason it is not activated for
 * GTK 2.4 is that the progress bar inside TreeViews is only
 * available since 2.6 and progress bars are a essential
 * feature of this dialog.
 *
 * The old style is kept until GTK >= 2.6 can be assumed for
 * any relevant system where NessusClient might run. Once this
 * is true and the minimum requirement for NessusClient is GTK
 * >= 2.6, the old style can be removed from the code.
 */

#include <includes.h>

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

#include "backend.h"
#include "nessus_plugin.h"
#include "context.h"
#include "preferences.h"
#include "attack.h"
#include "comm.h"
#include "auth.h"
#include "parser.h"
#include "parseutils.h"
#include "report.h"
#include "globals.h"
#include "error_dlg.h"
#include "xpm/computer.xpm"
#include "monitor_dialog.h"
#include "nessus_i18n.h"
#include "prefs_dialog/prefs_context.h"
#include "prefs_dialog/prefs_scope_tree.h"


static void monitor_input_callback(struct arglist *);
static void monitor_add_host(struct arglist *, char *, int);
static void monitor_list_update(struct arglist *, char *, int);
static int monitor_stop_whole_test_destroy(void*, void*, struct arglist *);
static int monitor_stop_whole_test(GtkWidget * , struct arglist *);


static int
is_server_present (int soc)
{
	fd_set  rd;
	struct timeval tv = {2,0};
	int fd = nessus_get_socket_from_connection(soc);

	if(fd < 0 || fd >= FD_SETSIZE)
	{
	 fprintf(stderr, _("is_server_present: fd(%d) out of range\n"), fd);
	 return 0;
	}
	FD_ZERO(&rd);
	FD_SET(fd, &rd);
	if(select(fd+1, &rd, NULL, NULL, &tv) > 0)
	{
		int len = -1;
		ioctl(fd, FIONREAD, &len);
		if(!len){
			return 0;
			}
	}
	return 1;
}


/**
 * @brief Function called when the UI is idle, which checks whether the server
 * @brief sent us anything.
 * 
 * We use this rather than the traditional gdk input watcher, because it
 * works under Win32.
 */
static int
idle_socket (struct arglist * ctrls)
{
  fd_set rd;
  struct timeval tv = {0, 100};
  int n, soc;
  struct context * context = arg_get_value(ctrls, "CONTEXT");
  if (context == NULL) return FALSE;

  if(context->socket < 0)
    {
      fprintf(stderr, "idle_socket: context->socket=%d\n", context->socket);
      return FALSE;
    }
  if (stream_pending(context->socket))
    {
      monitor_input_callback(ctrls);
      return TRUE;
    }
  soc = nessus_get_socket_from_connection(context->socket);
  if ((soc < 0) || (soc >= FD_SETSIZE))
    {
      fprintf(stderr, _("idle_server: soc(%d) out of range\n"), soc);
      return -1;
    }
  FD_ZERO(&rd);
  FD_SET(soc, &rd);
  n = select(soc+1, &rd, NULL, NULL, &tv);
  if (FD_ISSET(soc, &rd)&& (n > 0))
    monitor_input_callback(ctrls);
  return TRUE;
}

/** Column IDs of scanner TreeView Model */
enum {
  COL_ICON,
  COL_HOSTNAME,
  COL_PORTSCAN,
  COL_CHECKS,
  NUM_COLS
};

static void
stop_toggled (GtkCellRendererToggle *cell, gchar* path_str, gpointer data)
{
  GtkListStore * store = arg_get_value(data, "MON_STORE");
  struct context * context = arg_get_value(data, "CONTEXT");
  GtkTreeIter iter;
  char * hostname;

  gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path_str);

  gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
    COL_HOSTNAME, &hostname, -1);

  network_printf(context->socket,
    "CLIENT <|> STOP_ATTACK <|> %s <|> CLIENT\n", hostname);
}

/**
 * @brief Draws the window which will show the attack status.
 */
void
monitor_dialog_setup (char * victim, struct context * context)
{
  struct arglist * ctrls = emalloc(sizeof(struct arglist));
  GtkWindow* parent = arg_get_value(MainDialog, "CONTEXT");
  GtkWidget * scrolled_window; 
  GtkWidget * w, * box;
  char* window_title;
  const char* host_name;
  int tag;
  int backend = backend_init(NULL);

  /* Could not create a backend */
  if (backend < 0)
    return;

  arg_add_value(ctrls, "MONITOR_BACKEND", ARG_INT, -1, GSIZE_TO_POINTER(backend));
  arg_add_value(ctrls, "PARENT", ARG_PTR, -1, parent);
  arg_add_value(ctrls, "CONTEXT", ARG_PTR, -1, context);
  context->action = CONTEXT_SCANNING;
  prefs_context_update (context);

  w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size(GTK_WINDOW(w), 320,200);

  /* TODO make the window non-modal
   * This can be done when context is kept for all called functions */
#if 0
  gtk_window_set_modal(GTK_WINDOW(w), FALSE);
  gtk_window_set_transient_for(GTK_WINDOW(w), parent);
  arg_set_value(MainDialog, "CONTEXT", -1, w);
#endif

  gtk_widget_realize(w);
  g_signal_connect (GTK_OBJECT(w), "delete_event",
                   (GtkSignalFunc) monitor_stop_whole_test_destroy, ctrls);

  host_name = prefs_get_string (context, "nessusd_host");
  if(host_name) window_title = emalloc(strlen(host_name) + 255);
  else window_title = emalloc(255);
  sprintf(window_title, _("Scanning network from %s"),
          host_name ? host_name:_("some host"));
// window_title = g_strdup_printf(_("Scanning network from %s"), host_name ? host_name : _("some host"));
  gtk_window_set_title (GTK_WINDOW(w), window_title);
  efree (&window_title);
  gtk_container_border_width (GTK_CONTAINER(w), 10);
  arg_add_value (ctrls, "WINDOW", ARG_PTR, -1, w);
  gtk_widget_show (w);

  box = gtk_vbox_new(FALSE,10);
  gtk_container_add(GTK_CONTAINER(w), box);
  gtk_widget_show(box);

    GtkListStore * store;
    GtkCellRenderer   * renderer;
    GtkTreeViewColumn * column;

    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_border_width(GTK_CONTAINER (scrolled_window), 10);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    gtk_container_border_width(GTK_CONTAINER(scrolled_window), 10);  
    gtk_box_pack_start(GTK_BOX(box), scrolled_window, TRUE, TRUE, 0);

    store = gtk_list_store_new(NUM_COLS, GDK_TYPE_PIXBUF,
      G_TYPE_STRING, GTK_TYPE_INT, GTK_TYPE_INT);
    arg_add_value(ctrls, "MON_STORE", ARG_PTR, -1, store);

    w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));

    /* The Icon column */
    renderer = gtk_cell_renderer_pixbuf_new();
    column = gtk_tree_view_column_new_with_attributes (NULL,
      renderer, "pixbuf", COL_ICON, NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (w), column);

    /* The hostname column */
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes (_("Hostname"),
      renderer, "text", COL_HOSTNAME, NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (w), column);

    /* The port scan progress bar column */
    renderer = gtk_cell_renderer_progress_new();
    column = gtk_tree_view_column_new_with_attributes (_("Portscan"),
      renderer, "value", COL_PORTSCAN, NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (w), column);

    /* The checks progress bar column */
    renderer = gtk_cell_renderer_progress_new();
    column = gtk_tree_view_column_new_with_attributes (_("Checks"),
      renderer, "value", COL_CHECKS, NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (w), column);

    /* The stop action as toggle */
    renderer = gtk_cell_renderer_toggle_new();
    g_signal_connect(renderer, "toggled", G_CALLBACK(stop_toggled), ctrls);
    column = gtk_tree_view_column_new_with_attributes(_("Stop"), renderer,
        NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW (w), column);

    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
                                          w);
    gtk_widget_show(w);
    gtk_widget_show (scrolled_window);

  tag = gtk_idle_add ((GtkFunction)idle_socket, ctrls);

  arg_add_value(ctrls, "TAG", ARG_INT, sizeof(gpointer), GSIZE_TO_POINTER(tag));
  w = gtk_button_new_with_label(_("Stop the whole test"));
  g_signal_connect (GTK_OBJECT(w), "clicked",
                   (GtkSignalFunc)monitor_stop_whole_test,ctrls);
  gtk_box_pack_start(GTK_BOX(box), w, FALSE, TRUE, 0);
  gtk_widget_show(w);
  if(!attack_host(victim, context))
  {
    gtk_widget_hide(arg_get_value(ctrls, "WINDOW"));
    gtk_idle_remove(tag);
    gtk_widget_destroy(arg_get_value(ctrls, "WINDOW"));
#if 0
    arg_set_value(MainDialog, "CONTEXT", -1, parent);
#endif
    prefs_context_update(context);
    scopetreeview_connected_update(context);
  }
}

/**
 * @brief Updates the progress bars.
 */
static void
monitor_list_update (struct arglist * ctrls, char * msg, int short_status)
{
  char * hostname;
  char * action;
  char* current = NULL;
  int max;
  char * list_hostname;
  int flag = 0;

  if (!short_status)
    parse_nessusd_status (msg, &hostname, &action, &current, &max);
  else
    parse_nessusd_short_status (msg, &hostname, &action, &current, &max);

  GtkListStore * store = arg_get_value(ctrls, "MON_STORE");
  GtkTreeIter iter;
  int f;
  if (max == 0)
    {
      f = 100;
    }
  else
    {
      f = (atoi(current) / (float)max) * 100;
      if (f >= 100) f=100;
      if (f <= 0) f=0;
    }

  if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
    do
      {
        gtk_tree_model_get (GTK_TREE_MODEL(store), &iter,
                            COL_HOSTNAME, &list_hostname, -1);
        if (!list_hostname)
          {
            fprintf(stderr, _("Error ! Null hostname in the list\n"));
            return;
          }
        if (!strcmp(list_hostname, hostname))
          {
            if (strcmp(action, "portscan") == 0)
              gtk_list_store_set (store, &iter, COL_PORTSCAN, f, -1);
            else
              gtk_list_store_set (store, &iter, COL_CHECKS, f, -1);
            flag = 1;
            break;
          }
      } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter));
  }

 if(!flag)
  {
    /* the host was not found, we must add one... */
    monitor_add_host (ctrls, estrdup(hostname), atoi(current));
  }

 efree (&hostname);
 efree (&action);
 if (current) efree (&current);
}

/*
 * monitor_remove_host
 */
static void
monitor_remove_host (struct arglist * ctrls, char * host)
{
    GtkListStore * store = arg_get_value(ctrls, "MON_STORE");
    GtkTreeIter iter;
    char * list_hostname;

    if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
      do {
        gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
          COL_HOSTNAME, &list_hostname, -1);
        if (!strcmp(list_hostname, host)) {
          gtk_list_store_remove(store, &iter);
          break;
        }
      } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter));
    }
}


/**
 * @brief Adds a new hostname and progress bar in the monitor window.
 */
void
monitor_add_host (struct arglist * ctrls, char * hostname, int port)
{
    GtkListStore * store = arg_get_value(ctrls, "MON_STORE");
    GtkTreeIter iter;
    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data(
      (const char **)computer_xpm);

    gtk_list_store_append(store, &iter);  /* Acquire an iterator */
    gtk_list_store_set(store, &iter,
      COL_ICON, pixbuf,
      COL_HOSTNAME, hostname,
      COL_PORTSCAN, 0,
      COL_CHECKS, 0,
      -1);
}


/**
 * @brief Stops the connection between nessusd and the client, and reports the
 * @brief results to the screen.
 */
static int
monitor_stop_whole_test_destroy (void* a, void* b, struct arglist* ctrls)
{
 return monitor_stop_whole_test(NULL, ctrls);
}

static int
monitor_stop_whole_test (GtkWidget* w, struct arglist* ctrls)
{
 int type = 0, n;
 char * msg;
 char buf[32768];
 struct context * context;

 context = arg_get_value(ctrls, "CONTEXT");

 network_printf(context->socket, "CLIENT <|> STOP_WHOLE_TEST <|> CLIENT\n");
 gtk_idle_remove(GPOINTER_TO_SIZE(arg_get_value(ctrls, "TAG")));
 
 /*
  * Read the data remaining...
  */
 while(type != MSG_BYE)
 {
  buf[sizeof(buf) - 1] = '\0';
  network_gets_raw(context->socket, buf, sizeof(buf) - 1);
   if( buf[0] == '\0') {
  	break;
	}
  if ((n = strlen (buf)) && buf [n-1] == '\n') buf [n-1] = '\0';
  msg  = emalloc(strlen(buf)+1);
  type = parse_server_message(context, buf, GPOINTER_TO_SIZE(arg_get_value(ctrls, "MONITOR_BACKEND")), msg);
  efree(&msg);
 }

 context->action = CONTEXT_IDLE;
 prefs_context_update(context);
 gtk_widget_hide(arg_get_value(ctrls, "WINDOW"));
 report_save(context, GPOINTER_TO_SIZE(arg_get_value(ctrls, "MONITOR_BACKEND")), NULL);
 return(FALSE);
}


/**
 * @brief This function is called whenever there is new data coming from the
 * @brief server.
 */
void
monitor_input_callback (struct arglist * ctrls)
{
  int finished = 0;
  static char * buf = NULL;
  static int    bufsz = 0;
  static char * msg  = NULL;
  int n, type = -1;
  int interrupted = 0;
  struct context * context = arg_get_value(ctrls, "CONTEXT");

  if (context == NULL) return;

  if (buf == NULL)
    {
      bufsz = 1024 * 1024;
      buf   = emalloc( bufsz );
      msg   = emalloc( bufsz );
    }

  network_gets_raw (context->socket, buf, bufsz);
  // Replace newline at end of string with \0
  if ((n = strlen (buf)) && buf [n-1] == '\n')
    buf [n-1] = '\0';

  if( buf[0] == '\0')
    {
      if (!is_server_present(context->socket))
        {
          interrupted++;
          goto scan_finished;
        }
      else return;
    }

  type = parse_server_message(context, buf, GPOINTER_TO_SIZE(arg_get_value(ctrls, "MONITOR_BACKEND")), msg);

  switch (type)
    {
      case MSG_BYE :
          network_printf (context->socket, "CLIENT <|> BYE <|> ACK\n");
          finished = 1;
          break;
      case MSG_STAT2 :
          monitor_list_update (ctrls,buf+2, 1);
          break;
      case MSG_STAT :
          monitor_list_update (ctrls, msg, 0);
          break;
      case MSG_FINISHED :
          monitor_remove_host (ctrls, msg);
          break;
    }
  buf[0] = '\0';
  msg[0] = '\0';

  if(finished)
    {
   scan_finished :
      gtk_widget_hide(arg_get_value(ctrls, "WINDOW"));
      gtk_idle_remove(GPOINTER_TO_SIZE(arg_get_value(ctrls, "TAG")));
      gtk_widget_destroy(arg_get_value(ctrls, "WINDOW"));
      report_save(context, GPOINTER_TO_SIZE(arg_get_value(ctrls, "MONITOR_BACKEND")), NULL);
      context->action = CONTEXT_IDLE;
      prefs_context_update(context);
    }
}

#endif /* USE_GTK */
