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

/** @file
 * OpenVAS Communication Manager, handles client/server communication on a
 * rather high level
 */

#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 "openvas_certificates.h"
#include "openvas_ssh_login.h"
#include "plugin_cache.h"

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


/** Currently active time-consuming task: getting plugins. */
#define COMM_GET_PLUGINS 1
/** Currently active time-consuming task: getting dependencies. */
#define COMM_GET_DEPENDENCIES 2

int comm_send_file(struct context *, char *);

/**
 * @brief 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).
 * 
 * @param current COMM_GET_PLUGINS or COMM_GET_DEPENDENCIES.
 */
static void
comm_update_ui (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 */
}

/**
 * @brief Parses a plugin description message, and returns an arglist with the
 * plugin in it.
 * 
 * @param buf The description- string (usually from an OTP plugin*- element).
 * 
 * @return Parsed plugin as arglist or NULL if error(s) occured.
 */
static struct nessus_plugin *
parse_plugin (char * buf)
{
  char *str = NULL;
  char *t;
  size_t offset;

  char * oid  = NULL;
  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;
  char * sign_key_ids;
  char * script_tags;

  gboolean failed = FALSE;
  char * space = strstr(buf, " ");
  if(space != NULL)
  {
    oid = emalloc((space - buf) + 1);
    snprintf(oid, (space - buf) + 1, "%s", buf);
  }
  else
    return NULL;

  /* Parse all plugin fields. Fail, free memory and return NULL in case of error */
  offset = strlen(oid);
  str = parse_separator(buf + offset);
  if(!str) failed = TRUE;
  else name = str;

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else category = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else copyright = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else
    {
      t = str;
      while((t = strchr(t, ';')))
        t[0] = '\n';
      description = str;
    }
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else summary = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else family = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else version = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else cve = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else bid = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else xref = str;
  }

  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else sign_key_ids = str;
  }
  
  if(failed == FALSE)
  {
    offset += strlen(str) + 5;
    str = parse_separator(buf + offset);
    if(!str) failed = TRUE;
    else script_tags = str;
  }

  if(failed == FALSE)
  {
    return nessus_plugin_new(oid, name, category, copyright, description,
                             summary, family, version, cve, bid, xref, 
                             sign_key_ids, script_tags);
  }

  // else: parsing failed
  if(oid) efree(&oid);
  if(name) efree(&name);
  if(category) efree(&category);
  if(copyright) efree(&copyright);
  if(description) efree(&description);
  if(summary) efree(&summary);
  if(family) efree(&family);
  if(version) efree(&version);
  if(cve) efree(&cve);
  if(bid) efree(&bid);
  if(xref) efree(&xref);

  return NULL;
}


/**
 * @brief 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
 * 
 * @param soc A socket connected to the remote server.
 * @param proto_name The protocol with which the client intends to communicate.
 * 
 * @return 0 if the remote server is using proto_name and -1 if it's not.
 */
int
comm_init (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);
}


/**
 * @brief Parse a preference and add it to the context.
 * 
 * The preference may 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.
 * 
 * @return Always returns 0.
 */
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))
  {
    // It is a server preference or info
#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;
	}
        // FIXME: It is unclear why this work(ed?), as in preferences.c
        //  (prefs_buffer_parse) the type of "yes/no" preferences is set to(kb_0)
        //  ARG_INT. That means we cannot ensure the type of a
        //  server-preferences. A rather dirty workaround like in preferences.c
        //  (preference_yes_or_one) has to be used to query these preferences.
        //
        // Furthermore no priorities are documented 
        //  (usersettings > serversettings ?) and some preferences are even
        //  ignored by the server,if set by the client (for good reasons though).
        //  Separating changeable ("client- side") server settings and fixed 
        //  ("server- side") server settings might be an approach to deal with
        //  that problem. It might also be varorable to replace the internal
        //  representation (from arglists to something else).
        
        // FIXME: If this shall be a test for existance of a key/value in argl,
        //        replace it by something that does it more obvious.
        /* Don't add the value if set already */
	if (arg_get_type(serv_prefs, pref) < 0)
          {
	    arg_add_value (serv_prefs, pref, ARG_STRING, strlen(v), v);
          }
        // nasl_no_signature_check needs special treatment (non-settable preference)
        else if (arg_get_type (serv_prefs, pref) == ARG_INT 
                 && !strcmp(pref, "nasl_no_signature_check"))
          {
            // Set the int value instead
            int val = -1;
            if(!strcmp(v, "yes"))
              val = 1;
            if(!strcmp(v, "no"))
              val = 0;
            arg_set_value (serv_prefs, pref, sizeof(val), GINT_TO_POINTER(val));
          }
      }
  }
  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 = GPOINTER_TO_SIZE(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;
}


