/* $Id: context.c,v 1.16 2006-07-03 14:43:21 bh Exp $
 *
 * Copyright (C) 2004, 2005 by Intevation GmbH
 * Author(s):
 * Thomas Arendsen Hein <thomas@intevation.de>
 *
 * 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.
 */

#include "includes.h"
#include "nessus_i18n.h"

#include "file_utils.h"
#include "nessus_plugin.h"
#include "context.h"
#include "preferences.h"
#include "error_dlg.h"
#include "plugin_cache.h"
#include "comm.h"
#include "hash_table_file.h"
#include "openvas_certificate_file.h"
#include "openvas_lsc_user_deb.h"
#include "openvas_lsc_user_makensis.h"
#include "openvas_lsc_user_rpm.h"
#include "openvas_lsc_target_prep.h"
#include "openvas_ssh_login.h"
#include "openvas_ssh_key_create.h"



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

struct context *Global;
struct context *Context;

/**
 * Initializes a Context child of a parent Context, including memory
 * allocation.
 * The context_type will be the parents context_type +1.
 * 
 * @param context Double pointer to child (memory will be allocated).
 * @param parent The parent context.
 */
void
context_init (struct context **context, struct context *parent)
{
  *context = emalloc(sizeof(struct context));
  (*context)->parent = parent;
  (*context)->type = parent?parent->type+1:CONTEXT_GLOBAL;
  (*context)->prefs = NULL;
  (*context)->plugins_md5sum = NULL;
  (*context)->plugins = NULL;
  (*context)->scanners = NULL;
  (*context)->dependencies = NULL;
  (*context)->dir = NULL;
  (*context)->socket = -1;
  (*context)->passwd = NULL;
  (*context)->children = NULL;
  (*context)->next = NULL;
#ifdef USE_GTK
  (*context)->treerowref = NULL;
  (*context)->move_menuitem = NULL;
  (*context)->plugin_prefs_widget = NULL;
  (*context)->plugin_prefs_cred_widget = NULL;
  (*context)->pbar = NULL;
  (*context)->plugin_tree_store = NULL;
  (*context)->plugin_tree_model = NULL;
  (*context)->gui_sshlogins_per_target = NULL;
#endif
  (*context)->plugin_cache_loaded = 0;
  (*context)->signer_fp_certificates = NULL;
  (*context)->sshkeys = NULL;
  (*context)->map_target_sshlogin = NULL;
  (*context)->is_severity_mapped = FALSE;
}

struct context*
context_by_type (struct context *context, context_type type)
{
  while(context && context->parent && context->type > type)
    context = context->parent;

  if(context->type == type)
    return context;
  else
    return NULL;
}


/**
 * @brief Resets the tree store and model for the plugins.
 * 
 * When compiling without USE_GTK, this function does nothing.
 */
void
context_reset_plugin_tree (struct context *context)
{
#ifdef USE_GTK
  if (context->plugin_tree_store != NULL)
  {
    /* clear the tree store, in case it is still shown in a tree view */
    gtk_tree_store_clear(context->plugin_tree_store);
    g_object_unref(context->plugin_tree_store);
  }
  context->plugin_tree_store = NULL;
  if (context->plugin_tree_model != NULL)
    g_object_unref(context->plugin_tree_model);
  context->plugin_tree_model = NULL;
#endif
}

/**
 * @brief Force a redraw of the plugin prefs widgets.
 * 
 * The redraw is not directly here. It will happen as soon as 
 * prefs_plugins_prefs_redraw is called.
 * When compiling without USE_GTK, this function does nothing.
 */
void
context_force_plugin_prefs_redraw(struct context *context)
{
#ifdef USE_GTK
  if (context->plugin_prefs_widget != NULL) {
    g_object_unref(context->plugin_prefs_widget);
    context->plugin_prefs_widget = NULL;
  }
  if (context->plugin_prefs_cred_widget != NULL) {
    g_object_unref(context->plugin_prefs_cred_widget);
    context->plugin_prefs_cred_widget = NULL;
  }
#endif
}


/**
 * @brief Reset the plugin information of the context.
 */
