/* 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.
 *
 * Nessus Communication Manager
 *
 */

#include <includes.h>

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

#include "auth.h"
#include "comm.h"
#include "nessus_plugin.h"
#include "context.h"
#include "preferences.h"
#include "parser.h"
#include "globals.h"
#include "error_dlg.h"
#include "plugin_cache.h"

#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif


/* Currently active time-consuming task */
#define COMM_GET_PLUGINS 1
#define COMM_GET_DEPENDENCIES 2

/*
 * Update the UI while receiving plugin or dependency information.
 * For GTK this is displayed with a progress bar (maybe this should
 * be moved to a separate prefs_progressbar module)
 */
static void
comm_update_ui(context, current)
  struct context *context;
  int current;
{
#ifdef USE_GTK
  static int previous = 0;
  static int number;
  static int base;
  static int limit;
  static const char *fmt;
  gchar *pbar_text;
  /* maximum number of steps for the progress bar (plugins + dependencies) */
# define PBAR_MAX 12000

  if(F_quiet_mode)
    return; /* there is no UI to update in quiet mode */

  /* the task changes */
  if(previous != current)
  {
    previous = current;
    number = 0;
    switch(current)
    {
      case COMM_GET_PLUGINS:
	fmt = _("Receiving plugins: %d");
	base = 0;
	limit = PBAR_MAX;
	break;
      case COMM_GET_DEPENDENCIES:
	fmt = _("Receiving dependencies: %d");
	base = 0;
	limit = PBAR_MAX;
	break;
    }
  }

  /* Update progress bar every 10 plugins (every 100 on slow links) */
  if(++number % (F_show_pixmaps?100:1000) == 0)
  {
    /* if the progress bar is too short, step back 5% */
    if(base+number >= limit)
      base -= PBAR_MAX/20;

    /* update the progress bar */
    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(context->pbar),
	1.0*(base+number)/PBAR_MAX);

    /* update text displayed in the progress bar */
    pbar_text = g_strdup_printf(fmt, number);
    gtk_progress_bar_set_text(GTK_PROGRESS_BAR(context->pbar), pbar_text);
    g_free(pbar_text);

    /* perform pending GUI events */
     gtk_main_iteration();
  }

#endif /* USE_GTK */
}

int comm_send_file(struct context *, char *);

/*
 * Parses a plugin description message, and returns an arglist with the
 * plugin in it.
 */
static struct nessus_plugin *
parse_plugin(buf)
  char *buf;
{
  char *str = NULL;
  char *t;
  size_t l;

  int    i_id;
  char id[32];
  char * name = NULL;
  char * category = NULL;
  char * copyright = NULL;
  char * description = NULL;
  char * summary = NULL;
  char * family = NULL;
  char * version = NULL;
  char * cve = NULL;
  char * bid = NULL;
  char * xref = NULL;
  struct nessus_plugin * ret;

  sscanf(buf, "%d", &i_id);

  
  snprintf(id, sizeof(id), "%d", i_id);
  l = strlen(id);

  str = parse_separator(buf);
  if(!str)
    return NULL;

  name = str;


  l += strlen(str) + 5;

  str = parse_separator(buf + l);
  if(!str) return NULL;
  category = str;

  l += strlen(str) + 5;
  str = parse_separator(buf + l);
  if(!str) return NULL;
  copyright = str;

  l += strlen(str) + 5;


  str = parse_separator(buf + l);
  if(!str) return NULL;
  t = str;
  while((t = strchr(t, ';')))
    t[0] = '\n';

  description = str;
  l += strlen(str) + 5;

  str = parse_separator(buf + l);
  if(!str) return NULL;
  summary = str;

  l += strlen(str) + 5;

  str = parse_separator(buf + l);
  if(!str) return NULL;
  family = str;


  l += strlen(str) + 5;
  str = parse_separator(buf + l);
  if(str)
  {
    version = str;


    l += strlen(str) + 5;
    str = parse_separator(buf + l);
    if(str != NULL)
    {
      cve = str;
      l += strlen(str) + 5;

      str = parse_separator(buf + l);
      if(str != NULL)
      {
	bid = str;
	l += strlen(str) + 5;
	str = parse_separator(buf + l);
	if(str != NULL) xref = str;
      }
    }
  }

  ret = nessus_plugin_new(id, name, category, copyright, description, summary, family, version, cve, bid, xref);   
  return ret;
}






/*
 * comm_init
 *
 * This function initializes the communication between 
 * the server and the client.
 * Its role is to check that the remote server is using the
 * protocol specified in the argument proto_name
 * 
 * Arguments :
 *  soc : a socket connected to the remote server
 *  proto_name : the protocol with which the client intends to communicate
 * Returns :
 *  0 if the remote server is using proto_name
 * -1 if it's not
 */

