/*
    ettercap -- gtk interface for plugins

    Copyright (C) 2001  ALoR <alor@users.sourceforge.net>, NaGA <crwm@freemail.it>
    GTK+ 2.0 interface by daten <daten@dnetc.org>

    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*/

#include <ec_main.h>
#include <ec_thread.h>

#ifdef PERMIT_PLUGINS

#include <ctype.h>
#if defined(HAVE_SYS_POLL_H)
	#include <sys/poll.h>
#elif defined (HAVE_POLL_H)
	#include <poll.h>
#endif

#include <ec_gtk.h>
#include <ec_plugins.h>
#include <ec_buffer.h>

#define PAD_BUFFER 500        // buffered lines for plugin window

// protos....

void Ginterface_Plugins_Run(void);
void Ginterface_Plugins_PointItem(GtkWidget *widget, gpointer data);
void Ginterface_Plugins_InitList(void);
void Ginterface_Plugins_SelectItem(GtkTreeView *treeview, gpointer arg1, GtkTreeViewColumn *arg2, gpointer data);
void Ginterface_Plugins_Output(void);
void Ginterface_Plugins_Close(void);
void Ginterface_Plugins_ToolBar(GtkItemFactory *host_menu);
void Ginterface_Plugins_Stop(void);

void Ginterface_Plugins_Add_Output(char *message);
gboolean Ginterface_Plugins_Output_Update(gpointer data);
int Ginterface_Plugins_Input_Block(char *string, size_t size);
int Ginterface_Plugins_Input_NonBlock(char *buff, size_t size);
void Ginterface_Plugins_Input_Inserted(GtkTextBuffer *textbuffer, GtkTextIter *arg1, gchar *arg2, gint arg3, gpointer user_data);
void *Ginterface_Plugins_RunExt(void *param);

void Ginterface_Plugins_Output_Callback(GtkWidget *widget, gpointer data);
void Ginterface_Plugins_Callback(gpointer data, guint action, GtkWidget *widget);

/* global variables */
GtkListStore *p_list = NULL;
GtkTreeIter p_iter;
GtkWidget *p_treeview = NULL;
GtkTreeSelection *p_selection = NULL;
GtkWidget *p_scrolled = NULL;
/* .. for non-blocking input */
char *block_string = NULL;
int block_size = 0;
gboolean block_flag = FALSE;
/* .. for non-blocking input */
int nb_chars = -1;
gulong nb_input_id = 0, nb_enter_id = 0;

/* plugin output window */
GtkWidget *o_win = NULL, *o_textview = NULL, *o_scrolled = NULL;
GtkTextBuffer *o_buffer = NULL;
GtkTextMark *o_mark = NULL;

int id_watch;
pthread_t p_id = 0;
extern struct plug_array *Plugins_Array; /* from ec_plugins.c */
short number_of_plugins;
extern int Sel_Number;
int gPlug_Base_Pointer = 0;
int gPlug_Pointer = 0;
short P_Sel_Number;

// -------------------