void
context_reset_plugins (struct context *context)
{
  nessus_plugin_free (context->plugins);
  nessus_plugin_free (context->scanners);
  context->plugins = NULL;
  context->scanners = NULL;
  efree (&context->plugins_md5sum);
  context_reset_plugin_tree (context);
}

/**
 * @brief Add a plugin to the context.
 * 
 * If the plugin is a scanner, it's added to context->scanners and the
 * corresponding scanner set.
 * Otherwise it's a normal plugin and is added to context->plugins list and the
 * plugin set.
 * In case that the plugin can not be found in the cache and is not a scanner
 * it is added in an en/disabled state according to the 
 * "auto_enable_new_plugins" preference for the scope at hand.
 *
 * XXX: do we need hashing for pluginset?
 * @param context Cotnext to which to add the plugin to.
 * @param plugin Plugin to add to context.
 * 
 * @return 1 if the added plugin was known before (was in cache), 0 otherwise.
 */
int
context_add_plugin (struct context *context, struct nessus_plugin *plugin)
{
  char *category = plugin->category;
  char *oid =   plugin->oid;
  int is_scanner = (strcmp(category, "scanner") == 0);
  struct nessus_plugin *plugins = is_scanner ? context->scanners
					     : context->plugins;
  struct arglist *pluginset = prefs_get_pluginset(context,
      is_scanner ? "SCANNER_SET" : "PLUGIN_SET", NULL);
  int in_pluginset = (int)(arg_get_type(pluginset, oid) >= 0);
  int enabled = 0;

  /* Already known, enabled plugins are added in enabled state */
  if (in_pluginset)
  {
    if (arg_get_value(pluginset, oid))
      enabled = 1;
  }
  /* Unknown plugins are added in enabled state depending on a scope pref.    */
  else if (!is_scanner)
    enabled = prefs_get_int(context, "auto_enable_new_plugins");

  plugin->enabled = enabled;
  plugin->next = plugins;

  if (!in_pluginset)
    arg_add_value(pluginset, oid, ARG_INT, sizeof(gpointer), GSIZE_TO_POINTER(enabled));

  if (is_scanner)
    context->scanners = plugin;
  else
    context->plugins = plugin;
  /* Return whether the just added plugin was known from the cache before  */
  return (!in_pluginset && !is_scanner);
}


void
context_set_plugins_md5sum (struct context *context, const char *md5sum)
{
  efree(&context->plugins_md5sum);
  context->plugins_md5sum = estrdup(md5sum);
}

struct context **
context_find_child_ptr (struct context *context, struct context *child)
{
  struct context **ptr;

  if(context->children != child)
  {
    context = context->children;
    while(context->next != child)
      context = context->next;
    ptr = &context->next;
  }
  else
    ptr = &context->children;
  return ptr;
}

struct context *
context_create_child (struct context *context)
{
  struct context **child_ptr = context_find_child_ptr(context, NULL);

  context_init(child_ptr, context);
  return *child_ptr;
}

void
context_remove_child (struct context* context, struct context* child)
{
  struct context **child_ptr = context_find_child_ptr(context, child);
  struct context *next = child->next;

  if(child->children)
    show_error(_("context_remove_child detected existing children."));
  if(child->prefs)
    arg_free_all(child->prefs);

  efree(child_ptr);
  *child_ptr = next;
}


/**
 * @brief Warns the user if a login is invalid (e.g. misses key file) and asks
 *        how to solve the situation.
 * 
 * Used in a g_hash_table_foreach to check all logins for validity.
 * Adds keys that should be removed to a list.
 * 
 * @param accountname[in] User-defined name of a openvas_ssh_login (key of hashtable).
 * @param login[in]       User-defined ssh login.
 * @param removelist[out] List to that keys to removed get appended.
 */