int
comm_init(soc, proto_name)
  int soc;
  char *proto_name;
{
  char *buf;
  int n = strlen(proto_name);

  /* What shall I do if it fails? */
  (void)write_stream_connection(soc, proto_name, n);

  buf = emalloc(15);
  recv_line(soc, buf, 14);
  if(strncmp(buf, proto_name, 11))
  {
    efree(&buf);
    return (-1);
  }
  efree(&buf);
  return (0);
}


/* Parse a preference and add it to the context.  The preference may
 * either be anything the server sends in the PREFERENCES message.
 * Depending on the type of the preferences it will be stored in
 * serv_prefs, serv_infos or plugs_prefs.  If the caller knows that the
 * preference is a plugin preference the serv_prefs and serv_infos
 * parameters may be NULL.
 *
 * If the parameter warn_about_unknown_plugins is true, a warning is
 * printed to stderr if a preference refers to an unknown plugin.
 */
int
comm_parse_preference(struct context *context,
    struct arglist *serv_prefs, struct arglist *serv_infos,
    struct arglist *plugs_prefs, char *buf, char *value,
    int warn_about_unknown_plugins)
{
  char *pref;
  char *v;
  char *a = NULL, *b = NULL, *c = NULL;

  pref = buf;

  v = emalloc(strlen(value) + 1);
  strncpy(v, value, strlen(value));
  a = strchr(pref, '[');
  if(a)
    b = strchr(a, ']');
  if(b)
    c = strchr(b, ':');

  if ((!a) || (!b) || (!c))
  {
#ifdef ENABLE_SAVE_TESTS
    if (!strcmp(pref, "ntp_save_sessions"))
      context->sessions_saved = 1;
    else if (!strcmp(pref, "ntp_detached_sessions"))
      context->detached_sessions_saved = 1;
    else
#endif
      if (!strncmp(pref, "server_info_", strlen("server_info_")))
      {
	if (serv_infos == NULL)
	{
	  fprintf(stderr, "comm_parse_preference: server info preference given"
	      " but serv_infos is NULL\n");
	  return 0;
	}
	arg_add_value(serv_infos, pref, ARG_STRING, strlen(v), v);
      }
      else
      {
	if (serv_prefs == NULL)
	{
	  fprintf(stderr, "comm_parse_preference: server preference given"
	      " but serv_prefs is NULL\n");
	  return 0;
	}
	/* Don't set the value if set already */
	if (arg_get_type(serv_prefs, pref) < 0)
	  arg_add_value(serv_prefs, pref, ARG_STRING, strlen(v), v);
      }
  }
  else if (F_quiet_mode)
  {
    /*
     * Note that when using the cli,
     * the plugin prefs are not stored the same way in memory
     */
    if (arg_get_type(plugs_prefs, pref) < 0)
    {
      char *x = strchr(v, ';');

      if (!ListOnly && x)
	x[0] = '\0';
      arg_add_value(plugs_prefs, pref, ARG_STRING, strlen(v), v);
    }
  }
  else
  {
    /* the format of the pref name is xxxx[xxxx] : this is a plugin pref */
    char *plugname;
    char *type;
    char *name;
    struct arglist *pprefs, *prf;
    char *fullname = strdup(pref);
    struct nessus_plugin * plugin = NULL;

    while (fullname[strlen(fullname) - 1] == ' ')
      fullname[strlen(fullname) - 1] = '\0';
    a[0] = 0;
    plugname = emalloc(strlen(pref) + 1);
    strncpy(plugname, pref, strlen(pref));

    a[0] = '[';
    a++;
    b[0] = 0;
    type = emalloc(strlen(a) + 1);
    strncpy(type, a, strlen(a));
    b[0] = ']';
    c++;
    name = emalloc(strlen(c) + 1);
    strncpy(name, c, strlen(c));

    plugin = nessus_plugin_get_by_name(context->plugins, plugname);
    if (plugin == NULL)
    {
      plugin = nessus_plugin_get_by_name(context->scanners, plugname);
      if (plugin == NULL)
      {
	if (warn_about_unknown_plugins)
	{
	  fprintf(stderr,
	      _("Error : we received a preference (%s) for the plugin %s\n"),
	      name, plugname);
	  fprintf(stderr,
	      _("but apparently the server has not loaded it\n"));
	}
	return 0;
      }
    }
    pprefs = plugin->plugin_prefs;
    if (pprefs == NULL)
    {
      pprefs = emalloc(sizeof(struct arglist));
      plugin->plugin_prefs = pprefs;
    }

    if (arg_get_value(pprefs, name) == NULL)
    {
      char * value = NULL;

      prf = emalloc(sizeof(struct arglist));

      /*
       * No default value for files to upload (we don't want the
       * server to suggest we upload /etc/shadow ;)
       */

      if (arg_get_type(plugs_prefs, fullname) == ARG_INT)
      {
	int d = (int)arg_get_value(plugs_prefs, fullname);
	if (d == 0) value = "no";
	else value = "yes";
      }
      else if (arg_get_type(plugs_prefs, fullname) == ARG_STRING)
	value = arg_get_value(plugs_prefs, fullname);
      else
      {
	if (!strcmp(type, PREF_FILE))
	  value = "";
	else
	  value = v;
      }

      /* Check whether it is a radiobutton plugin preference and whether
       * it's value has no ';'. If so, it comes from an old, broken nessusrc file
       * and we have to repair it:
       * Make the found value the first one and add all others reported from the
       * server (in variable v) append to it, separated by semicolons.
       * This will keep the old value the selected one.
       */
      if (! strcmp(type, "radio") && ! strchr(value, ';')) {
        char * s = strstr(v, value);
        if (s) {
          if (s == v)
            /* it is the first in the list anyway, so simply take
             * the server string */
            value = estrdup(v);
          else {
            char * p;
            /* append the stuff before the found substring to value */
            s[0] = 0;
	    p = emalloc(strlen(value) + 1 + strlen(v) + 1);
	    strncpy(p, value, strlen(value));
	    value = p;
            value = strcat(value, ";");
            value = strcat(value, v);

            /* take care of the stuff after the found substring and append
             * it also to value */
            v = strstr(s, ";");
            if (v) {
              *v ++;
              value = (char *) erealloc(value, strlen(value) + strlen(v) + 1);
              value = strcat(value, v);
            } else {
              /* in this case we a ; to much in value already */
              value[strlen(value)-1] = 0;
            }
          }
        } else
          /* should acutally not happen, but then take server string */
          value = estrdup(v);
      }

      arg_add_value(prf, "value", ARG_STRING, strlen(value), value);
      arg_add_value(prf, "type", ARG_STRING, strlen(type), type);
      arg_add_value(prf, "fullname", ARG_STRING, strlen(fullname), fullname);
      arg_add_value(pprefs, name, ARG_ARGLIST, -1, prf);
    }
  }

  return 0;
}