/**
 * @brief Retrieves the server preferences and attempts parsing, calling
 * @brief comm_parse_preference.
 * 
 * We must make a difference between the prefs of the 
 * server itself and the prefs of the plugins!
 * Server prefs are stored in the current context in an arglist under the key
 * SERVER_PREFS.
 * 
 * @param context The context into which the preferences should be fed (a 
 *                SERVER_PREFS key will be added to it).
 * 
 * @return Always returns 0.
 * 
 * @see comm_parse_preference
 */
int
comm_get_preferences (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') /* RATS: ignore, network_gets ensures nul termination (together with recv_line) */
	buf[strlen(buf) - 1] = 0; /* RATS: ignore, network_gets ensures nul termination (together with recv_line) */
      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);
}


/**
 * @brief Sends entries from preference arglist to server.
 * 
 * Collects file preferences on the way and registers them in upload parameter
 * (to be send later on).
 *
 * @param      context Context that holds socket to use.
 * @param      pref    Preference arglist to send.
 * @param[out] upload  GSList double pointer where to collect filenames.
 * @param      pprefs  Indicator whether plugin or server preferences are sent
 *                    (ignored).
 * 
 * @return 0 on success, on error -1.
 */
static int
cli_send_prefs_arglist(struct context *context, struct arglist *pref, 
                       GSList** upload, int pprefs)
{
  if(!pref)
    return -1;

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

      // FIXME: Analogous to gui- preference sending, send the stuff in the cli
      if(strstr(pref->name, "[" PREF_SSH_CREDENTIALS "]:"))
      {
        openvas_ssh_login* loginfo = NULL;
        if(Global->sshkeys)
          loginfo = g_hash_table_lookup(Global->sshkeys, pref->value);
        // Add the files
        if(loginfo != NULL)
          {
            (*upload) = g_slist_prepend( (*upload), loginfo->public_key_path);
            (*upload) = g_slist_prepend( (*upload), loginfo->private_key_path);
            network_printf(context->socket, "%s <|> %s\n", pref->name, 
                           openvas_ssh_login_prefstring(loginfo));
          } // if no info about ssh key pair found, to not send anything.
      }
      else
        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;
}

/**
 * @brief Send certain ntp options.
 * 
 * This piece of code had been termed a "workaround to use new features while
 * keeping backward compatibility". It seems essential for a behaviour of the
 * server as expected, though.
 * 
 * @param socket Socket to use.
 */
static void
send_ntp_opts (int socket)
{
  network_printf (socket, "ntp_opt_show_end <|> yes\n");
  network_printf (socket, "ntp_keep_communication_alive <|> yes\n");
  network_printf (socket, "ntp_short_status <|> yes\n");
  network_printf (socket, "ntp_client_accepts_notes <|> yes\n");
}

/**
 * @brief Sends server and plugin preferences from the cli.
 * 
 * @param context Context with plugin and server preferences hooked into.
 * 
 * @return Always 0.
 */
static int
cli_comm_send_preferences (struct context* context)
{
  struct arglist *preferences = context->prefs;
  GSList *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");
  send_ntp_opts (context->socket);
  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");
  while(files_to_send != NULL)
  {
    comm_send_file(context, files_to_send->data);
    files_to_send = g_slist_next(files_to_send);
  }
  return (0);
}

/**
 * @brief Finds paths to key files of an ssh-account and adds them to a list.
 * 
 * Used as GHFunc in a g_hash_table_foreach.
 * 
 * @param hostname  Hostname (key of a map hostname -> login_name).
 * @param loginname User-defined name for a login (value of a map 
 *                  hostname -> login_name).
 * @param map_and_filelist GPtrArray with two pointers, first one to a map
 *                         login_name -> (login structs), second to a list.
 *                         (workaround, because g_hash_table_iterator is not in
 *                         GLib <= 2.12).
 */