static void
verify_sshlogin_integrity (char* accountname, openvas_ssh_login* login,
                           GSList** removelist)
{
  #ifdef USE_GTK
  // Check if something is missing
  if (login)
    {
      gint response;
      // Following will be true e.g. if makensis but no .exe found.
      gboolean rpm_missing = FALSE;
      gboolean deb_missing = FALSE;
      gboolean exe_missing = FALSE;
      gchar* packpath;

      // Find out if a package/installer is missing but its possible to recreate
      if (openvas_lsc_user_rpm_generator_found())
        {
          packpath = openvas_lsc_user_rpm_filename (login->name);
          if (!check_is_file (packpath))
              rpm_missing = TRUE;

          g_free (packpath);

          packpath = openvas_lsc_user_deb_filename (login->name);
          if (openvas_lsc_user_deb_alien_found() && !check_is_file (packpath))
              deb_missing = TRUE;
          g_free (packpath);
        }

      if (openvas_lsc_user_makensis_found())
        {
          packpath = openvas_lsc_user_makensis_filename (login->name);
          if (!check_is_file (packpath))
            exe_missing = TRUE;
        }

      // If some files are missing but can be recreated, let the user choose
      if (rpm_missing || deb_missing || exe_missing || login->valid == FALSE)
        {
          GtkWidget* dialog = gtk_message_dialog_new (NULL, 
                              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
                              GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                              _("SSH Login '%s' misses files.\nDelete this account and all information or recreate the files?"),
                              accountname);
          gtk_dialog_add_buttons (GTK_DIALOG(dialog), _("Delete Account"), 12,
                                                      _("Recreate Files"), 13, NULL);

          gtk_widget_show_all (dialog);

          response = gtk_dialog_run (GTK_DIALOG(dialog));

          if (response == 12) // "Delete"
            {
              (*removelist) = g_slist_prepend ((*removelist), g_strdup(accountname));
            }
          else if (response == 13) // "Create new"
            {
              openvas_ssh_login* login = g_hash_table_lookup (Global->sshkeys, accountname);
              // Recreate keys
              if (login->valid == FALSE)
                {
                  openvas_ssh_key_create_unlink_files (login);
                  if (openvas_ssh_key_create (login) == FALSE)
                    show_warning (_("Recreation of SSH login '%s' failed.\nCheck your console and remove or repair this login manually."), accountname);
                  else
                    show_info (_("Recreation of SSH login '%s' successful."), accountname);
                }
              // Recreate nsis installer
              if (exe_missing == TRUE)
                {
                  packpath = openvas_lsc_user_makensis_filename (login->name);
                  openvas_lsc_user_makensis (login, packpath);
                  g_free (packpath);
                }
              // Recreate rpm package
              if (rpm_missing == TRUE)
                {
                  packpath = openvas_lsc_user_rpm_filename (login->name);
                  openvas_lsc_user_rpm_create (login, packpath);
                  g_free (packpath);
                }
              // Recreate deb package
              if (deb_missing == TRUE)
                {
                  packpath = openvas_lsc_user_deb_filename (login->name);
                  openvas_lsc_user_deb (login, packpath);
                  g_free (packpath);
                }
            }
          else // Seems that dialog (or main-gui) was simply closed, do nothing.
            ;
          gtk_widget_destroy (dialog);
        }
    }
  #else
  if (login && login->valid == FALSE)
    show_warning (_("SSH Login %s seems to miss files. (The GUI can help you to repair or delete it)"), login->name);
  #endif /* USE_GTK */
}

/**
 * @brief Load all ssh login information from the directory sshdir to the 
 *        contexts sshkeys hashtable.
 * 
 * sshdir usually points to .openvas/contex/scope/.ssh.
 * For each invalid key (public or private keyfile is missing) the user will be
 * asked whether to delete or recreate the account.
 * 
 * @param context The context that shall get the sshkey filenames added.
 * @param sshdir Directory to scan for files.
 */