/*
 * Retrieves the server preferences
 * we must make a difference between the prefs of the 
 * server itself, and the prefs of the plugins
 */
int
comm_get_preferences(context)
  struct context *context;
{
  char *buf = emalloc(32768);
  int finished = 0;
  struct arglist *serv_prefs, *serv_infos, *plugs_prefs = NULL;
  struct arglist *prefs = context->prefs;

#ifdef ENABLE_SAVE_TESTS
  context->sessions_saved = 0;
  context->detached_sessions_saved = 0;
#endif

  serv_prefs = arg_get_value(prefs, "SERVER_PREFS");
  if(!serv_prefs)
  {
    serv_prefs = emalloc(sizeof(struct arglist));
    arg_add_value(context->prefs, "SERVER_PREFS", ARG_ARGLIST, -1, serv_prefs);
  }

  serv_infos = emalloc(sizeof(struct arglist));
  if(arg_get_value(prefs, "SERVER_INFO"))
  {
    arg_free_all(arg_get_value(prefs, "SERVER_INFO"));
    arg_set_value(context->prefs, "SERVER_INFO", -1, serv_infos);
  }
  else
    arg_add_value(context->prefs, "SERVER_INFO", ARG_ARGLIST, -1, serv_infos);

  plugs_prefs = arg_get_value(prefs, "PLUGINS_PREFS");
  if(!plugs_prefs)
    {
      plugs_prefs = emalloc(sizeof(struct arglist));
      arg_add_value(context->prefs, "PLUGINS_PREFS", ARG_ARGLIST, -1, plugs_prefs);
    }

  network_gets(context->socket, buf, 32768);
  if(!strncmp(buf, "SERVER <|> PREFERENCES <|>", 26))
  {
    while(!finished)
    {
      bzero(buf, 32768);
      network_gets(context->socket, buf, 32768);;
      if(buf[strlen(buf) - 1] == '\n')
	buf[strlen(buf) - 1] = 0;
      if(!strncmp(buf, "<|> SERVER", 10))
	finished = 1;
      else
      {
	char *value;
	char *v = strchr(buf, '<');
	if (!v)
	  continue;
	v -= 1;
	v[0] = 0;
	value = v + 5;

	comm_parse_preference(context, serv_prefs, serv_infos, plugs_prefs,
	    buf, value, 1);
      }
    }
  }
  efree(&buf);
  return (0);
}


static int
cli_send_prefs_arglist(context, pref, upload, pprefs)
  struct context * context;
  struct arglist *pref;
  harglst **upload;
  int pprefs;
{
  if(!pref)
    return -1;

  while(pref->next)
  {
    if(pref->type == ARG_STRING)
    {
      if(strstr(pref->name, "[" PREF_FILE "]:"))
      {
	if(!*upload)
	  *upload = harg_create(50);
	harg_add_int(*upload, pref->value, 1);
      }

    network_printf(context->socket, "%s <|> %s\n", pref->name, pref->value);
    }
    else if(pref->type == ARG_INT)
    {
      network_printf(context->socket, "%s <|> %s\n", pref->name, pref->value ? "yes" : "no");
    }
    pref = pref->next;
  }
  return 0;
}