static void
send_ssh_login_keys (char* hostname, char* loginname, GPtrArray* map_and_filelist)
{
  // Unpeel map and file list
  GHashTable* map_namelogin = g_ptr_array_index (map_and_filelist, 0);
  GSList* files_to_send     = g_ptr_array_index (map_and_filelist, 1);
  
  // Missing data or no login selected for this host
  if (map_namelogin == NULL || files_to_send == NULL
      || !strcmp(loginname, NO_SSH_LOGIN_SELECTED))
    return;
  
  // Look up the loginname in the map
  openvas_ssh_login* osl = g_hash_table_lookup (map_namelogin, loginname);
  
  if (osl == NULL)
    return;

  // Get the paths, add them to the list of files to send.
  // We can only add the items like this since we know that the list exists already!
  files_to_send = g_slist_append (files_to_send, g_strdup(osl->public_key_path));
  files_to_send = g_slist_append (files_to_send, g_strdup(osl->private_key_path));
}

/**
 * @brief Addds the files related to user-selected ssh_logins to a list if these
 * @brief files indeed exist.
 * 
 * @param files_to_send Start of the list of files to send.
 * 
 * @return New start of the list.
 */
GSList*
send_ssh_credential_files (GSList* files_to_send)
{
  GSList* return_file_list = files_to_send;

  char* loginsfile = g_build_filename (prefs_get_nessushome (), ".openvas",
                                       ".ssh", ".logins", NULL);

  char* host_loginsfile = g_build_filename (Context->dir, ".host_sshlogins", NULL);

  // Add files to list of files to send, if exists
  if (check_exists(loginsfile) == 1 && check_exists(host_loginsfile) == 1)
    {
      // Transfer all the key files of  keys that were selected.
      GHashTable* logins = openvas_ssh_login_file_read (loginsfile, TRUE); 

      // Attention, order of files matters (otherwise the server can not look up
      // the keys correctly)
      return_file_list = g_slist_prepend (return_file_list, host_loginsfile);
      return_file_list = g_slist_prepend (return_file_list, loginsfile);

      // For each host that is listed, lookup the login information and
      // add key-file-paths to the list of files to send.
      // GLib < 2.12 does not provide a iterator over keys. Work around it.
      GPtrArray* map_and_filelist = g_ptr_array_sized_new (2);
      g_ptr_array_add (map_and_filelist, logins);
      g_ptr_array_add (map_and_filelist, return_file_list);
      g_hash_table_foreach (Context->map_target_sshlogin, (GHFunc) send_ssh_login_keys,
                            map_and_filelist);

      // Clean up
      g_ptr_array_free (map_and_filelist, TRUE);
      g_hash_table_destroy (logins);
    }
  else
    {
      g_free (host_loginsfile);
      g_free (loginsfile);
    }

  return return_file_list;
}

/**
 * @brief Sends server and plugin preferences from the gui.
 * 
 * @param context Context with plugin and server preferences hooked into.
 * 
 * @return Always 0.
 */
static int
gui_comm_send_preferences (struct context* context)
{
  struct arglist *preferences = context->prefs;
  GSList* 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");
  send_ntp_opts (context->socket);
  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/scanners 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))
	{
	  files_to_send = g_slist_prepend(files_to_send, value);
	}

        if(!strcmp(type, PREF_SSH_CREDENTIALS))
        {
          files_to_send = send_ssh_credential_files (files_to_send);
        }

	plugin_prefs = plugin_prefs->next;
      }
      plugs = plugs->next;
    }
  }
  network_printf(context->socket, "<|> CLIENT\n");
  while(files_to_send != NULL )
  {
    comm_send_file(context, files_to_send->data);
    files_to_send = g_slist_next(files_to_send);
  }
  return (0);
}

/**
 * Gateways to gui_comm_send_preferences or cli_comm_send_preferences, depending
 * on quiet_mode.
 * 
 * @return Return value of either cli_comm_send_preferences or
 *         gui_comm_send_preferences.
 */
int
comm_send_preferences (struct context *context)
{
  if(F_quiet_mode)
    return cli_comm_send_preferences(context);
  else
    return gui_comm_send_preferences(context);
}

/**
 * @brief Send a file to the server, using OTP ATTACHED_FILE command.
 * 
 * @param context The context to use.
 * @param fname Path to file to send.
 * 
 * @return 0 on success or empty fname argument, -1 on error.
 */
int
comm_send_file (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 (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);
}


/**
 * @brief Retrieves the server rules and store them in a subcategory in the
 * @brief preferences.
 */
int
comm_get_rules (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);
}


/**
 * @brief 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.
 *
 * @return 0 on success, -1 on error.
 */