static void
context_pickup_sshkeys (struct context* context, const char* sshdir)
{
  gchar* loginsfile = g_build_filename(sshdir, ".logins", NULL);
  GHashTable* loginfos = openvas_ssh_login_file_read (loginsfile, FALSE);
  GSList* invalidkeylist = NULL;
  GSList* listit = NULL;

  if(Global->sshkeys != NULL)
    g_hash_table_destroy(Global->sshkeys);

  context->sshkeys = loginfos;

  // Check if all logins are valid (files exist).
  g_hash_table_foreach (loginfos, (GHFunc) verify_sshlogin_integrity, &invalidkeylist);

  // Remove the keys and other files that the user wanted to destroy
  if (invalidkeylist != NULL)
    {
      listit = invalidkeylist;
      while (listit != NULL)
        {
          openvas_lsc_target_prep_remove (g_hash_table_lookup (Global->sshkeys, listit->data));
          openvas_ssh_key_create_undo (g_hash_table_lookup (Global->sshkeys, listit->data));
          free (listit->data);
          listit = listit->next;
        }
      g_slist_free (invalidkeylist);
      // Rewrite the file, to remove eventually deleted information immidiately.
      openvas_ssh_login_file_write(Global->sshkeys, loginsfile);
    }

  g_free (loginsfile);
}

/**
 * Collects contexts from a directory (recursively descends in the directory).
 * Contexts are recognized by a openvasrc or report.nbe file, initialized and
 * added as childs to the context given as parameter "context".
 * 
 * @param context The to-be-parent context of found contexts.
 * @param dir Directory to start looking for contexts.
 */
void
context_collect_recurse (struct context *context, const char* dir)
{
  DIR *odir;
  struct dirent *dirent;
  gboolean context_found = FALSE;

  if((odir = opendir(dir)))
  {
    while((dirent = readdir(odir)))
    {
      const char *file = dirent->d_name;
      char *path;

      if(!strcmp(file, ".") || !strcmp(file, ".."))
	continue;

      path = g_build_filename(dir, file, NULL);

      /* 5 possible cases: .ssh directory, child directory, report or rc file
         found, .host_logins and "no hit" */
      if(check_is_dir(path) && strcmp(file, ".ssh") == 0)
      {
        // Just the global context respects .ssh subfolders
        if( context->type == CONTEXT_GLOBAL)
          context_pickup_sshkeys(context, path);
      }
      else if(context->type < CONTEXT_REPORT && check_is_dir(path))
      {
	struct context *child = context_create_child(context);

	child->dir = estrdup(path);
	context_collect_recurse (child, path);
	if(!child->prefs && !child->children)
	  context_remove_child(context, child);
      }
      else if((!strcmp(file, "openvasrc") && context->type < CONTEXT_REPORT)
	    || (!strcmp(file, "report.nbe") && context->type == CONTEXT_REPORT))
      {
	context_found = TRUE;
      }
      else if( !strcmp(file, ".host_sshlogins") )
      {
        context->map_target_sshlogin = hash_table_file_read (path);
      }
      if(context->type == CONTEXT_REPORT && strcmp(file, "certificates") == 0)
      {
        context->signer_fp_certificates = openvas_certificate_file_read(path);
      }
      g_free(path);
      path = NULL;
    }
    closedir(odir);
    if(context->type != CONTEXT_GLOBAL && (context_found || context->children))
      preferences_init(context);
  }
}

/**
 * @brief Recursively collects contexts found in and under the nessus_dir.
 * 
 * @param context Is queried for the nessus_dir preference, found contexts are
 *                added to it.
 */
void
context_collect (struct context *context)
{
  const char *dir = estrdup(prefs_get_string(context, "nessus_dir"));

  context_collect_recurse (context, dir);
}


char *
context_newpath (const char *oldpath, const char *dir, const char *name)
{
  char *newname = estrdup(name);
  char *pos = newname;
  char *g_newpath;
  char *newpath;

  while(pos[0])
  {
    if(!isalnum(pos[0]) && pos[0] != '-')
      pos[0] = '_';
    pos++;
  }

  g_newpath = g_build_filename(dir, newname, NULL);
  efree(&newname);
  /* always use g_free for memory allocated by glib */
  newpath = estrdup(g_newpath);
  g_free(g_newpath);

  if((!oldpath || strcmp(newpath, oldpath)) && check_exists(newpath))
  {
    int i = 0;
    char *uniq = emalloc(strlen(newpath) + strlen("-99") + 1);
    gboolean uniq_found = FALSE;

    while(++i < 100)
    {
      sprintf(uniq, "%s-%d", newpath, i);
      if(!check_exists(uniq))
      {
	efree(&newpath);
	newpath = uniq;
	uniq_found = TRUE;
	break;
      }
    }
    if(!uniq_found)
    {
      efree(&uniq);
      efree(&newpath);
    }
  }

  return newpath;
}