static int
cli_comm_send_preferences(context)
  struct context *context;
{
  struct arglist *preferences = context->prefs;
  harglst *files_to_send = NULL;
  struct arglist *pref = arg_get_value(preferences, "SERVER_PREFS");
  struct arglist *pprefs = arg_get_value(preferences, "PLUGINS_PREFS");

  network_printf(context->socket, "CLIENT <|> PREFERENCES <|>\n");
  /*
   * workaround to use new features while keeping
   * backward compatibility
   */
  network_printf(context->socket, "ntp_opt_show_end <|> yes\n");
  network_printf(context->socket, "ntp_keep_communication_alive <|> yes\n");
  network_printf(context->socket, "ntp_short_status <|> yes\n");
  network_printf(context->socket, "ntp_client_accepts_notes <|> yes\n");
  network_printf(context->socket, "ntp_escape_crlf <|> yes\n");
  if(pref)
    cli_send_prefs_arglist(context, pref, &files_to_send, 0);
  if(pprefs)
    cli_send_prefs_arglist(context, pprefs, &files_to_send, 1);
  network_printf(context->socket, "<|> CLIENT\n");
  if(files_to_send)
  {
    hargwalk *hw;
    char *key;

    hw = harg_walk_init(files_to_send);
    while((key = (char *)harg_walk_next(hw)))
    {
      comm_send_file(context, key);
    }
    harg_close_all(files_to_send);	/* frees memory */
  }
  return (0);
}



static int
gui_comm_send_preferences(context)
  struct context *context;
{
  struct arglist *preferences = context->prefs;
  harglst *files_to_send = NULL;
  struct arglist *pref = arg_get_value(preferences, "SERVER_PREFS");
  struct nessus_plugin *plugins[2];
  int i;

  context_sync_plugin_prefs(context);

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

  network_printf(context->socket, "CLIENT <|> PREFERENCES <|>\n");
  /*
   * workaround to use new features while keeping
   * backward compatibility
   */
  network_printf(context->socket, "ntp_opt_show_end <|> yes\n");
  network_printf(context->socket, "ntp_keep_communication_alive <|> yes\n");
  network_printf(context->socket, "ntp_short_status <|> yes\n");
  network_printf(context->socket, "ntp_client_accepts_notes <|> yes\n");
  network_printf(context->socket, "ntp_escape_crlf <|> yes\n");
  while(pref && pref->next)
  {
    if(pref->type == ARG_STRING)
    {
      network_printf(context->socket, "%s <|> %s\n", pref->name, pref->value);
    }
    else if(pref->type == ARG_INT)
    {
      network_printf(context->socket, "%s <|> %s\n", pref->name, pref->value ? "yes" : "no");
    }
    pref = pref->next;
  }

  /* send the plugins prefs back to the server */
  for(i = 0; i < 2; i++)
  {
    struct nessus_plugin *plugs = plugins[i];

    while(plugs != NULL )
    {
      struct arglist *plugin_prefs = plugs->plugin_prefs;
      while(plugin_prefs && plugin_prefs->next)
      {
	char *name = plugin_prefs->name;
	char *type = arg_get_value(plugin_prefs->value, "type");
	char *value = arg_get_value(plugin_prefs->value, "value");

        /* This cuts down the list of alternatives (separated by ';')
         * down to just the first element, because the Nessus Server
         * does not handle cutting itself.
         * Background: The value of a "radio" preference is the set
         * of possible values at one point (sent by Server and managed
         * within the NessusClient) and the value chosen by the user at another
         * (execution time of a scan by Nessus Server).
         */
        if(!strcmp(type, PREF_RADIO))
        {
          char * t, * v;
          v = strdup(value);
          if (v && (t = strchr(v, ';')))
            t[0] = 0;
          network_printf(context->socket, "%s[%s]:%s <|> %s\n", plugs->name, type, name, v);
          free(v);
        } else
          network_printf(context->socket, "%s[%s]:%s <|> %s\n", plugs->name, type, name, value);

	if(!strcmp(type, PREF_FILE))
	{
	  if(!files_to_send)
	    files_to_send = harg_create(50);
	  harg_add_int(files_to_send, value, 1);
	}
	plugin_prefs = plugin_prefs->next;
      }
      plugs = plugs->next;
    }
  }
  network_printf(context->socket, "<|> CLIENT\n");
  if(files_to_send != NULL )
  {
    hargwalk *hw;
    char *key;

    hw = harg_walk_init(files_to_send);
    while((key = (char *)harg_walk_next(hw)))
    {
      comm_send_file(context, key);
    }
    harg_close_all(files_to_send);	/* frees memory */
  }
  return (0);
}



int
comm_send_preferences(context)
  struct context *context;
{
  if(F_quiet_mode)
    return cli_comm_send_preferences(context);
  else
    return gui_comm_send_preferences(context);
}