static int
comm_get_plugins_md5(struct context *context, char * buf, int bufsz,
    int (callback)(struct context *context, const char * oid,
                   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 * space = strstr(buf, " ");
        if(space != NULL)
        {
          char * oid = emalloc((space - buf) + 1);
          snprintf(oid, (space - buf) + 1, "%s", buf);
          char * md5sum = buf + strlen(oid) + 5;

	  struct nessus_plugin * plugin = NULL;

	  /* 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_oid(context->plugins, oid);
	  if (plugin == NULL)
	    plugin = nessus_plugin_get_by_oid(context->scanners, oid);
	  if (callback(context, oid, 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 {
  char * oid;
  char * md5sum;
};

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

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

/**
 * @brief Add an entry to the list of missing/outdated plugins.
 */
static void
missing_plugins_list_add (struct missing_plugins_list * missing,
                          const char * oid, 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].oid = estrdup(oid);
  missing->plugins[missing->length].md5sum = estrdup(md5sum);
  missing->length += 1;
}


/**
 * @brief Free the list (and only 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);
}


/**
 * @brief Callback for comm_get_plugins_md5 that checks the md5sum of an
 * @brief 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,
                        const char * oid, 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, oid, md5sum);
    }

  return 0;
}


/**
 * @brief Remove outdated plugins from the plugin list.
 * 
 * @return Pointer to the first nvt that was removed or NULL if all nvts were
 *         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;
}


/**
 * @brief 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;
  int n_new_plugins = 0;
  struct nessus_plugin * plugin;

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

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

  /* Show the user the number of new plugins, if any. Also indicate whether 
   * they have been enabled (auto_enable_new_plugins option in context prefs).
   * For easier translation 4 strings (auto_enable*number) */
  if(n_new_plugins > 0)
    {
    int auto_enabled = prefs_get_int(context, "auto_enable_new_plugins");
    if(n_new_plugins == 1)
      {
      if(auto_enabled)
        show_info(_("Found and enabled one new plugin."));
      else
        show_info(_("Found and disabled one new plugin."));
      }
    else /* more than one, use linguistic plural*/
      {
      if(auto_enabled)
        show_info(_("Found and enabled %d new plugins."), n_new_plugins);
      else
        show_info(_("Found and disabled %d new plugins."), n_new_plugins);
      }
    }

  return 0;
}


/**
 * @brief Update the plugins in context by comparing them to the individual
 * @brief md5sums from the server.
 *
 * Missing and updated plugins are fetched
 * from the server, plugins that no longer exist on the server are
 * removed.
 * 
 * @return If successful, returns 0. 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;
}


/**
 * @brief Callback for comm_get_plugins_md5 that simply adds the md5sum to a
 * @brief plugin.
 * 
 * @return Always 0.
 */
static int
add_md5sum_to_plugin (struct context *context, const char * oid,
                      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 %s\n", oid);

  return 0;
}

int
comm_get_plugins (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) /* "error" */
      {
        /* The cache could not be read for some reason.  Most likely an
         * error or no cache file exists. 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. */
  int n_new_plugins = 0;
  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;
	}

        /* Count the number of new plugins */
	if(context_add_plugin(context, plugin))
          ++n_new_plugins;
      }
    }

    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);
    }
  
  /* Show the user the number of new plugins, if any. Also indicate whether 
   * they have been enabled (auto_enable_new_plugins option in context prefs).
   * For easier translation 4 strings (auto_enable*number) */
  if(n_new_plugins > 0)
    {
      int auto_enabled = prefs_get_int(context, "auto_enable_new_plugins");
      if(n_new_plugins == 1)
        {
        if(auto_enabled)
          show_info(_("Found and enabled one new plugin."));
        else
          show_info(_("Found and disabled one new plugin."));
        }
      else /* more than one, use linguistic plural*/
        {
        if(auto_enabled)
          show_info(_("Found and enabled %d new plugins."), n_new_plugins);
        else
          show_info(_("Found and disabled %d new plugins."), n_new_plugins);
        }
    }

 fail:
  efree(&server_md5sum);
  efree(&buf);
  return result;
}


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

/**
 * @brief Does the server support sessions saving?
 */