void
context_update_children_dirs (struct context *context)
{
  char *newpath;

  while(context)
  {
    newpath = g_build_filename(context->parent->dir,
	g_path_get_basename(context->dir), NULL);
    efree(&context->dir);
    /* always use g_free for memory allocated by glib */
    context->dir = estrdup(newpath);
    g_free(newpath);
    context_update_children_dirs(context->children);
    context = context->next;
  }
}

void
context_rename (struct context *context, const char *newname)
{
  char *newpath;
  char *parentdir;

  if(context->type < CONTEXT_TASK || context->type > CONTEXT_REPORT)
  {
    show_error(_("context_rename() called with illegal type"));
    return;
  }

  if(!strcmp(newname, prefs_get_string(context, "name")))
    return;

  parentdir = g_path_get_dirname(context->dir);
  newpath = context_newpath(context->dir, parentdir, newname);
  if(newpath)
  {
    if(!strcmp(context->dir, newpath))
    {
      efree(&newpath);
    }
    else if(rename(context->dir, newpath) < 0)
    {
      show_warning(_("Directory %s couldn't be renamed to %s: %s."),
	  context->dir, newpath, strerror(errno));
      efree(&newpath);
    }
    else
    {
      efree(&context->dir);
      context->dir = newpath;
      context_update_children_dirs(context->children);
    }
  }
  g_free(parentdir);

  prefs_set_string(context, "name", newname);
  preferences_save(context);
}

int
context_move (struct context *context, struct context *new_parent)
{
  struct context **child_ptr = context_find_child_ptr(context->parent, context);
  struct context **new_ptr = context_find_child_ptr(new_parent, NULL);
  char *newpath = context_newpath(NULL, new_parent->dir,
      prefs_get_string(context, "name"));

  if(newpath)
  {
    if(rename(context->dir, newpath) < 0)
    {
      show_warning(_("Directory %s couldn't be renamed to %s: %s."),
	  context->dir, newpath, strerror(errno));
      efree(&newpath);
    }
    else
    {
      efree(&context->dir);
      context->dir = newpath;
      context_update_children_dirs(context->children);
      *child_ptr = context->next;
      *new_ptr = context;
      context->next = NULL;
      context->parent = new_parent;
      return 1;
    }
  }
  else
    show_error(_("Can't move \"%s\" to \"%s\"."),
	prefs_get_string(context, "name"),
	prefs_get_string(new_parent, "name"));
  return 0;
}

/**
 * @brief Creates and initializes a context.
 * 
 * @param parent   New contexts parent.
 * @param name     (Optional) name of the new context, if NULL will be set to
 *                 unnamed_(scope|task).
 * @param filename (Optional) name of the openvasrc file which can be used to
 *                 initialize the new context.
 * 
 * @return Fresh context.
 */
struct context*
context_new (struct context* parent, const char* name, const char* filename)
{
  const char *dir;
  char *context_dir;
  struct context *context;

  if(parent->dir)
    dir = parent->dir;
  else
    dir = prefs_get_string(parent, "nessus_dir");

  if(!check_is_dir(dir) && (mkdir(dir, 0700) < 0))
  {
    show_error_and_wait(_("Directory %s couldn't be created: %s."),
	  dir, strerror(errno));
    return NULL;
  }
  context = context_create_child(parent);

  if(filename)
  {
    context->prefs = emalloc(sizeof(struct arglist));
    if(filename[0])
    {
      if(preferences_process_filename(context, estrdup(filename)))
      {
	context_remove_child(parent, context);
	return NULL;
      }
    }
    if(!name)
      name = prefs_get_string(context, "name");
  }

  if(!name)
    switch(context->type)
    {
      case CONTEXT_TASK:
	name = _("unnamed task");
	break;
      case CONTEXT_SCOPE:
	name = _("unnamed scope");
	break;
      default:
	show_error(_("context_new(): No name provided for context"));
	context_remove_child(parent, context);
	return NULL;
    }

  context_dir = context_newpath(NULL, dir, name);
  if(mkdir(context_dir, 0700) < 0)
  {
    show_error_and_wait(_("Directory %s couldn't be created: %s."),
	  context_dir, strerror(errno));
    context_remove_child(parent, context);
    efree(&context_dir);
    return NULL;
  }
  context->dir = context_dir;

  preferences_generate_new_file(context, name);

  context->signer_fp_certificates = g_hash_table_new_full(g_str_hash, 
                                    g_str_equal, NULL, (GDestroyNotify) openvas_certificate_free);
  context->map_target_sshlogin =  g_hash_table_new_full (g_str_hash, g_str_equal,
                                                        NULL, NULL);

  return context;
}