int
comm_send_file(context ,fname)
  struct context * context;
  char *fname;
{
  int fd;
  struct stat stt;
  long tot = 0;
  char buff[1024];
  int len;

  if(!fname || !strlen(fname))
    return 0;

  fd = open(fname, O_RDONLY);
  if(fd < 0)
  {
    show_error(_("Can't open %s: %s"), fname, strerror(errno));
    return -1;
  }

  fstat(fd, &stt);
  len = (int)stt.st_size;
  network_printf(context->socket, "CLIENT <|> ATTACHED_FILE\n");
  network_printf(context->socket, "name: %s\n", fname);
  network_printf(context->socket, "content: octet/stream\n");
  network_printf(context->socket, "bytes: %d\n", len);
  tot = len;
  while(tot > 0)
  {
    int m = 0, n;

    bzero(buff, sizeof(buff));
    n = read(fd, buff, MIN(tot, sizeof(buff)));
    while(m < n)
    {
      int e;

      e = nsend(context->socket, buff + m, n - m, 0);
      if(e < 0)
      {
	show_error(_("Error reading from %s: %s"), fname, strerror(errno));
	close(fd);
	return -1;
      }
      else
	m += e;
    }
    tot -= n;
  }
  network_gets(context->socket, buff, sizeof(buff) - 1);
  return 0;
}

int
comm_send_rules(context)
  struct context *context;
{
  struct arglist *rules = arg_get_value(context->prefs, "CLIENTSIDE_USERRULES");

  network_printf(context->socket, "CLIENT <|> RULES <|>\n");
  while(rules && rules->next)
  {
    network_printf(context->socket, "%s\n", rules->value);
    rules = rules->next;
  }
  network_printf(context->socket, "<|> CLIENT\n");
  return (0);
}


void
comm_get_preferences_errors(context)
  struct context *context;
{
  char buf[512];

  network_gets(context->socket, buf, sizeof(buf));
  network_gets(context->socket, buf, sizeof(buf));
}


/*
 * Retrieves the server rules and store them in
 * a subcategory in the preferences
 */
int
comm_get_rules(context)
  struct context *context;
{
  struct arglist *serv_prefs = arg_get_value(context->prefs, "SERVER_PREFS");
  struct arglist *rules = NULL;
  char *buf = emalloc(32768);
  int finished = 0;

  rules = arg_get_value(serv_prefs, "RULES");
  if(!rules)
  {
    rules = emalloc(sizeof(struct arglist));
    arg_add_value(serv_prefs, "RULES", ARG_ARGLIST, -1, rules);
  }

  network_gets(context->socket, buf, 32768);
  if(!strncmp(buf, "SERVER <|> RULES <|>", 20))
  {
    while(!finished)
    {
      char *rule;
      network_gets(context->socket, buf, 32768);
      if(strstr(buf, "<|> SERVER"))
      {
        finished = 1;
      }
      else
      {
        struct arglist *t = rules;
        int ok = 1;
        int i = 0;

        rule = emalloc(strlen(buf));
        strncpy(rule, buf, strlen(buf) - 1);
        while(t && t->next && ok)
        {
          if(!strcmp(t->value, rule))
            ok = 0;
          t = t->next;
        }
        if(ok)
        {
          char name[10];
          snprintf(name, sizeof(name), "%d", ++i);
          arg_add_value(rules, name, ARG_STRING, strlen(rule), rule);
        }
        else
          efree(&rule);
      }
    }
  }
  efree(&buf);
  return (0);
}


/* Get the md5sums for each plugin from the server.  For each pair of
 * plugin id and md5sum received from the server, this function calls
 * the given callback function with the context, the plugin id, the
 * md5sum, the plugin with the id (NULL if the plugin is not already
 * known) and the data parameter.  The callback should return 0 in case
 * of success, non-zero otherwise.  The md5sum parameter given to the
 * callback is a pointer into a buffer maintained by
 * comm_get_plugins_md5 so if the callback stores it somewhere it should
 * make a copy.
 *
 * comm_get_plugins_md5 returns 0 on success, -1 on error.
 */
static int
comm_get_plugins_md5(struct context *context, char * buf, int bufsz,
    int (callback)(struct context *context, int id, const char * md5sum,
	struct nessus_plugin * plugin, void * data), void * data)
{
  network_printf(context->socket, "CLIENT <|> SEND_PLUGINS_MD5 <|> CLIENT\n");
  network_gets(context->socket, buf, 23);

