/* Nessus
 * Copyright (C) 1998 - 2001 Renaud Deraison
 * Copyright (C) 2004 Intevation GmbH
 *
 * 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.
 *
 * Preferences  -- maps the content of the nessusrc file to memory
 *
 */

#include <includes.h>

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

#include "nessus_plugin.h"
#include "nessus_i18n.h"
#include "context.h"
#include "preferences.h"
#include "globals.h"
#include "nessus.h"
#include "error_dialog.h"

static int preferences_new(struct context *);
static int prefs_buffer_parse(char *, struct arglist *, int);
static int prefs_add_subcategory(struct arglist *, FILE *, int);
static char *get_username();
static char *get_nessushome();
int preferences_process(struct context *);
char * preferences_get_filename(struct context *);

static int
plugin_id(plugin)
  struct arglist *plugin;
{
  return (int)arg_get_value(plugin, "ID");
}

char *
plugin_asc_id(plugin)
  struct arglist *plugin;
{
  static char asc_id[21];
  char *ret;

  if((ret = arg_get_value(plugin, "ASC_ID")))
    return ret;
  else
  {
    bzero(asc_id, sizeof(asc_id));
    sprintf(asc_id, "%d", plugin_id(plugin));
    arg_add_value(plugin, "ASC_ID", ARG_STRING, strlen(asc_id), asc_id);
    return asc_id;
  }
}

int
preferences_init(context)
  struct context *context;
{
  int result;

  context->prefs = emalloc(sizeof(struct arglist));
  result = preferences_process(context);
  if(result && getenv("NESSUSHOME") == 0)
    show_error_and_wait(CANNOT_SET_HOMEVAR);
  return (result);
}

/*
 * TODO : Under NT, the preference file should be put
 * at a propper place 
 * FIXED: nt used peks, which supports that feature
 */

char *
preferences_get_filename(context)
  struct context *context;
{
  char *nessusrc;

  if(context->dir)
  {
    nessusrc = emalloc(strlen(context->dir) + strlen("/nessusrc") + 1);
    sprintf(nessusrc, "%s/nessusrc", context->dir);
  }
  else
  {
    if(Alt_rcfile)
      nessusrc = estrdup(Alt_rcfile);
    else
    {
      char *home = get_nessushome();

      nessusrc = emalloc(strlen(home) + strlen("/.nessusrc") + 1);
      sprintf(nessusrc, "%s/.nessusrc", home);
    }
  }
  return nessusrc;
}

char *
preferences_get_altname(context, ext)
  struct context *context;
  const char *ext;
{
  char *nessusrc = preferences_get_filename(context);

  if(ext && strlen(ext))
  {
    char *ret;

    ret = emalloc(strlen(nessusrc) + 1 + strlen(ext) + 1);
    sprintf(ret, "%s.%s", nessusrc, ext);
    efree(&nessusrc);
    return ret;
  }
  else
    return nessusrc;
}

static int
preferences_new(context)
  struct context *context;
{
  FILE *f;
  char *fn = preferences_get_filename(context);

  if(!fn)
    return (-1);

  if(!(f = fopen(fn, "w")))
  {
    show_error_and_wait(_("Error writing %s: %s"),
	fn, strerror(errno));
    return -1;
  }

  if(context->type != CONTEXT_TASK && context->type != CONTEXT_REPORT)
  {
    fprintf(f, _("# Nessus Client Preferences File\n\n"));
#ifdef NESSUS_ON_SSL
# ifdef NESSUSD_CA
    if(strlen(NESSUSD_CA))
      fprintf(f, "trusted_ca = %s/cacert.pem\n", NESSUSD_CA);
# endif
#endif /* NESSUS_ON_SSL */
    fprintf(f, "begin(SCANNER_SET)\n");
    fprintf(f, "10180 = yes\n");
    fprintf(f, "10278 = no\n");
    fprintf(f, "10331 = no\n");
    fprintf(f, "10335 = yes\n");
    fprintf(f, "10841 = no\n");
    fprintf(f, "10336 = no\n");
    fprintf(f, "10796 = no\n");
    fprintf(f, "11219 = no\n");
    fprintf(f, "14259 = no\n");
    fprintf(f, "14272 = no\n");
    fprintf(f, "14274 = no\n");
    fprintf(f, "14663 = no\n");
    fprintf(f, "end(SCANNER_SET)\n\n");

    fprintf(f, "begin(SERVER_PREFS)\n");
    fprintf(f, " max_hosts = 20\n");
    fprintf(f, " max_checks = 4\n");
    /*
     * FIXME: see prefs_dialog_apply()
     * start of values from nessusd/preferences.c
     */
    fprintf(f, " cgi_path = /cgi-bin:/scripts\n");
    fprintf(f, " port_range = default\n");
    fprintf(f, " auto_enable_dependencies = yes\n");
    fprintf(f, " silent_dependencies = yes\n");
    /* end of values from nessusd/preferences.c */
    fprintf(f, "end(SERVER_PREFS)\n");
  }