int
comm_server_restores_sessions (struct context *context)
{
#ifdef ENABLE_SAVE_TESTS
  return context->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_restore_session (struct context * context, char *name)
{
  network_printf(context->socket, "CLIENT <|> SESSION_RESTORE <|> %s <|> CLIENT\n", name);
}

/*-------------------------------------------------------------------------
               Requesting Dependencies and Certificates
---------------------------------------------------------------------------*/

int
comm_get_dependencies (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;
}


/**
 * @brief Parses a certificate command by server and indexes the certificate 
 * 
 * Parse a certificate command sent by the server and adds the respective
 * pointers to the signer_fp_certificates hashtable of the context.
 * Will return 0 on success, 1 when buffer is "<|> SERVER\n", -1 on errors.
 * Does replace if a certificate with that key is already indexed (and 
 * releases the old one)!
 * 
 * @param buffer The OTP "certificate" element sent by the server.
 * @param context The local context to add the certificate information to.
 * 
 * @return 0 on success, 1 on the servers announcement of the end of certificate 
 *         list, -1 on error.
 */
int
comm_parse_certificate (char* buffer, struct context* context)
{
  char* sep;
  char* name;
  char* trust_level;
  char* nbytes;
  char* pubkey;
  char* fpr;
  char* fpr_short = NULL;

  if( strcmp(buffer, "<|> SERVER\n") == 0 )
    return 1;

  // Initialize the hashtable if not yet done.
  if(context->signer_fp_certificates == NULL)
    context->signer_fp_certificates =  g_hash_table_new_full(g_str_hash,
                   g_str_equal, NULL, (GDestroyNotify) openvas_certificate_free);

  sep = strstr(buffer, "<|>");
  if (sep == NULL)
    return -1;

  // Read in tokens
  fpr = strtok(buffer, " <|>");
  name = strtok(NULL, "<|>");
  trust_level = strtok(NULL, " <|>");
  gboolean trusted = ( strcmp(trust_level, "trusted") == 0 )? TRUE : FALSE;
  nbytes = strtok(NULL, "<|>");
  long pkey_length = atol(nbytes);

  if(pkey_length < 1)
    return -1;
  pubkey = strtok(NULL,"<|>");

  // Not enough tokens or incomplete public key
  if(fpr == NULL || name == NULL || trust_level == NULL || pkey_length < 1
                  || pubkey == NULL
     || strlen(pubkey)-2 != pkey_length )
    {
      return -1;
    }

  // Replace semicolons by newlines
  char* pos = pubkey;
  while (pos[0] != '\0')
  {
    if (pos[0] == ';') pos[0] = '\n';
    pos++;
  }

  // Make the "fingerprint"- key 16 chars long (it seems it sometimes is,
  // sometimes isnt)
  if (strlen (fpr) > 16)
    {
      fpr_short = fpr + (strlen(fpr)-16) ;
    }

  // Public key field might contain a space before the actual data, skip it.
  while (pubkey[0] == ' ')
    pubkey++;

  // Create certificate
  openvas_certificate* cert = openvas_certificate_new ( estrdup(fpr),
                                                        estrdup(name),
                                                        trusted,
                                                        estrdup(pubkey));

  // Index certificate
  g_hash_table_insert(context->signer_fp_certificates, estrdup(fpr_short), cert);

  return 0;
}


/**
 * @brief Request certificates used for NVTs signatures.
 *
 * Certificates are then stored in the contexts hashtable signer_fp_certificates
 * where a fingerprint points to a certificate structure.
 *
 * @param context The Context to use (e.g. a specific scope).
 *
 * @return 0 on success; != 0 on error.
 */
int
comm_get_certificates (struct context* context)
{
  char buffer[32768];
  int response_length = 25; /* expected response: "SERVER <|> CERTIFICATES\n" */
  network_printf(context->socket, "CLIENT <|> CERTIFICATES <|> CLIENT\n");
  
  buffer[0] = '\0';
  network_gets(context->socket, buffer, response_length);

  // If certificate list starts
  if( strcmp(buffer, "SERVER <|> CERTIFICATES\n") == 0)
  {
   int parsing_status = 0;
   do
     {
     buffer[0] = '\0';
     network_gets(context->socket, buffer, 16348);
     parsing_status = comm_parse_certificate(buffer, context);
     if(parsing_status == -1)
       show_error(_("Could not parse certificate: %s"), buffer);
     }
   while ( parsing_status == 0);
  }
  // else respond is invalid
  else
  {
    show_error(_("Invalid response from server to certificate request: %s"),
               buffer);
    return -1;
  }

  // Success
  return 0;
}