/**
 * @brief Removes a Context and its children, including the representation on
 * @brief disc.
 * 
 * @param context The context to remove.
 */
void
context_delete (struct context *context)
{
  while(context->children)
    context_delete(context->children);

  arg_free_all (context->prefs);
  context->prefs = NULL;
  context_reset_plugins (context);
  arg_free_all (context->dependencies);
  context->dependencies = NULL;
  if (file_utils_rmdir_rf (context->dir) == -1)
    show_error (_("Error removing files or directories\n(possible cause of error: '%s')"), strerror(errno));
  if (context->signer_fp_certificates)
    g_hash_table_destroy (context->signer_fp_certificates);
  if (context->map_target_sshlogin)
    g_hash_table_destroy (context->map_target_sshlogin);
  context_remove_child (context->parent, context);
  if (context == Context)
    {
      show_error(_("context_delete() deleted the current context."));
      Context = NULL;
    }
}

/**
 * @brief Recurses throught contexts and saves preferences and ssh login
 *        information.
 * 
 * This function should be called just before OpenVAS-client quits.
 * 
 * @param context The context to start with (usually Global).
 */
void
context_save_recurse (struct context *context)
{
  struct context *child = context->children;

  while(child && child->type <= CONTEXT_REPORT)
  {
    context_save_recurse(child);
    child = child->next;
  }
  // Save the target_sshlogin map of Tasks and Reports
  if (context->map_target_sshlogin && context->type >= CONTEXT_SCOPE)
  {
    char* fileloc = g_build_filename (context->dir, ".host_sshlogins", NULL);
    gboolean success = hash_table_file_write (context->map_target_sshlogin, fileloc);
    if (success == FALSE)
      {
        show_warning (_("Could not save ssh-login selection per target!\n"));
      }
    efree (&fileloc);
  }

  preferences_save(context);
}


/**
 * @brief Fill the plugin preferences from the information read from the
 * @brief report's nessusrc.
 */
static void
fill_plugin_prefs (struct context *context)
{
  struct arglist *pref = arg_get_value(context->prefs, "PLUGINS_PREFS");
  while (pref && pref->next)
  {
    char *value = NULL;
    if (pref->type == ARG_INT)
      value = pref->value ? "yes" : "no";
    else if (pref->type == ARG_STRING)
      value = pref->value;
    else
      fprintf(stderr, "unknown type\n");

    if (value != NULL)
      /* nessusrc and hence PLUGINS_PREFS may contain settings for old
       * plugins that are not available on the server anymore and which
       * are therefore also not in the plugin cache.  So we deactivate
       * the warnings about missing plugins in comm_parse_preference */
      comm_parse_preference(context, NULL, NULL, context->prefs,
	  pref->name, value, 0);

    pref = pref->next;
  }
}


/**
 * @brief Load the plugin cache of the context if it has one.
 * 
 * The context doesn't have plugin information yet and the user actually wants to
 * load plugin information for the given type of context.
 */
void
context_load_plugin_cache (struct context *context)
{
  if (context->plugins == NULL && !context->plugin_cache_loaded
      &&
      ((context->type == CONTEXT_REPORT
	  && prefs_get_int(Global, "reports_use_plugin_cache"))
       || (context->type == CONTEXT_SCOPE
	   && prefs_get_int(Global, "scopes_load_plugin_cache_immediately"))))
  {
    plugin_cache_read(context);
    context->plugin_cache_loaded = 1;
    fill_plugin_prefs(context);
    context_reset_plugin_tree(context);
  }
}