  fclose(f);
  chmod(fn, 0600);
  efree(&fn);
  return (0);
}

int
preferences_process(context)
  struct context *context;
{
  return preferences_process_filename(context, NULL);
}

int
preferences_process_filename(context, filename)
  struct context *context;
  char *filename;
{
  FILE *fd;
  char *buffer;
  int default_filename = !filename;

  if(default_filename)
  {
    filename = preferences_get_filename(context);
    chmod(filename, 0600);
  }

  if(!(fd = fopen(filename, "r")))
  {
#ifndef NESSUSNT
    if(errno == EACCES)
    {
      show_error_and_wait(
	  _("The Nessus client doesn't have the right to read %s\n"),
	  filename);
      efree(&filename);
      return (1);
    }
#endif
    if(default_filename)
    {
      if(context->type == CONTEXT_TASK || context->type == CONTEXT_REPORT)
      {
	efree(&filename);
        return 0;
      }
#ifdef DEBUG
      show_info(_("Couldn't find prefs file... Creating a new one..."));
#endif
      if((preferences_new(context)) < 0)
      {
	show_error_and_wait(_("Error creating %s: %s"),
	    filename, strerror(errno));
	efree(&filename);
	return (1);
      }
      else if(!(fd = fopen(filename, "r")))
      {
	show_error_and_wait(_("Error creating %s: %s"),
	    filename, strerror(errno));
	efree(&filename);
	return(2);
      }
    }
    else
    {
      show_error(_("Error reading %s: %s"),
	  filename, strerror(errno));
	efree(&filename);
      return (1);
    }
  }

  buffer = emalloc(4096);
  while(!feof(fd) && fgets(buffer, 4096, fd))
  {
    if(strchr(buffer, '='))
      prefs_buffer_parse(buffer, context->prefs, 1);
    else if(!strncmp(buffer, "begin(", strlen("begin(")))
    {
      char *t = buffer + (strlen("begin(") * sizeof(char));
      char *end = strchr(t, ')');

      if(!end)
	show_warning(_("Parse error in %s: %s"), filename, buffer);
      else
      {
	char *category_name;
	struct arglist *subcategory;

	end[0] = 0;
	category_name = emalloc(strlen(t) + 1);
	strncpy(category_name, t, strlen(t));
	subcategory = emalloc(sizeof(struct arglist));
	/* When parsing a plugin set the order is not important and we
	 * can use a faster method to build the arglist */
	if(prefs_add_subcategory(subcategory, fd,
		strcmp(category_name, "PLUGIN_SET") != 0))
	  show_warning(_("Missing 'end' in %s"), filename);
	else
	  arg_add_value(context->prefs, category_name, ARG_ARGLIST, -1, subcategory);
      }
    }
  }
  fclose(fd);
  efree(&filename);
  return (0);
}

static int
prefs_add_subcategory(arglist, fd, keep_order)
  struct arglist *arglist;
  FILE *fd;
  int keep_order;
{
  char *buffer = emalloc(4096);
  int flag = 0;

  while(!flag && !feof(fd) && fgets(buffer, 4096, fd))
  {
    if(!strlen(buffer))
      return (1);
    if((!strcmp(buffer, "end\n")) || (!strncmp(buffer, "end(", 4)))
      flag = 1;
    else
      prefs_buffer_parse(buffer, arglist, keep_order);
    bzero(buffer, 255);
  }
  efree(&buffer);
  return (0);
}