  if (strncmp(buf, "SERVER <|> PLUGINS_MD5", 22) == 0)
  {
    for(;;)
    {
      network_gets(context->socket, buf, bufsz);
      if(buf[0] == '\0')
      {
	show_error(_("The daemon shut down the communication"));
	break;
      }
      else if(!strncmp(buf, "<|> SERVER", 10))
	break;
      else
      {
	char * rest;
	int id = strtol(buf, &rest, 10);
	if (strncmp(rest, " <|> ", 5) == 0)
	{
	  struct nessus_plugin * plugin = NULL;
	  char *md5sum = rest + 5;

	  /* the md5sum goes on until the end of the line.  Strip the
	   * trailing newline */
	  int md5len = strlen(md5sum);
	  if (md5sum[md5len - 1] == '\n')
	    md5sum[md5len - 1] = '\0';

	  plugin = nessus_plugin_get_by_id(context->plugins, id);
	  if (plugin == NULL)
	    plugin = nessus_plugin_get_by_id(context->scanners, id);
	  if (callback(context, id, md5sum, plugin, data))
	  {
	    show_error(_("Error processing plugin information from the server"));
	    return -1;
	  }
	}
	else
	{
	  show_error(_("Invalid SEND_PLUGINS_MD5 response from server"));
	  return -1;
	}
      }
    }
  }

  return 0;
}


/* Manage a list (well actually an array) of missing or outdated
 * plugins.  The list contains the ids and md5sums of the plugins */

struct missing_plugin {
  int id;
  char * md5sum;
};

struct missing_plugins_list {
  int allocated;
  int length;
  struct missing_plugin * plugins;
};

/* initialize a missing_plugins_list struct */
static void
missing_plugins_list_init(struct missing_plugins_list * missing)
{
  missing->length = missing->allocated = 0;
  missing->plugins = NULL;
}

/* Add an entry to the list of missing/outdated plugins */
static void
missing_plugins_list_add(struct missing_plugins_list * missing,
    int id, const char * md5sum)
{
  if (missing->length == missing->allocated)
  {
    int new_length = missing->allocated + 1000;
    if (missing->plugins != NULL)
      missing->plugins = erealloc(missing->plugins,
	  sizeof(struct missing_plugin) * new_length);
    else
      missing->plugins = emalloc(sizeof(struct missing_plugin) * new_length);
    missing->allocated = new_length;
  }

  missing->plugins[missing->length].id = id;
  missing->plugins[missing->length].md5sum = estrdup(md5sum);
  missing->length += 1;
}


/* free the list of missing plugins.  Does not free the struct
 * itself. */
static void
missing_plugins_list_free(struct missing_plugins_list * missing)
{
  int i;
  for (i = 0; i < missing->length; i++)
    efree(&missing->plugins[i].md5sum);
  efree(&missing->plugins);
  missing_plugins_list_init(missing);
}


/* callback for comm_get_plugins_md5 that checks the md5sum of an
 * existing plugin.
 *
 * If plugin is given, i.e. if it's a known plugin, and the md5sums are
 * equal, the plugin is up to date.  In that case the plugin's
 * is_current flag is set.  Otherwise the flag is not modified (the code
 * practically assumes it's false) and the id and md5sum are added to
 * the missing plugins list which should be passed to this function as
 * the data parameter.
 */
static int
update_existing_plugin(struct context *context, int id, const char * md5sum,
    struct nessus_plugin * plugin, void * data)
{
  if (plugin && strcmp(plugin->md5sum, md5sum) == 0)
  {
    plugin->is_current = 1;
  }
  else
  {
    missing_plugins_list_add((struct missing_plugins_list*)data, id, md5sum);
  }

  return 0;
}


/* Remove outdated plugins from the plugin list.  The return value is a
 * pointer to the first plugin that was not removed or NULL if all
 * plugins have been removed.
 *
 * TODO: the plugins should also be removed from the pluginset.  It
 * should be noted, though, that items are never removed from the
 * pluginset currently, even without the plugin cache.
 */
static struct nessus_plugin*
remove_outdated_plugins(struct nessus_plugin* plugin)
{
  struct nessus_plugin * first = NULL;
  struct nessus_plugin * prev = NULL;

  while (plugin)
  {
    struct nessus_plugin * next = plugin->next;

    if (!plugin->is_current)
    {
      if (prev != NULL)
      {
	prev->next = plugin->next;
      }
      nessus_plugin_free(plugin);
    }
    else
    {
      prev = plugin;
      if (first == NULL)
	first = plugin;
    }

    plugin = next;
  }

  return first;
}


/* Fetch the information for the plugins listed in missing.
 * Return 0 on success, -1 on errors.
 */
static int
fetch_new_plugins(struct context *context, struct missing_plugins_list* missing,
    char * buf, int bufsz)
{
  int i;
  struct nessus_plugin * plugin;

  for (i = 0; i < missing->length; i++)
  {
    network_printf(context->socket,
	"CLIENT <|> PLUGIN_INFO <|> %d <|> CLIENT\n", missing->plugins[i].id);
    network_gets(context->socket, buf, bufsz);

    plugin = parse_plugin(buf);
    if (plugin != NULL)
    {
      nessus_plugin_set_md5sum(plugin, missing->plugins[i].md5sum);
      context_add_plugin(context, plugin);
    }
    else
    {
      /* plugin information could not be parsed.  Looks like a server
       * error */
      show_error(_("Invalid PLUGIN_INFO response from server"));
      return -1;
    }
  }

  return 0;
}