/**
 * @brief Syncs the plugin preferences (for plugins and scanners) for the given 
 *        context.
 * 
 * In fact, the plugin preferences are copied from
 * context->plugins[plugin][plugin-pref]
 * and
 * context->scanners->[plugin][plugin-pref]
 * to
 * context->prefs["PLUGINS_PREFS"][plugin]
 */
void
context_sync_plugin_prefs (struct context* context)
{
  struct arglist * plugins_prefs = arg_get_value(context->prefs, "PLUGINS_PREFS");
  struct nessus_plugin * plugins[2];
  int i;

  /* If there is yet no entry "PLUGINS_PREFS", add it now */
  if(!plugins_prefs)
  {
    plugins_prefs = emalloc(sizeof(struct arglist));
    arg_add_value(context->prefs, "PLUGINS_PREFS", ARG_ARGLIST, -1, plugins_prefs);
  }

  plugins[0] = context->plugins;
  plugins[1] = context->scanners;

  /* iterate over the two plugin sets */
  for (i = 0; i < 2; i++)
  {
    struct nessus_plugin * plugin = plugins[i];

    /* iterate over plugins */
    while (plugin != NULL)
    {
      struct arglist * plugin_prefs = plugin->plugin_prefs;

      /* iterate over the prefs of the plugin */
      while (plugin_prefs && plugin_prefs->next)
      {
        char * value = arg_get_value(plugin_prefs->value, "value");
        char * fullname = arg_get_value(plugin_prefs->value, "fullname");

        /* is this pref already in the plugins_prefs? */
        if ((arg_get_type(plugins_prefs, fullname)) >= 0)
        { /* yes, then just copy the value */
          if ((arg_get_type(plugins_prefs, fullname)) == ARG_INT)
          {
            if (!strcmp(value, "yes"))
              arg_set_value(plugins_prefs, fullname, sizeof(int), (void *)1);
            else
              arg_set_value(plugins_prefs, fullname, sizeof(int), NULL);
          }
          else
            arg_set_value(plugins_prefs, fullname, strlen(value), strdup(value));
        }
        else
        { /* no, then create a new pref */
          if (!strcmp(value, "yes"))
            arg_add_value(plugins_prefs, fullname, ARG_INT, sizeof(int), (void *)1);
          else if (!strcmp(value, "no"))
            arg_add_value(plugins_prefs, fullname, ARG_INT, sizeof(int), NULL);
          else
            arg_add_value(plugins_prefs, fullname, ARG_STRING, strlen(value),
                strdup(value));
        }

        plugin_prefs = plugin_prefs->next;
      }
      plugin = plugin->next;
    }
  }
}


/**
 * Replacement for g_file_test which is unreliable on windows
 * if nessus and gtk are compiled with a different libc.
 *
 * FIXME: handle symbolic links
 * @return 1 if file exists, 0 otherwise.
 * @TODO Move to other module.
 */
int
check_exists (const char *name)
{
  struct stat sb;

  if (stat (name, &sb))
    return 0;
  else
    return 1;
}

/**
 * Replacement for g_file_test which is unreliable on windows
 * if nessus and gtk are compiled with a different libc.
 * FIXME: handle symbolic links
 * @TODO Move to other module.
 */
int
check_is_file (const char *name)
{
  struct stat sb;

  if (stat (name, &sb))
    return 0;
  else
    return (S_ISREG(sb.st_mode));
}

/**
 * Replacement for g_file_test which is unreliable on windows
 * if nessus and gtk are compiled with a different libc.
 *
 * FIXME: handle symbolic links
 * @return 0 if parameter is directory, 1 if it is not (or does not exist).
 * @TODO Move to other module.
 */
int
check_is_dir (const char* name)
{
  struct stat sb;

  if (stat (name, &sb))
    return 0;
  else
    return (S_ISDIR(sb.st_mode));
}