static int
prefs_buffer_parse(buffer, arglist, keep_order)
  char *buffer;
  struct arglist *arglist;
  int keep_order;
{
  char *t;
  char *opt;
  char *value;
  int val = -1;

  /* If we need to heep the order, we have to use arg_add_value,
   * otherwise we can use the faster (for long lists much faster)
   * arg_add_value_at_head */
  void (*arg_add)(struct arglist *, const char *, int, long, void *)
    = keep_order ? arg_add_value : arg_add_value_at_head;

  if(buffer[strlen(buffer) - 1] == '\n')
    buffer[strlen(buffer) - 1] = 0;
  if(buffer[0] == '#')
    return (1);
  opt = buffer;
/* remove the spaces before the pref name */
  if(opt[0] == ' ' && opt[0])
    opt += sizeof(char);
  if((t = strchr(buffer, '=')))
  {
    t[0] = 0;
    t += sizeof(char);
    while(t[0] == ' ' && t[0])
      t += sizeof(char);
    if(!t[0])
      return (1);
/* remove the spaces after the pref name */
    while(opt[strlen(opt) - 1] == ' ')
      opt[strlen(opt) - 1] = 0;

/* char to int conversion if necessary */
    if(!strcmp(t, "yes"))
      val = 1;
    if(!strcmp(t, "no"))
      val = 0;

    if(!strcmp(opt, "paranoia_level") || !strcmp(opt, "nessusd_port"))
    {
      arg_add(arglist, opt, ARG_INT, sizeof(int), (void *)atoi(t));
    }
    else
    {
      if(val == -1)
      {
	/* the string is not 'yes' nor 'no' so we take it as a string */
	value = emalloc(strlen(t) + 1);
	strncpy(value, t, strlen(t));
	arg_add(arglist, opt, ARG_STRING, strlen(value), value);
      }
      else
	arg_add(arglist, opt, ARG_INT, sizeof(int), (void *)val);
    }
    return (0);
  }
  else
    return (1);
}


static void
new_pluginset(pluginset, plugins)
  struct arglist *pluginset;
  struct nessus_plugin *plugins;
{
  while(plugins != NULL )
  {
    char name[32];

    snprintf(name, sizeof(name), "%d", plugins->id);
    arg_add_value(pluginset, name, ARG_INT, sizeof(int), (void*)(plugins->enabled != 0) );
    plugins = plugins->next;
  }
}

struct arglist *
prefs_get_pluginset(context, name, plugins)
  struct context *context;
  char *name;
  struct nessus_plugin *plugins;
{
  struct arglist *plugin_set = arg_get_value(context->prefs, name);

  if(!plugin_set)
  {
    plugin_set = emalloc(sizeof(struct arglist));
    arg_add_value(context->prefs, name, ARG_ARGLIST, -1, plugin_set);
    new_pluginset(plugin_set, plugins);
  }
  return plugin_set;
}


#define MAGIC 8197
struct hash
{
  int name;
  struct arglist *v;
  struct hash *next;
};

static struct arglist *
hash_get(struct hash **hash, int id)
{
  int idx = id % MAGIC;
  struct hash *h = hash[idx];

  while(h != NULL)
  {
    if(h->name == id)
      return h->v;
    h = h->next;
  }
  return NULL;
}

/*
 * update arglists "PLUGIN_SET" or "SCANNER_SET"
 * from context->plugins or context->scanners
 */
void
pluginset_reload(context, name, plugins)
  struct context *context;
  char *name;
  struct nessus_plugin *plugins;
{
  struct arglist *pluginset;
  struct hash **hash;
  struct hash *h;
  int i;

  if(!plugins)
    return;

  hash = emalloc(MAGIC * sizeof(struct hash *));
  pluginset = prefs_get_pluginset(context, name, plugins);

  while(pluginset->next)
  {
    int id = atoi(pluginset->name);
    int idx = id % MAGIC;

    h = emalloc(sizeof(struct hash));
    h->name = id;
    h->v = pluginset;
    h->next = hash[idx];
    hash[idx] = h;
    pluginset = pluginset->next;
  }

  while(plugins != NULL )
  {
    int id = plugins->id;
    struct arglist *pluginset_entry = hash_get(hash, id);

    if(pluginset_entry != NULL )
       pluginset_entry->value = (void *)(plugins->enabled != 0 );
    plugins = plugins->next;
  }

  i = MAGIC;
  while(i--)
    efree(hash+i);
  efree(&hash);
}

void
preferences_save(context)
  struct context *context;
{
  char *filename = preferences_get_filename(context);

  preferences_save_as(context, filename);
  efree(&filename);
}

