/* $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 "nessus_plugin.h"
#include "context.h"
#include "preferences.h"
#include "error_dlg.h"
#include "plugin_cache.h"
#include "comm.h"

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

struct context *Global;
struct context *Context;

void
context_init(context, parent)
  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;
#endif  
  (*context)->plugin_cache_loaded = 0;
}

struct context *
context_by_type(context, 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;
}


/* reset 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
}

/* 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
}


/* 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);
}

/*
 * 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.
 *
 * XXX: do we need hashing for pluginset?
 */
void
context_add_plugin(struct context *context, struct nessus_plugin *plugin)
{
  char *category = plugin->category;
  char *asc_id =   plugin->asc_id;
  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, asc_id) >= 0);
  int enabled = 0;

  if (in_pluginset)
  {
    if (arg_get_value(pluginset, asc_id))
      enabled = 1;
  }
  else if (!is_scanner)
    enabled = 1;

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

  if (!in_pluginset)
    arg_add_value(pluginset, asc_id, ARG_INT, sizeof(int), (void *)enabled);

  if (is_scanner)
    context->scanners = plugin;
  else
    context->plugins = plugin;
}


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(context, child)
  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(context)
  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(context, 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;
}

void
context_collect_recurse(context, dir)
  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);

      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, "nessusrc") && context->type < CONTEXT_REPORT)
	|| (!strcmp(file, "report.nbe") && context->type == CONTEXT_REPORT))
	context_found = TRUE;
      g_free(path);
      path = NULL;
    }
    closedir(odir);
    if(context->type != CONTEXT_GLOBAL && (context_found || context->children))
      preferences_init(context);
  }
}

void
context_collect(context)
  struct context *context;
{
  const char *dir = estrdup(prefs_get_string(context, "nessus_dir"));

  context_collect_recurse(context, dir, 0);
}


char *
context_newpath(oldpath, dir, name)
  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(context)
  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(context, newname)
  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(context, new_parent)
  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;
}

struct context*
context_new(parent, name, filename)
  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);

  return context;
}


void
context_delete_directory(dir)
  const char *dir;
{
  DIR *odir;
  struct dirent *dirent;

  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);

      if(check_is_dir(path))
	context_delete_directory(path);
      else
	if(unlink(path))
	  show_error_and_wait(_("File %s couldn't be deleted: %s."),
		path, strerror(errno));
      g_free(path);
    }
    closedir(odir);
    if(rmdir(dir))
      show_error_and_wait(_("Directory %s couldn't be deleted: %s."),
	    dir, strerror(errno));
  }
}

void
context_delete(context)
  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;
  context_delete_directory(context->dir);
  context_remove_child(context->parent, context);
  if(context == Context)
  {
    show_error(_("context_delete() deleted the current context."));
    Context = NULL;
  }
}

void
context_save_recurse(context)
  struct context *context;
{
  struct context *child = context->children;

  while(child && child->type <= CONTEXT_REPORT)
  {
    context_save_recurse(child);
    child = child->next;
  }
  preferences_save(context);
}


/* Fill the plugin preferences from the information read from the
 * 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;
  }
}


/* 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);
  }
}

/* This function sync 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(context)
  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;
    }
  }
}


/*
 * replacements for g_file_test which is unreliable on windows
 * if nessus and gtk are compiled with a different libc.
 *
 * FIXME: handle symbolic links
 */

int
check_exists(name)
  const char *name;
{
  struct stat sb;

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

int
check_is_file(name)
  const char *name;
{
  struct stat sb;

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

int
check_is_dir(name)
  const char *name;
{
  struct stat sb;

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