void Ginterface_Plugins_InitList(void)
{
   int j;
   gchar ver[5], stat[2];

   if (number_of_plugins == 0)      // no plugin... no action... ;)
   {
      Ginterface_PopUp ("NO plugin available in %s or in ./ !!\n", PLUGIN_PATH);
      return;
   }

   g_signal_handlers_block_by_func (G_OBJECT (p_selection), G_CALLBACK (Ginterface_Plugins_PointItem), NULL);

   if(p_list == NULL) {
      /* number, hostname, ip, divider, hostname, ip, status, type */
      p_list = gtk_list_store_new (5, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
      gtk_tree_view_set_model (GTK_TREE_VIEW (p_treeview), GTK_TREE_MODEL (p_list));
   } else
      gtk_list_store_clear( GTK_LIST_STORE (p_list) );

   for(j=gPlug_Base_Pointer; j<number_of_plugins; j++) 
   {
      snprintf(ver, sizeof(ver), "%.1f", Plugins_Array[j].version);
      snprintf(stat, sizeof(stat), "%c", Plugins_Array[j].status);

      gtk_list_store_append (GTK_LIST_STORE (p_list), &p_iter);
      gtk_list_store_set (GTK_LIST_STORE (p_list), &p_iter,
            0, j+1,
            1, Plugins_Array[j].name,
            2, ver,
            3, stat,
            4, Plugins_Array[j].description, -1);
   }

   g_signal_handlers_unblock_by_func (G_OBJECT (p_selection), G_CALLBACK (Ginterface_Plugins_PointItem), NULL);
}

void Ginterface_Plugins_PointItem(GtkWidget *widget, gpointer data)
{
   GtkTreeIter iter;
   GtkTreeModel *model;
   int line;

   if (number_of_plugins == 0) return; // no plugin... no action... ;)

   if (gtk_tree_selection_get_selected (GTK_TREE_SELECTION (widget), &model, &iter)) {
      gtk_tree_model_get (model, &iter, 0, &line, -1);
   } else return;

   gPlug_Pointer = line - 1;
}

void Ginterface_Plugins_SelectItem(GtkTreeView *treeview, gpointer arg1, GtkTreeViewColumn *arg2, gpointer data)
{
   DEBUG_MSG("Ginterface_Plugins_SelectItem");

   if (number_of_plugins == 0) return; // no plugin... no action... ;)

   if (Plugins_Array[gPlug_Pointer].status == 'E')
   {
      if(o_win) {
	Ginterface_PopUp("Only one interactive plugin may be run at a time.");
	return;
      }

      Ginterface_Plugins_Output(); /* opens a plugin output window */

      p_id = ECThread_create("gtkrunext", &Ginterface_Plugins_RunExt, NULL);
   }
   else  // it is a hooking plugin
   {
      Plugins_Array[gPlug_Pointer].status = (Plugins_Array[gPlug_Pointer].status == 'A') ? ' ' : 'A';
      Ginterface_Plugins_InitList();
   }
}

void *Ginterface_Plugins_RunExt(void *param) 
{
   if (!Plugin_RunExt(Plugins_Array[gPlug_Pointer].name))
   {
      Ginterface_PopUp("There was an error loading this plugin !!");
   }

   return(NULL);
}

void Ginterface_Plugins_Output(void)
{
   GtkWidget *hbox, *button, *hsep;
   GtkTextIter iter;

   if(o_win && !(GTK_WIDGET_VISIBLE(o_win)) ) {
      gtk_widget_show(o_win);
      return;
   } else if(o_win && GTK_WIDGET_VISIBLE(o_win) ) {
      gtk_widget_hide(o_win);
      return;
   }

   o_win = gtk_dialog_new_with_buttons ("Plugin Output", GTK_WINDOW (window), GTK_DIALOG_DESTROY_WITH_PARENT, NULL);
   gtk_dialog_set_has_separator(GTK_DIALOG(o_win), FALSE);
   gtk_window_set_default_size(GTK_WINDOW (o_win), 500, 300);
   gtk_window_set_position (GTK_WINDOW (o_win), GTK_WIN_POS_CENTER);
   g_signal_connect (G_OBJECT (o_win), "delete_event", G_CALLBACK (gtk_widget_hide), GTK_OBJECT (o_win));

   /* make the scroled window */
   o_scrolled = gtk_scrolled_window_new(NULL, NULL);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (o_scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (o_scrolled), GTK_SHADOW_IN);
   gtk_box_pack_start (GTK_BOX(GTK_DIALOG(o_win)->vbox), o_scrolled, TRUE, TRUE, 0);
   gtk_widget_show (o_scrolled);

   /* make the Text View window */
   o_textview = gtk_text_view_new();
   o_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (o_textview));
   gtk_container_add(GTK_CONTAINER (o_scrolled), o_textview);
   gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER (o_buffer), &iter);
   o_mark = gtk_text_buffer_create_mark(o_buffer, "end", &iter, FALSE);
   gtk_widget_show(o_textview);

   /* make tags to be inserted later */
   gtk_text_buffer_create_tag (o_buffer, "not_editable", "editable", FALSE, NULL);

   /* a quick horizontal line */
   hsep = gtk_hseparator_new();
   gtk_box_pack_start (GTK_BOX(GTK_DIALOG(o_win)->vbox), hsep, FALSE, FALSE, 0);
   gtk_widget_show(hsep);

   /* make the input row */
   hbox = gtk_hbox_new(FALSE, 0);
   gtk_box_pack_start (GTK_BOX(GTK_DIALOG(o_win)->vbox), hbox, FALSE, FALSE, 0);
   gtk_widget_show(hbox);
   
   button = gtk_button_new_with_label ("Close");
   g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (Ginterface_Plugins_Close), NULL);
   gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 0);
   gtk_widget_show(button);

   gtk_widget_show(o_win);
}