void
preferences_save_as(context, filename)
  struct context *context;
  char *filename;
{
  FILE *fd;
  struct arglist *t;

  fd = fopen(filename, "w");
  if(!fd)
  {
    show_error(_("%s could not be opened write only"), filename);
    return;
  }
  chmod(filename, 0600);
  fprintf(fd, _("# This file was automagically created by nessus\n"));

  pluginset_reload(context, "PLUGIN_SET", context->plugins);
  pluginset_reload(context, "SCANNER_SET", context->scanners);

  t = context->prefs;
  while(t && t->next)
  {
    if((int)t->type == ARG_INT)
    {
      if(!strcmp(t->name, "paranoia_level")
	  || !strcmp(t->name, "nessusd_port"))
	fprintf(fd, "%s = %d\n", t->name, (int)t->value);
      else
	fprintf(fd, "%s = %s\n", t->name, t->value ? "yes" : "no");
    }
    else if((t->type == ARG_STRING) && (strlen(t->value)))
      fprintf(fd, "%s = %s\n", t->name, (char *)t->value);
    t = t->next;
  }

  t = context->prefs;
  while(t && t->next)
  {
    if(t->type == ARG_ARGLIST)
    {
      struct arglist *v;

      v = t->value;
      fprintf(fd, "begin(%s)\n", t->name);
      while(v && v->next)
      {
	if(!strcmp(v->name, "plugin_set"))
	{
	  v = v->next;
	  continue;
	}
	if(v->type == ARG_INT)
	  fprintf(fd, " %s = %s\n", v->name, v->value ? "yes" : "no");
	else if((v->type == ARG_STRING) && v->value)
	{
	  if(!strcmp(t->name, "PLUGINS_PREFS") || ((char *)v->value)[0])
	    fprintf(fd, " %s = %s\n", v->name, (char *)v->value);
	}
	v = v->next;
      }
      fprintf(fd, "end(%s)\n\n", t->name);
    }
    t = t->next;
  }

  fclose(fd);
}


int
preferences_generate_new_file(context, name)
  struct context *context;
  const char *name;
{
  struct context *copy_from = context->parent;

  if(!context->prefs)
  {
    if(context->type == CONTEXT_TASK)
      copy_from = NULL;
    else if(context->type == CONTEXT_SCOPE)
      copy_from = Global;

    if(copy_from)
    {
      context_type type = copy_from->type;
      char *filename = preferences_get_filename(context);

      copy_from->type = context->type;
      preferences_save_as(copy_from, filename);
      copy_from->type = type;
      efree(&filename);
    }
    else
      preferences_new(context);

    preferences_init(context);
  }
  if(name && name[0])
    prefs_set_string(context, "name", name);
  preferences_save(context);
  return 0;
}


#ifdef USE_GTK
gboolean
prefs_is_executable(name)
  gchar *name;
{
  gchar *prog = g_find_program_in_path(name);

  if(prog)
  {
    g_free(prog);
    return TRUE;
  }
  else
    return FALSE;
}
#endif /* USE_GTK */