/* Update the plugins in context by comparing them to the individual
 * md5sums from the server.  Missing and updated plugins are fetched
 * from the server, plugins that no longer exist on the server are
 * removed.  If successful, the function returns 0 and a non-zero value
 * otherwise */
static int
update_individual_plugins(struct context *context, char * buf, int bufsz)
{
  struct missing_plugins_list missing;
  int result = 0;

  missing_plugins_list_init(&missing);

  result = comm_get_plugins_md5(context, buf, bufsz, update_existing_plugin,
      &missing);
  if (result)
    goto fail;

  context->plugins = remove_outdated_plugins(context->plugins);
  context->scanners = remove_outdated_plugins(context->scanners);

  result = fetch_new_plugins(context, &missing, buf, bufsz);
    
fail:
  missing_plugins_list_free(&missing);
  return result;
}


/* callback for comm_get_plugins_md5 that simply adds the md5sum to a plugin
 */
static int
add_md5sum_to_plugin(struct context *context, int id, const char * md5sum,
    struct nessus_plugin * plugin, void * data)
{
  if (plugin != NULL)
  {
    nessus_plugin_set_md5sum(plugin, md5sum);
  }
  else
    /* Since this function is used to fetch the md5sums for the plugins
     * immediately after the full list of plugins has been fetched from
     * the server, all plugins should be known, so we should never get
     * here */
    fprintf(stderr, "add_md5sum_to_plugin: Unknown plugin %d\n", id);

  return 0;
}
  

int
comm_get_plugins(context)
  struct context *context;
{
  int result = 0;
  char *buf;
  int bufsz;
  char * server_md5sum = NULL;
  int cache_status = -1;

  /* without the plugins_md5sum we can't reliably update the plugins
   * incrementally, so we have to make sure that all of the plugins are
   * reloaded */
  if (context->plugins_md5sum == NULL)
  {
    context_reset_plugins(context);
  }

  bufsz = 1024 * 1024;
  buf = emalloc(bufsz);
  network_gets(context->socket, buf, bufsz);

  /* Valid data from the server at this can either start with "SERVER
   * <|> PLUGINS_MD5 <|>" or "SERVER <|> PLUGIN_LIST <|>".  In either
   * case it starts with "SERVER <|> PLUGIN".  Anything else is probably
   * a login problem (wrong password, etc.)
   */
  if(strncmp(buf, "SERVER <|> PLUGIN", 17) != 0)
  {
    result = -1;
    goto fail;
  }

  /* if we requested md5sums, we get the md5sum over all the plugins
   * now */
  if(strncmp(buf, "SERVER <|> PLUGINS_MD5 <|> ", 27) == 0)
  {
    server_md5sum = parse_separator(buf + 22);
    if (server_md5sum == NULL)
    {
      show_error(_("Invalid PLUGINS_MD5 information sent from server"));
      result = -1;
      goto fail;
    }

    /* Read and check the cache */
    if (context->plugins_md5sum == NULL)
    {
      cache_status = plugin_cache_read(context);
    }
    else
    {
      /* The cache was already loaded */
      cache_status = 0;
    }
    if (cache_status < 0)
    {
      /* the cache could not be read for some reason.  Most likely an
       * error.  Fetch the full list of plugins  */
      network_printf(context->socket, "CLIENT <|> COMPLETE_LIST <|> CLIENT\n");
      network_gets(context->socket, buf, 27);
    }
    else if (strcmp(context->plugins_md5sum, server_md5sum) != 0)
    {
      /* the cache is outdated so we update the individual plugins.  We
       * set the plugins_md5sum temporarily to a different value because
       * neither the old md5sum nor the new md5sum just read from the
       * server correspond to the plugin information in the context
       * while the update is being performed.
       */
      context_set_plugins_md5sum(context, "");
      if (update_individual_plugins(context, buf, bufsz) < 0)
      {
	show_error(_("Error while updating the cached plugin information"));
	result = -1;
	goto fail;
      }
      context_set_plugins_md5sum(context, server_md5sum);
    }
  }

  /* we may get a complete plugin list if we either did not request the
   * md5sum in the first place or if the cache wasn't current. */
  if (strncmp(buf, "SERVER <|> PLUGIN_LIST <|>", 26) == 0)
  {
    context_reset_plugins(context);
    for(;;)
    {
      comm_update_ui(context, COMM_GET_PLUGINS);
      network_gets(context->socket, buf, bufsz);
      if(buf[0] == '\0')
      {
	show_error(_("The daemon shut down the communication"));
	break;
      }
      else if(!strncmp(buf, "<|> SERVER", 10))
	break;
      else
      {
	struct nessus_plugin *plugin = parse_plugin(buf);

	if ( plugin == NULL )
	{
	  fprintf(stderr, "Could not parse %s\n", buf);
	  continue;
	}

	context_add_plugin(context, plugin);
      }
    }

    if (server_md5sum != NULL)
      comm_get_plugins_md5(context, buf, bufsz, add_md5sum_to_plugin, NULL);
  }