void Ginterface_Plugins_Close(void) {
   if(!o_win)
      return;

   gtk_widget_hide(o_win);
   if(p_id != 0) {
      ECThread_destroy(p_id);
      p_id = 0;
   }
   gtk_widget_destroy(o_textview);
   gtk_widget_destroy(o_scrolled);
   gtk_widget_destroy(o_win); /* this should take care of child widgets too */
   
   o_win = NULL;
   o_buffer = NULL;
   o_scrolled = NULL;
}

void Ginterface_Plugins_Add_Output(char *message) {
   GtkTextIter iter;

   if(message == NULL || strlen(message) < 1)
      return;
        
   gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER (o_buffer), &iter);
   gtk_text_buffer_insert_with_tags_by_name(GTK_TEXT_BUFFER (o_buffer), &iter,
      g_locale_to_utf8 (message, strlen(message), NULL, NULL, NULL), -1,
      "not_editable", NULL);

   gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(o_textview), o_mark, 0, FALSE, 0, 0);
}

int Ginterface_Plugins_Input_Block(char *string, size_t size) {
   if(string == NULL || size < 1)
      return 0;

   block_string = string;
   block_size = size;
   block_flag = FALSE;

   g_signal_connect (G_OBJECT (o_buffer), "insert-text", G_CALLBACK (Ginterface_Plugins_Input_Inserted), NULL);

   /* pause here until newline is recieved or size is met */
   while(!block_flag)
      sleep(1);
   block_flag = FALSE;

   g_signal_handlers_disconnect_by_func (G_OBJECT (o_buffer), G_CALLBACK (Ginterface_Plugins_Input_Inserted), NULL);

   block_string = NULL;
   block_size = 0;

   return strlen(string);
}

int Ginterface_Plugins_Input_NonBlock(char *buff, size_t size) {
   if(nb_chars < 0) {
      nb_chars = 0;
      block_string = buff;
      block_size = size;
      g_signal_connect (G_OBJECT (o_buffer), "insert-text", G_CALLBACK (Ginterface_Plugins_Input_Inserted), NULL);
      return 0;
   } 

   if(nb_chars == block_size) {
      int i = block_size;

      g_signal_handlers_disconnect_by_func (G_OBJECT (o_buffer), G_CALLBACK (Ginterface_Plugins_Input_Inserted), NULL);
      nb_chars = -1;
      block_string = NULL;
      block_size = 0;
      return(i);
   } else 
      return(nb_chars);
}

/* this is used in both block and non-blocking input, hence the presence of nb_chars AND block_flag */
void Ginterface_Plugins_Input_Inserted(GtkTextBuffer *textbuffer, GtkTextIter *iter, gchar *text, gint size, gpointer user_data) {
   if(size < block_size)
      nb_chars += size;
   else {
      nb_chars = block_size;
      block_flag = TRUE;
   }

   if(strchr(text, '\n'))
      block_flag = TRUE;

   if(block_string)
      strncat(block_string, text, block_size);
}