void *
prefs_get_default(context, name)
  struct context *context;
  const char *name;
{
  if(!strcmp(name, "name"))
  {
    char *name = NULL;
    if(context->type == CONTEXT_GLOBAL)
      name = _("Global Settings");
    else if(context->dir)
      name = strrchr(context->dir, '/') + 1;
    if(!name)
      name = NULL;
    return name;
  }
  else if(context->parent)
    return prefs_get_default(context->parent, name);
  else if(!strcmp(name, "nessusd_host"))
#ifdef DEFAULT_SERVER
    return DEFAULT_SERVER;
#else
    return "localhost";
#endif
  else if(!strcmp(name, "nessusd_user"))
    return get_username();
  else if(!strcmp(name, "nessusd_port"))
    return (void *)NESIANA_PORT;
#ifdef NESSUS_ON_SSL
  else if(!strcmp(name, "use_ssl"))
    return (void *)1;
  else if(!strcmp(name, "ssl_version"))
    return SSL_VER_DEF_NAME;
#endif /* NESSUS_ON_SSL */
  else if(!strcmp(name, "nessus_dir"))
  {
    static char *nessus_dir;
    if(!nessus_dir)
    {
      char *nessushome = get_nessushome();
      nessus_dir = emalloc(strlen(nessushome) + strlen("/.nessus") + 1);
      sprintf(nessus_dir, "%s/.nessus", nessushome);
    }
    return nessus_dir;
  }
#ifdef USE_GTK
  else if(!strcmp(name, "pdfviewer"))
  {
    static char *pdfviewer;
    if(!pdfviewer)
    {
      if(prefs_is_executable("gpdf"))
	pdfviewer = "gpdf";
      else if(prefs_is_executable("kpdf"))
	pdfviewer = "kpdf";
      else if(prefs_is_executable("xpdf"))
	pdfviewer = "xpdf";
      else if(prefs_is_executable("ggv"))
	pdfviewer = "ggv";
      else if(prefs_is_executable("gv"))
	pdfviewer = "gv";
      else if(prefs_is_executable("acroread"))
	pdfviewer = "acroread";
      else if(prefs_is_executable("acrord32"))
	pdfviewer = "acrord32";
    }
    return pdfviewer;
  }
#endif /* USE_GTK */
  else if(!strcmp(name, "url_cve"))
    return "http://cgi.nessus.org/cve.php3?cve=%s-%s-%s";
  else if(!strcmp(name, "url_bid"))
    return "http://cgi.nessus.org/bid.php3?bid=%s";
  else if(!strcmp(name, "url_nessus"))
    return "http://cgi.nessus.org/nessus_id.php3?id=%s";
  else if(!strcmp(name, "targets"))
    return "localhost";
  else if(!strcmp(name, "tree_autoexpand"))
    return (void *)1;
  else
    return NULL;
}

void *
prefs_get_value(context, name)
  struct context *context;
  const char *name;
{
  void *value = arg_get_value(context->prefs, name);
  int type = arg_get_type(context->prefs, name);

  if(type < 0 || (type != ARG_INT && value == NULL)
      || (type == ARG_STRING && !strlen((const char *)value)))
    value = prefs_get_default(context, name);
  return value;
}

void
prefs_set_value(context, name, value, type)
  struct context *context;
  const char *name;
  void *value;
  int type;
{
  int len;
  int arg_type;

  switch (type)
  {
    case ARG_INT:
      len = sizeof(int);
      break;
    case ARG_STRING:
      len = strlen(value);
      value = estrdup((char *)value);
      break;
    default:
      show_error(_("prefs_set_value() called with illegal type"));
      return;
  }

  arg_type = arg_get_type(context->prefs, name);
  if(arg_type < 0)
    arg_add_value(context->prefs, name, type, len, value);
  else if(arg_type == type)
  {
    if(type == ARG_STRING)
    {
      char *old_value = arg_get_value(context->prefs, name);

      efree(&old_value);
    }
    arg_set_value(context->prefs, name, len, value);
  }
  else
  {
    if(type == ARG_STRING)
      efree(&value);
    show_error(_("prefs_set_value() called with illegal type"));
  }
}

void
prefs_set_int(context, name, value)
  struct context *context;
  const char *name;
  int value;
{
  prefs_set_value(context, name, (void *)value, ARG_INT);
}

void
prefs_set_string(context, name, value)
  struct context *context;
  const char *name;
  const char *value;
{
  const char *default_value = prefs_get_default(context, name);
  if(!default_value || strcmp(value, default_value))
    prefs_set_value(context, name, (void *)value, ARG_STRING);
  else
    prefs_set_value(context, name, (void *)"", ARG_STRING);
}

/* are there any options besides "name" and "comment"? */
int
prefs_has_options(context)
  struct context *context;
{
  struct arglist *t = context->prefs;

  while(t && t->next)
  {
    if(strcmp(t->name, "name") && strcmp(t->name, "comment"))
      return 1;
    t = t->next;
  }

  return 0;
}


/*
 * get_username : returns the name of the current user
 */
static char *
get_username(void)
{
  char *user;
  struct passwd *pwd;

  /* Look up the user's name. */
  user = getenv("USER");
  if(user)
    return user;

  user = getenv("LOGNAME");
  if(user)
    return user;

  pwd = getpwuid(getuid());
  if(pwd && pwd->pw_name)
    return pwd->pw_name;

  return "";
}

static char *
get_nessushome(void)
{
  char *home;
  struct passwd *pwd;

  home = getenv("NESSUSHOME");
  if(home)
    return home;

  home = getenv("HOME");
  if(home)
    return home;

  pwd = getpwuid(getuid());
  if(pwd && pwd->pw_dir)
    return pwd->pw_dir;

  return "";
}