  /* if we requested md5sums, we need to tell the server explicitly that
   * it should continue.  Also, write the cache. */
  if (server_md5sum != NULL)
  {
    context_set_plugins_md5sum(context, server_md5sum);
    network_printf(context->socket, "CLIENT <|> GO ON <|> CLIENT\n");
    context_reset_plugin_tree(context);
    context_force_plugin_prefs_redraw(context);
  }
  
 fail:
  efree(&server_md5sum);
  efree(&buf);
  return result;
}








/*-------------------------------------------------------------------------

			Sessions management
			
---------------------------------------------------------------------------*/

/*
 * Does the server support sessions saving ?
 */
int
comm_server_restores_sessions(context)
  struct context *context;
{
#ifdef ENABLE_SAVE_TESTS
  return context->sessions_saved;
#else
  return 0;
#endif
}

int
comm_server_detached_sessions(context)
  struct context *context;
{
#ifdef ENABLE_SAVE_TESTS
  return context->detached_sessions_saved;
#else
  return 0;
#endif
}

harglst *
comm_get_sessions(struct context * context)
{
  char buff[32768];
  harglst *ret = NULL;

  network_printf(context->socket, "CLIENT <|> SESSIONS_LIST <|> CLIENT\n");
  network_gets(context->socket, buff, sizeof(buff));
  if(!strcmp(buff, "SERVER <|> SESSIONS_LIST\n"))
  {
    ret = harg_create(15000);
    while(!strstr(buff, "<|> SERVER"))
    {
      char *t;

      network_gets(context->socket, buff, sizeof(buff));
      t = strchr(buff, ' ');
      if(t && !strstr(buff, "<|> SERVER"))
      {
	if(buff[strlen(buff) - 1] == '\n')
	  buff[strlen(buff) - 1] = '\0';
	t[0] = 0;
	t++;
	harg_add_string(ret, buff, t);
      }
    }
  }
  return ret;
}


void
comm_delete_session(context, name)
  struct context * context;
  char *name;
{
  network_printf(context->socket, "CLIENT <|> SESSION_DELETE <|> %s <|> CLIENT\n", name);
}

void
comm_restore_session(context, name)
  struct context * context;
  char *name;
{
  network_printf(context->socket, "CLIENT <|> SESSION_RESTORE <|> %s <|> CLIENT\n", name);
}

void
comm_stop_detached_session(context ,name)
  struct context * context;
  char *name;
{
  network_printf(context->socket, "CLIENT <|> STOP_DETACHED <|> %s <|> CLIENT\n", name);
}

harglst *
comm_get_detached_sessions(struct context * context)
{
  char buff[32768];
  harglst *ret = NULL;

  network_printf(context->socket, "CLIENT <|> DETACHED_SESSIONS_LIST <|> CLIENT\n");
  network_gets(context->socket, buff, sizeof(buff));
  if(!strcmp(buff, "SERVER <|> DETACHED_SESSIONS_LIST\n"))
  {
    ret = harg_create(15000);
    while(!strstr(buff, "<|> SERVER"))
    {
      char *t;

      network_gets(context->socket, buff, sizeof(buff));
      t = strchr(buff, ' ');
      if(t && !strstr(buff, "<|> SERVER"))
      {
	if(buff[strlen(buff) - 1] == '\n')
	  buff[strlen(buff) - 1] = '\0';
	t[0] = 0;
	t++;
	harg_add_string(ret, buff, t);
      }
    }
  }
  return ret;
}





int
comm_get_dependencies(context)
  struct context *context;
{
  char buff[32768];

  buff[0] = '\0';
  network_gets(context->socket, buff, sizeof(buff) - 1);
  if(context->dependencies)
    arg_free_all(context->dependencies);
  context->dependencies = emalloc(sizeof(struct arglist));
  if(!strcmp(buff, "SERVER <|> PLUGINS_DEPENDENCIES\n"))
  {
    network_gets(context->socket, buff, sizeof(buff) - 1);
    while(strcmp(buff, "<|> SERVER\n"))
    {
      struct arglist *deps;
      char *name;
      char *t = strstr(buff, " <|> ");
      char *s;

      comm_update_ui(context, COMM_GET_DEPENDENCIES);
      if(t)
      {
	s = t + 5;
	t[0] = '\0';
	name = buff;
	deps = emalloc(sizeof(struct arglist));
	while(s)
	{
	  t = strstr(s, " <|> ");
	  if(t)
	  {
	    t[0] = '\0';
	    arg_add_value(deps, s, ARG_INT, (sizeof(int)), (void *)1);
	    s = t + 5;
	  }
	  else
	    s = NULL;
	}
	arg_add_value(context->dependencies, name, ARG_ARGLIST, -1, deps);
      }
      network_gets(context->socket, buff, sizeof(buff) - 1);
    }
  }
  /* XXX: this resetting should be moved to another place
   * once there is a separate module for handling the progress bar. */
  comm_update_ui(context, 0); /* reset progress bar */
  return 0;
}