void Ginterface_Plugins_Output_Callback (GtkWidget *widget, gpointer data) {
   gchar action = '!';

   sscanf(data, "%c", &action);

   switch (action) {
/*
      case 'D':
      case 'd':
         {
            FILE *bin_dump, *str_dump;
            int ch, ok = 0;

            bin_dump = tmpfile();
            putwin(output_window, bin_dump);
            str_dump = fopen("plugin_output_dump.log", "wb");
            rewind(bin_dump);
            fseek(bin_dump, 0x48, SEEK_CUR); // skip the header file
            while( (ch = fgetc(bin_dump)) != EOF)
            {
               if (isprint(ch))
               {
                  if (!ok && ch != 0x20) ok = 1;
                  if (ok)
                  {
                     if (ok++ == (W_MAINX2-2))
                     {
                        fputc('\n', str_dump);
                        ok = 1;
                     }
                     else
                        fputc(ch, str_dump);
                  }
               }
            }
            Ginterface_PopUp("Data dumped to : plugin_output_dump.log");
            touchwin(o_win);
            wnoutrefresh(o_win);
         }
         break;
*/
      case 'H':
      case 'h':
         {
            static char *help[] = {
               "[qQ][F10] - quit",
               "[arrows]  - scroll the output",
               "[dD]      - dump plugins output to a file",
               "[hH]      - this help screen",
               NULL};
            Ginterface_HelpWindow(help);
         }
         break;

      case 'Q':
      case 'q':
         {
            char answer;
            answer = Ginterface_PopUpQ("Do you really want to exit?");
            if ((answer == 'y') || (answer == 'Y'))
               Ginterface_WExit("They are safe!!  for now... ");
         }
         break;
   }
}

gboolean Ginterface_Plugins_Output_Update(gpointer data)
{
   int mesglen = 0;
   char message[500];

   Buffer_Get(pipe_with_plugins, &mesglen, sizeof(int));
   Buffer_Get(pipe_with_plugins, message, mesglen);

   message[mesglen] = 0;

   if (!mesglen) return(TRUE);

   gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER (o_buffer), message, -1);

   return(TRUE);
}

void Ginterface_Plugins_Run(void)
{
   GtkCellRenderer *p_renderer;
   GtkTreeViewColumn *p_column;

   DEBUG_MSG("Ginterface_Plugins_Run");

   if (!Plugins_Array) number_of_plugins = Plugin_ExtArray();

   /* make the scrolled window */
   p_scrolled = gtk_scrolled_window_new(NULL, NULL);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (p_scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (p_scrolled), GTK_SHADOW_IN);
   p_treeview = gtk_tree_view_new ();
   gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (p_treeview), FALSE);
      /* add the column */
   p_renderer = gtk_cell_renderer_text_new ();
   p_column = gtk_tree_view_column_new_with_attributes ("#", p_renderer, "text", 0, NULL);
   gtk_tree_view_column_set_sort_column_id (p_column, 0);
   gtk_tree_view_column_set_resizable (p_column, FALSE);
   gtk_tree_view_append_column (GTK_TREE_VIEW(p_treeview), p_column);
      /* add the column */
   p_renderer = gtk_cell_renderer_text_new ();
   p_column = gtk_tree_view_column_new_with_attributes ("Name", p_renderer, "text", 1, NULL);
   gtk_tree_view_column_set_sort_column_id (p_column, 1);
   gtk_tree_view_column_set_resizable (p_column, FALSE);
   gtk_tree_view_append_column (GTK_TREE_VIEW(p_treeview), p_column);
      /* add the column */
   p_renderer = gtk_cell_renderer_text_new ();
   p_column = gtk_tree_view_column_new_with_attributes ("Ver", p_renderer, "text", 2, NULL);
   gtk_tree_view_column_set_sort_column_id (p_column, 2);
   gtk_tree_view_column_set_resizable (p_column, FALSE);
   gtk_tree_view_append_column (GTK_TREE_VIEW(p_treeview), p_column);
      /* add the column */
   p_renderer = gtk_cell_renderer_text_new ();
   p_column = gtk_tree_view_column_new_with_attributes ("", p_renderer, "text", 3, NULL);
   gtk_tree_view_column_set_sort_column_id (p_column, 3);
   gtk_tree_view_column_set_resizable (p_column, FALSE);
   gtk_tree_view_append_column (GTK_TREE_VIEW(p_treeview), p_column);
      /* add the column */
   p_renderer = gtk_cell_renderer_text_new ();
   p_column = gtk_tree_view_column_new_with_attributes ("Description", p_renderer, "text", 4, NULL);
   gtk_tree_view_column_set_sort_column_id (p_column, 4);
   gtk_tree_view_column_set_resizable (p_column, FALSE);
   gtk_tree_view_append_column (GTK_TREE_VIEW(p_treeview), p_column);
      /* connect signals */
   p_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (p_treeview));
   gtk_tree_selection_set_mode (p_selection, GTK_SELECTION_SINGLE);
   g_signal_connect (G_OBJECT (p_selection), "changed", G_CALLBACK (Ginterface_Plugins_PointItem), NULL);
   g_signal_connect (G_OBJECT (p_treeview), "row_activated", G_CALLBACK (Ginterface_Plugins_SelectItem), NULL);
      /* add it and display it */
   gtk_container_add(GTK_CONTAINER (p_scrolled), p_treeview);
   gtk_widget_show(p_treeview);

   Ginterface_Plugins_InitList();

   gtk_container_add(GTK_CONTAINER (mainbox[5]), p_scrolled);
   gtk_widget_show (p_scrolled);

   id_watch = g_timeout_add(1000, Ginterface_Plugins_Output_Update, NULL);
}

void Ginterface_Plugins_ToolBar(GtkItemFactory *host_menu) {
   static GtkItemFactoryEntry action_menu[] = {
      MENU_COMMON
      { "/Action", "h", NULL, 0, "<Branch>", NULL},
      { "/Action/Toggle Plugin Output", "p", Ginterface_Plugins_Callback, 'p', "<StockItem>", GTK_STOCK_JUMP_TO},
      { "/Help", NULL, NULL, 0, "<LastBranch>", NULL},
      { "/Help/_Manual", NULL, Ginterface_Manual, 0, "<StockItem>", GTK_STOCK_HELP},
      { "/Help/_About", NULL, Ginterface_About, 0, "<StockItem>", GTK_STOCK_DIALOG_INFO}
   };
   gint num_items = 0;

   DEBUG_MSG("Ginterface_ToolBar");

   gtk_item_factory_delete_item(host_menu, "/Quit");
   gtk_item_factory_delete_item(host_menu, "/Sniff");
   gtk_item_factory_delete_item(host_menu, "/Action");
   gtk_item_factory_delete_item(host_menu, "/Help");

   num_items = sizeof (action_menu) / sizeof (action_menu[0]);
   gtk_item_factory_create_items (host_menu, num_items, action_menu, NULL);
}

void Ginterface_Plugins_Callback(gpointer data, guint action, GtkWidget *widget) {
   switch (action) {
      case 'H':
      case 'h':
	 {
            static char *help[] = {
               "[qQ][F10] - quit",
               "[return]  - if external: run the selected plug in",
               "          - if hooking : activate/deactivate it",
               "[hH]      - this help screen",
               NULL};
            Ginterface_HelpWindow(help);
         }
         break;
      case 'p':
         Ginterface_Plugins_Output();
         break;
      case 'q':
      case 'Q':
         {
            char answer;
            answer = Ginterface_PopUpQ("Do you really want to exit?");
            if ((answer == 'y') || (answer == 'Y'))
               Ginterface_WExit("They are safe!!  for now... ");
         }
         break;
   }
}

void Ginterface_Plugins_Stop(void) {
   int i;
   DEBUG_MSG("Ginterface_Plugins_Stop");

   g_source_remove(id_watch);

   for(i=0; i< number_of_plugins; i++)    // activate the plugins
      if (Plugins_Array[i].status == 'A')
         Plugin_SetActivation(Plugins_Array[i].name, 1);
      else if (Plugins_Array[i].status == ' ')
         Plugin_SetActivation(Plugins_Array[i].name, 0);

   gtk_container_remove(GTK_CONTAINER (mainbox[5]), p_scrolled);
   g_free(p_list);
   p_list = NULL;
   p_scrolled = NULL;
}

#endif   // PERMIT_PLUGINS

/* EOF */


// vim:ts=3:expandtab

