/* OpenVAS Manager
 * $Id$
 * Description: Manager Manage library: SQL backend.
 *
 * Authors:
 * Matthew Mundell <matthew.mundell@greenbone.net>
 * Timo Pollmeier <timo.pollmeier@greenbone.net>
 *
 * Copyright:
 * Copyright (C) 2009-2013 Greenbone Networks GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file  manage_sql.c
 * @brief The OpenVAS Manager management library (SQLite implementation).
 *
 * This file defines the SQLite specific portions of the OpenVAS manager
 * management library.
 */

#define _GNU_SOURCE

#include "manage_sql.h"
#include "manage_acl.h"
#include "lsc_user.h"
#include "sql.h"
#include "scanner.h"
#include "tracef.h"

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <gcrypt.h>
#include <glib/gstdio.h>
#include <locale.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <grp.h>

#include <openvas/base/openvas_string.h>
#include <openvas/base/openvas_file.h>
#include <openvas/base/openvas_hosts.h>
#include <openvas/base/openvas_networking.h>
#include <openvas/misc/openvas_auth.h>
#include <openvas/misc/openvas_logging.h>
#include <openvas/misc/openvas_uuid.h>
#include <openvas/base/pwpolicy.h>
#include <openvas/omp/xml.h>

#ifdef S_SPLINT_S
#include "splint.h"
#endif


/* Headers from backend specific manage_xxx.c file. */

int
manage_create_sql_functions ();

void
create_tables ();

void
manage_attach_databases ();

int
manage_cert_loaded ();

int
manage_scap_loaded ();


/* Headers for symbols defined in manage.c which are private to libmanage. */

/**
 * @brief Flag to force authentication to succeed.  For scheduled tasks.
 */
int authenticate_allow_all;

const char *threat_message_type (const char *);

const char *message_type_threat (const char *);

int delete_reports (task_t);

int delete_slave_task (slave_t, const char *);

int
stop_task_internal (task_t);

int
validate_username (const gchar *);


/* Port range headers. */

void
make_port_ranges_iana_tcp_2012 (port_list_t);

void
make_port_ranges_iana_tcp_udp_2012 (port_list_t);

void
make_port_ranges_all_tcp_nmap_5_51_top_100 (port_list_t);

void
make_port_ranges_all_tcp_nmap_5_51_top_1000 (port_list_t);

void
make_port_ranges_nmap_5_51_top_2000_top_100 (port_list_t);

void
make_config_discovery (char *const, const char * const);

void
make_config_host_discovery (char *const, const char * const);

void
make_config_system_discovery (char *const, const char * const);

void
check_config_host_discovery (const char *);


/* Static headers. */

static void
nvt_selector_add (const char*, const char*, const char*, int);

static int
nvt_selector_families_growing (const char*);

static int
nvt_selector_nvts_growing_2 (const char*, int);

static void
nvt_selector_remove_selector (const char*, const char*, int);

static void
update_config_caches (config_t);

static void
update_all_config_caches ();

static gchar*
select_config_nvts (config_t, const char*, int, const char*);

int
family_count ();

static int
report_counts_cache_exists (report_t, int);

static void report_severity_data (report_t, int, const char *, const char *,
                                  const char *, const char *, int, int,
                                  severity_data_t*, severity_data_t*);

static int cache_report_counts (report_t, int, severity_data_t*, int);

static char*
task_owner_uuid (task_t);

static int
insert_nvt_selectors (const char *, const array_t*);

static int
validate_param_value (report_format_t, report_format_param_t param, const char *,
                      const char *);

int
delete_task_lock (task_t, int);

gchar*
clean_hosts (const char *, int*);

int
create_port_list_unique (const char *, const char *, const char *,
                         port_list_t *);

int
valid_type (const char*);

static gboolean
find_user_by_name (const char *, user_t *user);

gboolean
find_group (const char*, group_t*);

gboolean
find_role (const char *, group_t *);

gboolean
find_role_with_permission (const char *, role_t *, const char *);

static gboolean
find_user_by_name (const char *, user_t *);

int
user_ensure_in_db (const gchar *, const gchar *);

static int
report_format_verify (report_format_t);

void
cleanup_prognosis_iterator ();

int
set_password (const gchar *, const gchar *, const gchar *, gchar **);

void
permissions_set_locations (const char *, resource_t, resource_t, int);

void
permissions_set_orphans (const char *, resource_t, int);

void
permissions_set_subjects (const char *, resource_t, resource_t, int);

static resource_t
permission_resource (permission_t);

resource_t
permission_subject (permission_t);

char *
permission_subject_type (permission_t);

void
tags_set_locations (const char *, resource_t, resource_t, int);

void
tags_set_orphans (const char *, resource_t, int);

int
role_is_predefined (role_t);

int
role_is_predefined_id (const char *);


/* Variables. */

/**
 * @brief Function to mark progress.
 */
void (*progress) () = NULL;

/**
 * @brief Max number of hosts per target.
 */
static int max_hosts = MANAGE_MAX_HOSTS;

/**
 * @brief Memory cache of NVT information from the database.
 */
nvtis_t* nvti_cache = NULL;

/**
 * @brief Name of the database file.
 */
gchar* task_db_name = NULL;

/**
 * @brief Whether a transaction has been opened and not committed yet.
 */
static gboolean in_transaction;

/**
 * @brief Time of reception of the currently processed message.
 */
static struct timeval last_msg;


/* OMP commands. */

/**
 * @brief The OMP command list.
 */
command_t omp_commands[]
 = {{"AUTHENTICATE", "Authenticate with the manager." },
    {"COMMANDS",     "Run a list of commands."},
    {"CREATE_AGENT", "Create an agent."},
    {"CREATE_ALERT", "Create an alert."},
    {"CREATE_CONFIG", "Create a config."},
    {"CREATE_FILTER", "Create a filter."},
    {"CREATE_GROUP", "Create a group."},
    {"CREATE_LSC_CREDENTIAL", "Create a local security check credential."},
    {"CREATE_NOTE", "Create a note."},
    {"CREATE_OVERRIDE", "Create an override."},
    {"CREATE_PERMISSION", "Create a permission."},
    {"CREATE_PORT_LIST", "Create a port list."},
    {"CREATE_PORT_RANGE", "Create a port range in a port list."},
    {"CREATE_REPORT", "Create a report."},
    {"CREATE_REPORT_FORMAT", "Create a report format."},
    {"CREATE_ROLE", "Create a role."},
    {"CREATE_SCANNER", "Create a scanner."},
    {"CREATE_SCHEDULE", "Create a schedule."},
    {"CREATE_SLAVE", "Create a slave."},
    {"CREATE_TAG", "Create a tag."},
    {"CREATE_TARGET", "Create a target."},
    {"CREATE_TASK", "Create a task."},
    {"CREATE_USER", "Create a new user."},
    {"DELETE_AGENT", "Delete an agent."},
    {"DELETE_ALERT", "Delete an alert."},
    {"DELETE_CONFIG", "Delete a config."},
    {"DELETE_FILTER", "Delete a filter."},
    {"DELETE_GROUP", "Delete a group."},
    {"DELETE_LSC_CREDENTIAL", "Delete a local security check credential."},
    {"DELETE_NOTE", "Delete a note."},
    {"DELETE_OVERRIDE", "Delete an override."},
    {"DELETE_PERMISSION", "Delete a permission."},
    {"DELETE_PORT_LIST", "Delete a port list."},
    {"DELETE_PORT_RANGE", "Delete a port range."},
    {"DELETE_REPORT", "Delete a report."},
    {"DELETE_REPORT_FORMAT", "Delete a report format."},
    {"DELETE_ROLE", "Delete a role."},
    {"DELETE_SCANNER", "Delete a scanner."},
    {"DELETE_SCHEDULE", "Delete a schedule."},
    {"DELETE_SLAVE", "Delete a slave."},
    {"DELETE_TAG", "Delete a tag."},
    {"DELETE_TARGET", "Delete a target."},
    {"DELETE_TASK", "Delete a task."},
    {"DELETE_USER", "Delete an existing user."},
    {"DESCRIBE_AUTH", "Get details about the used authentication methods."},
    {"DESCRIBE_CERT", "Get details of the CERT feed this Manager uses."},
    {"DESCRIBE_FEED", "Get details of the NVT feed this Manager uses."},
    {"DESCRIBE_SCAP", "Get details of the SCAP feed this Manager uses."},
    {"EMPTY_TRASHCAN", "Empty the trashcan."},
    {"GET_AGENTS", "Get all agents."},
    {"GET_AGGREGATES", "Get aggregates of resources."},
    {"GET_ALERTS", "Get all alerts."},
    {"GET_CONFIGS", "Get all configs."},
    {"GET_FILTERS", "Get all filters."},
    {"GET_GROUPS", "Get all groups."},
    {"GET_INFO", "Get raw information for a given item."},
    {"GET_LSC_CREDENTIALS", "Get all local security check credentials."},
    {"GET_NOTES", "Get all notes."},
    {"GET_NVTS", "Get one or all available NVTs."},
    {"GET_NVT_FAMILIES", "Get a list of all NVT families."},
    {"GET_NVT_FEED_VERSION", "Get NVT feed version."},
    {"GET_OVERRIDES", "Get all overrides."},
    {"GET_PERMISSIONS", "Get all permissions."},
    {"GET_PORT_LISTS", "Get all port lists."},
    {"GET_PREFERENCES", "Get preferences for all available NVTs."},
    {"GET_REPORTS", "Get all reports."},
    {"GET_REPORT_FORMATS", "Get all report formats."},
    {"GET_RESULTS", "Get results."},
    {"GET_ROLES", "Get all roles."},
    {"GET_SCANNERS", "Get all scanners."},
    {"GET_SCHEDULES", "Get all schedules."},
    {"GET_SETTINGS", "Get all settings."},
    {"GET_SLAVES", "Get all slaves."},
    {"GET_SYSTEM_REPORTS", "Get all system reports."},
    {"GET_TAGS", "Get all tags."},
    {"GET_TARGETS", "Get all targets."},
    {"GET_TASKS", "Get all tasks."},
    {"GET_USERS", "Get all users."},
    {"GET_VERSION", "Get the OpenVAS Manager Protocol version."},
    {"HELP", "Get this help text."},
    {"MODIFY_AGENT", "Modify an existing agent."},
    {"MODIFY_ALERT", "Modify an existing alert."},
    {"MODIFY_AUTH", "Modify the authentication methods."},
    {"MODIFY_CONFIG", "Update an existing config."},
    {"MODIFY_FILTER", "Modify an existing filter."},
    {"MODIFY_GROUP", "Modify an existing group."},
    {"MODIFY_LSC_CREDENTIAL", "Modify an existing LSC credential."},
    {"MODIFY_NOTE", "Modify an existing note."},
    {"MODIFY_OVERRIDE", "Modify an existing override."},
    {"MODIFY_PERMISSION", "Modify an existing permission."},
    {"MODIFY_PORT_LIST", "Modify an existing port list."},
    {"MODIFY_REPORT", "Modify an existing report."},
    {"MODIFY_REPORT_FORMAT", "Modify an existing report format."},
    {"MODIFY_ROLE", "Modify an existing role."},
    {"MODIFY_SCANNER", "Modify an existing scanner."},
    {"MODIFY_SCHEDULE", "Modify an existing schedule."},
    {"MODIFY_SETTING", "Modify an existing setting."},
    {"MODIFY_SLAVE", "Modify an existing slave."},
    {"MODIFY_TAG", "Modify an existing tag."},
    {"MODIFY_TARGET", "Modify an existing target."},
    {"MODIFY_TASK", "Update an existing task."},
    {"MODIFY_USER", "Modify a user."},
    {"RESTORE", "Restore a resource."},
    {"RESUME_TASK", "Resume a stopped task."},
    {"RUN_WIZARD", "Run a wizard."},
    {"START_TASK", "Manually start an existing task."},
    {"STOP_TASK", "Stop a running task."},
    {"SYNC_CERT", "Synchronize with a CERT feed."},
    {"SYNC_FEED", "Synchronize with an NVT feed."},
    {"SYNC_SCAP", "Synchronize with a SCAP feed."},
    {"TEST_ALERT", "Run an alert."},
    {"VERIFY_AGENT", "Verify an agent."},
    {"VERIFY_REPORT_FORMAT", "Verify a report format."},
    {"VERIFY_SCANNER", "Verify a scanner."},
    {NULL, NULL}};

/**
 * @brief Check whether a command name is valid.
 *
 * @param[in]  name  Command name.
 *
 * @return 1 yes, 0 no.
 */
int
valid_omp_command (const char* name)
{
  command_t *command;
  command = omp_commands;
  while (command[0].name)
    if (strcasecmp (command[0].name, name) == 0)
      return 1;
    else
      command++;
  return 0;
}

/**
 * @brief Get the type associated with an OMP command.
 *
 * @param[in]  name  Command name.
 *
 * @return Freshly allocated type name if any, else NULL.
 */
gchar *
omp_command_type (const char* name)
{
  const char *under;
  under = strchr (name, '_');
  if (under && (strlen (under) > 1))
    {
      gchar *command;
      under++;
      command = g_strdup (under);
      if (command[strlen (command) - 1] == 's')
        command[strlen (command) - 1] = '\0';
      if (valid_type (command))
        return command;
      g_free (command);
    }
  return NULL;
}

/**
 * @brief Check whether an OMP command takes a resource.
 *
 * MODIFY_TARGET, for example, takes a target.
 *
 * @param[in]  name  Command name.
 *
 * @return 1 if takes resource, else 0.
 */
static int
omp_command_takes_resource (const char* name)
{
  assert (name);
  return strcasecmp (name, "AUTHENTICATE")
         && strcasecmp (name, "COMMANDS")
         && strcasestr (name, "CREATE_") != name
         && strcasestr (name, "DESCRIBE_") != name
         && strcasecmp (name, "EMPTY_TRASHCAN")
         && strcasecmp (name, "GET_VERSION")
         && strcasecmp (name, "HELP")
         && strcasecmp (name, "RUN_WIZARD")
         && strcasestr (name, "SYNC_") != name;
}


/* General helpers. */

/**
 * @brief Check if a resource with a certain name exists already.
 *
 * Conflicting resource can be global or owned by the current user.
 *
 * @param[in]   name      Name of resource to check for.
 * @param[in]   type      Type of resource.
 * @param[in]   resource  Resource to ignore, 0 otherwise.
 */
static gboolean
resource_with_name_exists (const char *name, const char *type,
                           resource_t resource)
{
  int ret;
  char *quoted_name, *quoted_type;

  assert (type);
  if (!name)
    return 0;
  quoted_name = sql_quote (name);
  quoted_type = sql_quote (type);
  if (resource)
    ret = sql_int ("SELECT COUNT(*) FROM %ss"
                   " WHERE name = '%s'"
                   " AND id != %llu"
                   " AND ((owner IS NULL)"
                   "      OR (owner = (SELECT users.id FROM users"
                   "                   WHERE users.uuid = '%s')));",
                   quoted_type, quoted_name, resource,
                   current_credentials.uuid);
  else
    ret = sql_int ("SELECT COUNT(*) FROM %ss"
                   " WHERE name = '%s'"
                   " AND ((owner IS NULL)"
                   "      OR (owner = (SELECT users.id FROM users"
                   "                   WHERE users.uuid = '%s')));",
                   quoted_type, quoted_name, current_credentials.uuid);

  g_free (quoted_name);
  g_free (quoted_type);
  return !!ret;
}

/**
 * @brief Check if a resource with a certain name exists already.
 *
 * Conflicting resource can be owned by anybody.
 *
 * @param[in]   name      Name of resource to check for.
 * @param[in]   type      Type of resource.
 * @param[in]   resource  Resource to ignore, 0 otherwise.
 */
static gboolean
resource_with_name_exists_global (const char *name, const char *type,
                                  resource_t resource)
{
  int ret;
  char *quoted_name, *quoted_type;

  assert (type);
  if (!name)
    return 0;
  quoted_name = sql_quote (name);
  quoted_type = sql_quote (type);
  if (resource)
    ret = sql_int ("SELECT COUNT(*) FROM %ss"
                   " WHERE name = '%s'"
                   " AND id != %llu;",
                   quoted_type, quoted_name, resource);
  else
    ret = sql_int ("SELECT COUNT(*) FROM %ss"
                   " WHERE name = '%s';",
                   quoted_type, quoted_name);

  g_free (quoted_name);
  g_free (quoted_type);
  return !!ret;
}

/**
 * @brief Get the threat of a CVSS.
 *
 * @param  cvss  CVSS.
 *
 * @return Static threat name.
 */
static const char *
cvss_threat (double cvss)
{
  if (cvss < 0.0 || cvss > 10.0)
    return "";
  if (cvss <= 2.0)
    return "Low";
  if (cvss <= 5.0)
    return "Medium";
  return "High";
}

/**
 * @brief Test whether a string equal to a given string exists in an array.
 *
 * @param[in]  array   Array of gchar* pointers.
 * @param[in]  string  String.
 *
 * @return 1 if a string equal to \arg string exists in \arg array, else 0.
 */
static int
member (GPtrArray *array, const char *string)
{
  const gchar *item;
  int index = 0;
  while ((item = (gchar*) g_ptr_array_index (array, index++)))
    if (strcmp (item, string) == 0) return 1;
  return 0;
}

/**
 * @brief Ensure a string is in an array.
 *
 * @param[in]  array   Array.
 * @param[in]  string  String.  Copied into array.
 */
static void
array_add_new_string (array_t *array, const gchar *string)
{
  guint index;
  for (index = 0; index < array->len; index++)
    if (strcmp (g_ptr_array_index (array, index), string) == 0)
      return;
  array_add (array, g_strdup (string));
}

/**
 * @brief Find a resource in the trashcan given a UUID.
 *
 * @param[in]   type      Type of resource.
 * @param[in]   uuid      UUID of resource.
 * @param[out]  resource  Resource return, 0 if succesfully failed to find
 *                        resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
static gboolean
find_trash (const char *type, const char *uuid, resource_t *resource)
{
  gchar *quoted_uuid;

  if (!uuid)
    return FALSE;
  quoted_uuid = sql_quote (uuid);
  if (user_owns_trash_uuid (type, quoted_uuid) == 0)
    {
      g_free (quoted_uuid);
      *resource = 0;
      return FALSE;
    }
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss_trash WHERE uuid = '%s';",
                     type,
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Convert an OTP time into seconds since epoch.
 *
 * Use UTC as timezone.
 *
 * @param[in]  text_time  Time as text in ctime format.
 *
 * @return Time since epoch.
 */
static int
parse_otp_time (const char *text_time)
{
  int epoch_time;
  struct tm tm;
  gchar *tz;

  /* Scanner sends UTC in ctime format: "Wed Jun 30 21:49:08 1993". */

  /* Store current TZ. */
  tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

  if (setenv ("TZ", "UTC", 1) == -1)
    {
      g_warning ("%s: Failed to switch to UTC", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }

  if (strptime ((char*) text_time, "%a %b %d %H:%M:%S %Y", &tm) == NULL)
    {
      g_warning ("%s: Failed to parse time", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }
  epoch_time = mktime (&tm);
  if (epoch_time == -1)
    {
      g_warning ("%s: Failed to make time", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }

  /* Revert to stored TZ. */
  if (tz)
    {
      if (setenv ("TZ", tz, 1) == -1)
        {
          g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
          g_free (tz);
          return 0;
        }
    }
  else
    unsetenv ("TZ");

  g_free (tz);
  return epoch_time;
}

/**
 * @brief Convert a ctime into seconds since epoch.
 *
 * Use the current timezone.
 *
 * @param[in]  text_time  Time as text in ctime format.
 *
 * @return Time since epoch.
 */
static int
parse_ctime (const char *text_time)
{
  int epoch_time;
  struct tm tm;

  /* ctime format: "Wed Jun 30 21:49:08 1993". */

  if (strptime ((char*) text_time, "%a %b %d %H:%M:%S %Y", &tm) == NULL)
    {
      g_warning ("%s: Failed to parse time", __FUNCTION__);
      return 0;
    }
  epoch_time = mktime (&tm);
  if (epoch_time == -1)
    {
      g_warning ("%s: Failed to make time", __FUNCTION__);
      return 0;
    }

  return epoch_time;
}

/**
 * @brief Convert an ISO time into seconds since epoch.
 *
 * For backward compatibility, if the conversion fails try parse in ctime
 * format.
 *
 * @param[in]  text_time  Time as text in ISO format: 2011-11-03T09:23:28+02:00.
 *
 * @return Time since epoch.  0 on error.
 */
static int
parse_iso_time (const char *text_time)
{
  int epoch_time;
  struct tm tm;

  if (strptime ((char*) text_time, "%FT%T%z", &tm) == NULL)
    {
      gchar *tz;

      if (strptime ((char*) text_time, "%FT%TZ", &tm) == NULL)
        return parse_ctime (text_time);

      /* Store current TZ. */
      tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

      if (setenv ("TZ", "UTC", 1) == -1)
        {
          g_warning ("%s: Failed to switch to UTC", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }

      if (strptime ((char*) text_time, "%FT%TZ", &tm) == NULL)
        {
          assert (0);
          g_warning ("%s: Failed to parse time", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }

      epoch_time = mktime (&tm);
      if (epoch_time == -1)
        {
          g_warning ("%s: Failed to make time", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }

      /* Revert to stored TZ. */
      if (tz)
        {
          if (setenv ("TZ", tz, 1) == -1)
            {
              g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
              g_free (tz);
              return 0;
            }
        }
      else
        unsetenv ("TZ");

      g_free (tz);
    }
  else
    {
      gchar *tz, *new_tz;
      int offset_hour, offset_minute;
      char sign;

      /* Get the timezone offset from the string. */

      if (sscanf ((char*) text_time,
                  "%*u-%*u-%*uT%*u:%*u:%*u%[-+]%d:%d",
                  &sign, &offset_hour, &offset_minute)
          != 3)
        {
          /* Perhaps %z is an acronym like "CEST".  Assume it's local time. */
          epoch_time = mktime (&tm);
          if (epoch_time == -1)
            {
              g_warning ("%s: Failed to make time", __FUNCTION__);
              return 0;
            }
          return epoch_time;
        }

      /* Store current TZ. */

      tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

      /* Switch to the timezone in the time string. */

      new_tz = g_strdup_printf ("UTC%c%d:%d",
                                sign == '-' ? '+' : '-',
                                offset_hour,
                                offset_minute);
      if (setenv ("TZ", new_tz, 1) == -1)
        {
          g_warning ("%s: Failed to switch to %s", __FUNCTION__, new_tz);
          g_free (new_tz);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }
      g_free (new_tz);

      /* Parse time again under the new timezone. */

      if (strptime ((char*) text_time, "%FT%T%z", &tm) == NULL)
        {
          assert (0);
          g_warning ("%s: Failed to parse time", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }

      epoch_time = mktime (&tm);
      if (epoch_time == -1)
        {
          g_warning ("%s: Failed to make time", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          return 0;
        }

      /* Revert to stored TZ. */
      if (tz)
        {
          if (setenv ("TZ", tz, 1) == -1)
            {
              g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
              g_free (tz);
              return 0;
            }
        }
      else
        unsetenv ("TZ");

      g_free (tz);
    }

  return epoch_time;
}

/**
 * @brief Create an ISO time from seconds since epoch.
 *
 * @param[in]  epoch_time  Time in seconds from epoch.
 * @param[out] abbrev      Abbreviation for current timezone.
 *
 * @return Pointer to ISO time in static memory, or NULL on error.
 */
static char *
iso_time_internal (time_t *epoch_time, const char **abbrev)
{
  struct tm *tm;
  static char time_string[100];

  tm = localtime (epoch_time);
  if (timezone == 0)
    {
      if (strftime (time_string, 98, "%FT%TZ", tm) == 0)
        return NULL;

      if (abbrev)
        *abbrev = "UTC";
    }
  else
    {
      int len;

      if (strftime (time_string, 98, "%FT%T%z", tm) == 0)
        return NULL;

      /* Insert the ISO 8601 colon by hand. */
      len = strlen (time_string);
      time_string[len + 1] = '\0';
      time_string[len] = time_string[len - 1];
      time_string[len - 1] = time_string[len - 2];
      time_string[len - 2] = ':';

      if (abbrev)
        {
          static char abbrev_string[100];
          if (strftime (abbrev_string, 98, "%Z", tm) == 0)
            return NULL;
          *abbrev = abbrev_string;
        }
    }

  return time_string;
}

/**
 * @brief Create an ISO time from seconds since epoch.
 *
 * @param[in]  epoch_time  Time in seconds from epoch.
 *
 * @return Pointer to ISO time in static memory, or NULL on error.
 */
char *
iso_time (time_t *epoch_time)
{
  return iso_time_internal (epoch_time, NULL);
}

/**
 * @brief Create an ISO time from seconds since epoch, given a timezone.
 *
 * @param[in]  epoch_time  Time in seconds from epoch.
 * @param[in]  timezone    Timezone.
 * @param[out] abbrev      Timezone abbreviation.
 *
 * @return Pointer to ISO time in static memory, or NULL on error.
 */
char *
iso_time_tz (time_t *epoch_time, const char *timezone, const char **abbrev)
{
  gchar *tz;
  char *ret;

  if (timezone == NULL)
    return iso_time (epoch_time);

  /* Store current TZ. */
  tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

  if (setenv ("TZ", timezone, 1) == -1)
    {
      g_warning ("%s: Failed to switch to timezone", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return iso_time (epoch_time);
    }

  tzset ();
  ret = iso_time_internal (epoch_time, abbrev);

  /* Revert to stored TZ. */
  if (tz)
    {
      if (setenv ("TZ", tz, 1) == -1)
        {
          g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
          g_free (tz);
          return ret;
        }
    }
  else
    unsetenv ("TZ");

  g_free (tz);
  return ret;
}

/**
 * @brief Get the current offset from UTC of a timezone.
 *
 * @param[in]  zone  Timezone, or NULL for UTC.
 *
 * @return Seconds east of UTC.
 */
long
current_offset (const char *zone)
{
  gchar *tz;
  long offset;
  time_t now;
  struct tm *now_broken;

  if (zone == NULL)
    return 0;

  /* Store current TZ. */
  tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

  if (setenv ("TZ", zone, 1) == -1)
    {
      g_warning ("%s: Failed to switch to timezone", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }

  tzset ();

  time (&now);
  now_broken = localtime (&now);
  if (setenv ("TZ", "UTC", 1) == -1)
    {
      g_warning ("%s: Failed to switch to UTC", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }
  tzset ();
  offset = - (now - mktime (now_broken));

  /* Revert to stored TZ. */
  if (tz)
    {
      if (setenv ("TZ", tz, 1) == -1)
        {
          g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
          g_free (tz);
          return 0;
        }
    }
  else
    unsetenv ("TZ");

  g_free (tz);
  return offset;
}

/**
 * @brief Get the offset from UTC of a timezone at a particular time.
 *
 * @param[in]  zone  Timezone, or NULL for UTC.
 * @param[in]  time  Time.
 *
 * @return Seconds east of UTC.
 */
long
time_offset (const char *zone, time_t time)
{
  gchar *tz;
  struct tm *time_broken;
  int mins;
  char buf[100];

  if (zone == NULL || strcmp (zone, "UTC") == 0)
    return 0;

  /* Store current TZ. */
  tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

  if (setenv ("TZ", zone, 1) == -1)
    {
      g_warning ("%s: Failed to switch to timezone", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }

  tzset ();

  time_broken = localtime (&time);
  if (strftime (buf, 100, "%z", time_broken) == 0)
    {
      g_warning ("%s: Failed to format timezone", __FUNCTION__);
      if (tz != NULL)
        setenv ("TZ", tz, 1);
      g_free (tz);
      return 0;
    }

  if (strlen (buf) > 3)
    {
      mins = atoi (buf);
      mins /= 100;
      mins *= 60;
      mins += atoi (buf + 3);
    }
  else
    mins = 0;

  /* Revert to stored TZ. */
  if (tz)
    {
      if (setenv ("TZ", tz, 1) == -1)
        {
          g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
          g_free (tz);
          return mins * 60;
        }
    }
  else
    unsetenv ("TZ");

  g_free (tz);
  return mins * 60;
}

/**
 * @brief Find a string in an array.
 *
 * @param[in]  array   Array.
 * @param[in]  string  String.
 *
 * @return The string from the array if found, else NULL.
 */
static gchar*
array_find_string (array_t *array, const gchar *string)
{
  guint index;
  for (index = 0; index < array->len; index++)
    {
      gchar *ele;
      ele = (gchar*) g_ptr_array_index (array, index);
      if (ele && (strcmp (ele, string) == 0))
        return ele;
    }
  return NULL;
}

/**
 * @brief Find a string in a glib style string vector.
 *
 * @param[in]  vector  Vector.
 * @param[in]  string  String.
 *
 * @return The string from the vector if found, else NULL.
 */
static const gchar*
vector_find_string (const gchar **vector, const gchar *string)
{
  if (vector == NULL)
    return NULL;
  while (*vector)
    if (strcmp (*vector, string) == 0)
      return *vector;
    else
      vector++;
  return NULL;
}

/**
 * @brief Find a filter string in a glib style string vector.
 *
 * @param[in]  vector  Vector.
 * @param[in]  string  String.
 *
 * @return 1 if found, 2 if found with underscore prefix, else NULL.
 */
static int
vector_find_filter (const gchar **vector, const gchar *string)
{
  gchar *underscore;
  if (vector_find_string (vector, string))
    return 1;
  underscore = g_strdup_printf ("_%s", string);
  if (vector_find_string (vector, underscore))
    {
      g_free (underscore);
      return 2;
    }
  g_free (underscore);
  return 0;
}

/**
 * @brief Extract a tag from an OTP tag list.
 *
 * @param[in]   tags  Tag list.
 * @param[out]  tag   Tag name.
 *
 * @return Newly allocated tag value.
 */
gchar *
tag_value (const gchar *tags, const gchar *tag)
{
  gchar **split, **point;

  /* creation_date=2009-04-09 14:18:58 +0200 (Thu, 09 Apr 2009)|... */

  if (tags == NULL)
    return g_strdup ("");

  split = g_strsplit (tags, "|", 0);
  point = split;

  while (*point)
    {
      if ((strlen (*point) > strlen (tag))
          && (strncmp (*point, tag, strlen (tag)) == 0)
          && ((*point)[strlen (tag)] == '='))
        {
          gchar *ret;
          ret = g_strdup (*point + strlen (tag) + 1);
          g_strfreev (split);
          return ret;
        }
      point++;
    }
  g_strfreev (split);
  return g_strdup ("");
}

/**
 * @brief Try convert an OTP NVT tag time string into epoch time.
 *
 * @param[in]   string   String.
 * @param[out]  seconds  Time as seconds since the epoch.
 *
 * @return -1 failed to parse time, -2 failed to make time, -3 failed to parse
 *         timezone offset, 0 success.
 */
int
parse_time (const gchar *string, int *seconds)
{
  int epoch_time, offset;
  struct tm tm;

  if ((strcmp ((char*) string, "") == 0)
      || (strcmp ((char*) string, "$Date: $") == 0)
      || (strcmp ((char*) string, "$Date$") == 0)
      || (strcmp ((char*) string, "$Date:$") == 0)
      || (strcmp ((char*) string, "$Date") == 0)
      || (strcmp ((char*) string, "$$") == 0))
    {
      if (seconds)
        *seconds = 0;
      return 0;
    }

  /* Parse the time. */

  /* 2011-08-09 08:20:34 +0200 (Tue, 09 Aug 2011) */
  /* $Date: 2012-02-17 16:05:26 +0100 (Fr, 17. Feb 2012) $ */
  /* $Date: Fri, 11 Nov 2011 14:42:28 +0100 $ */
  if ((strptime ((char*) string, "%F %T %z", &tm) == NULL)
      && (strptime ((char*) string, "$Date: %F %T %z", &tm) == NULL)
      && (strptime ((char*) string, "%a %b %d %T %Y %z", &tm) == NULL)
      && (strptime ((char*) string, "$Date: %a, %d %b %Y %T %z", &tm) == NULL)
      && (strptime ((char*) string, "$Date: %a %b %d %T %Y %z", &tm) == NULL))
    {
      g_warning ("%s: Failed to parse time: %s", __FUNCTION__, string);
      return -1;
    }
  epoch_time = mktime (&tm);
  if (epoch_time == -1)
    {
      g_warning ("%s: Failed to make time: %s", __FUNCTION__, string);
      return -2;
    }

  /* Get the timezone offset from the string. */

  if ((sscanf ((char*) string, "%*u-%*u-%*u %*u:%*u:%*u %d%*[^]]", &offset)
               != 1)
      && (sscanf ((char*) string, "$Date: %*u-%*u-%*u %*u:%*u:%*u %d%*[^]]",
                  &offset)
          != 1)
      && (sscanf ((char*) string, "%*s %*s %*s %*u:%*u:%*u %*u %d%*[^]]",
                  &offset)
          != 1)
      && (sscanf ((char*) string,
                  "$Date: %*s %*s %*s %*u %*u:%*u:%*u %d%*[^]]",
                  &offset)
          != 1)
      && (sscanf ((char*) string, "$Date: %*s %*s %*s %*u:%*u:%*u %*u %d%*[^]]",
                  &offset)
          != 1))
    {
      g_warning ("%s: Failed to parse timezone offset: %s", __FUNCTION__,
                 string);
      return -3;
    }

  /* Use the offset to convert to UTC. */

  if (offset < 0)
    {
      epoch_time += ((-offset) / 100) * 60 * 60;
      epoch_time += ((-offset) % 100) * 60;
    }
  else if (offset > 0)
    {
      epoch_time -= (offset / 100) * 60 * 60;
      epoch_time -= (offset % 100) * 60;
    }

  if (seconds)
    *seconds = epoch_time;
  return 0;
}


/* Filter utilities. */

/**
 * @brief Get the symbol of a keyword relation.
 *
 * @param[in]  relation  Relation.
 *
 * @return Relation symbol.
 */
const char *
keyword_relation_symbol (keyword_relation_t relation)
{
  switch (relation)
    {
      case KEYWORD_RELATION_APPROX:        return "~";
      case KEYWORD_RELATION_COLUMN_ABOVE:  return ">";
      case KEYWORD_RELATION_COLUMN_APPROX: return "~";
      case KEYWORD_RELATION_COLUMN_EQUAL:  return "=";
      case KEYWORD_RELATION_COLUMN_BELOW:  return "<";
      case KEYWORD_RELATION_COLUMN_REGEXP: return ":";
      default:                             return "";
    }
}

/**
 * @brief Free a keyword.
 *
 * @param[in]  keyword  Filter keyword.
 */
static void
keyword_free (keyword_t* keyword)
{
  g_free (keyword->string);
  g_free (keyword->column);
}

/**
 * @brief Parse a filter keyword.
 *
 * @param[in]  keyword  Filter keyword.
 */
static void
parse_keyword (keyword_t* keyword)
{
  gchar *string;
  int digits;

  if (keyword->column == NULL && keyword->equal == 0)
    {
      keyword->relation = KEYWORD_RELATION_APPROX;
      keyword->type = KEYWORD_TYPE_STRING;
      return;
    }

  /* Special values to substitute */

  if (keyword->column
      && (strcasecmp (keyword->column, "severity") == 0
          || strcasecmp (keyword->column, "new_severity") == 0))
    {
      if (strcasecmp (keyword->string, "Log") == 0)
        {
          keyword->double_value = SEVERITY_LOG;
          keyword->type = KEYWORD_TYPE_DOUBLE;
          return;
        }
      if (strcasecmp (keyword->string, "False Positive") == 0)
        {
          keyword->double_value = SEVERITY_FP;
          keyword->type = KEYWORD_TYPE_DOUBLE;
          return;
        }
      else if (strcasecmp (keyword->string, "Debug") == 0)
        {
          keyword->double_value = SEVERITY_DEBUG;
          keyword->type = KEYWORD_TYPE_DOUBLE;
          return;
        }
      else if (strcasecmp (keyword->string, "Error") == 0)
        {
          keyword->double_value = SEVERITY_ERROR;
          keyword->type = KEYWORD_TYPE_DOUBLE;
          return;
        }
    }

  /* The type. */

  string = keyword->string;
  if (*string == '\0')
    {
      keyword->type = KEYWORD_TYPE_STRING;
      return;
    }
  if (*string && *string == '-' && strlen (string) > 1) string++;
  digits = 0;
  while (*string && isdigit (*string))
    {
      digits = 1;
      string++;
    }
  if (digits == 0)
    keyword->type = KEYWORD_TYPE_STRING;
  else if (*string)
    {
      struct tm date;
      gchar next;
      int parsed_integer;
      double parsed_double;
      char dummy[2];
      memset (&date, 0, sizeof (date));
      next = *(string + 1);
      if (next == '\0' && *string == 's')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + atoi (keyword->string);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'm')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 60);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'h')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 3600);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'd')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 86400);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'w')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + atoi (keyword->string) * 604800;
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'M')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = add_months (now, atoi (keyword->string));
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'y')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = add_months (now,
                                               atoi (keyword->string) * 12);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (strptime (keyword->string, "%Y-%m-%dT%H:%M", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (strptime (keyword->string, "%Y-%m-%d", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (sscanf (keyword->string, "%d%1s", &parsed_integer, dummy) == 1)
        {
          keyword->integer_value = parsed_integer;
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (sscanf (keyword->string, "%lf%1s", &parsed_double, dummy) == 1
               && parsed_double <= DBL_MAX)
        {
          keyword->double_value = parsed_double;
          keyword->type = KEYWORD_TYPE_DOUBLE;
        }
      else
        keyword->type = KEYWORD_TYPE_STRING;
    }
  else
    {
      keyword->integer_value = atoi (keyword->string);
      keyword->type = KEYWORD_TYPE_INTEGER;
    }
}

/**
 * @brief Check whether a keyword has any effect in the filter.
 *
 * Some keywords are redundant, like a second sort= keyword.
 *
 * @param[in]  array    Array of existing keywords.
 * @param[in]  keyword  Keyword under consideration.
 *
 * @return 0 no, 1 yes.
 */
static int
keyword_applies (array_t *array, const keyword_t *keyword)
{
  if (keyword->column
      && ((strcmp (keyword->column, "sort") == 0)
          || (strcmp (keyword->column, "sort-reverse") == 0))
      && (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL))
    {
      int index;

      index = array->len;
      while (index--)
        {
          keyword_t *item;
          item = (keyword_t*) g_ptr_array_index (array, index);
          if (item->column
              && ((strcmp (item->column, "sort") == 0)
                  || (strcmp (item->column, "sort-reverse") == 0)))
            return 0;
        }
      return 1;
    }

  if (keyword->column
      && (strcmp (keyword->column, "first") == 0))
    {
      int index;

      index = array->len;
      while (index--)
        {
          keyword_t *item;
          item = (keyword_t*) g_ptr_array_index (array, index);
          if (item->column && (strcmp (item->column, "first") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "rows") == 0))
    {
      int index;

      index = array->len;
      while (index--)
        {
          keyword_t *item;
          item = (keyword_t*) g_ptr_array_index (array, index);
          if (item->column && (strcmp (item->column, "rows") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "apply_overrides") == 0))
    {
      int index;

      index = array->len;
      while (index--)
        {
          keyword_t *item;
          item = (keyword_t*) g_ptr_array_index (array, index);
          if (item->column && (strcmp (item->column, "apply_overrides") == 0))
            return 0;
        }
    }

  return 1;
}

/**
 * @brief Free a split filter.
 *
 * @param[in]  split  Split filter.
 */
void
filter_free (array_t *split)
{
  keyword_t **point;
  for (point = (keyword_t**) split->pdata; *point; point++)
    keyword_free (*point);
  array_free (split);
}

/**
 * @brief Split the filter term into parts.
 *
 * @param[in]  given_filter  Filter term.
 *
 * @return Array of strings, the parts.
 */
array_t *
split_filter (const gchar* given_filter)
{
  int in_quote, between;
  array_t *parts;
  const gchar *current_part, *filter;
  keyword_t *keyword;

  filter = given_filter;
  parts = make_array ();
  in_quote = 0;
  between = 1;
  keyword = NULL;
  current_part = filter;  /* To silence compiler warning. */
  while (*filter)
    {
      switch (*filter)
        {
          case '=':
            if (between)
              {
                /* Empty index.  Start a part. */
                keyword = g_malloc0 (sizeof (keyword_t));
                keyword->equal = 1;
                current_part = filter + 1;
                between = 0;
                break;
              }
          case ':':
          case '~':
          case '>':
          case '<':
            if (between)
              {
                /* Empty index.  Just start a part for now. */
                keyword = g_malloc0 (sizeof (keyword_t));
                current_part = filter;
                between = 0;
                break;
              }
            if (in_quote)
              break;
            /* End of an index. */
            if (keyword == NULL)
              {
                assert (0);
                break;
              }
            if (keyword->column)
              /* Already had an index char. */
              break;
            if (filter <= (current_part - 1))
              {
                assert (0);
                break;
              }
            keyword->column = g_strndup (current_part,
                                         filter - current_part);
            current_part = filter + 1;
            switch (*filter)
              {
                case '=':
                  keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
                  break;
                case '~':
                  keyword->relation = KEYWORD_RELATION_COLUMN_APPROX;
                  break;
                case '>':
                  keyword->relation = KEYWORD_RELATION_COLUMN_ABOVE;
                  break;
                case '<':
                  keyword->relation = KEYWORD_RELATION_COLUMN_BELOW;
                  break;
                case ':':
                  keyword->relation = KEYWORD_RELATION_COLUMN_REGEXP;
                  break;
              }
            break;

          case ' ':
          case '\t':
          case '\n':
          case '\r':
            if (in_quote || between)
              break;
            /* End of a part. */
            if (keyword == NULL)
              {
                assert (0);
                break;
              }
            keyword->string = g_strndup (current_part, filter - current_part);
            parse_keyword (keyword);
            if (keyword_applies (parts, keyword))
              array_add (parts, keyword);
            keyword = NULL;
            between = 1;
            break;

          case '"':
            if (in_quote)
              {
                /* End of a quoted part. */
                if (keyword == NULL)
                  {
                    assert (0);
                    break;
                  }
                keyword->quoted = 1;
                keyword->string = g_strndup (current_part,
                                             filter - current_part);
                parse_keyword (keyword);
                if (keyword_applies (parts, keyword))
                  array_add (parts, keyword);
                keyword = NULL;
                in_quote = 0;
                between = 1;
              }
            else if (between)
              {
                /* Start of a quoted part. */
                keyword = g_malloc0 (sizeof (keyword_t));
                in_quote = 1;
                current_part = filter + 1;
                between = 0;
              }
            else if (keyword->column && filter == current_part)
              {
                /* A quoted index. */
                in_quote = 1;
                current_part++;
              }
            else if (keyword->equal && filter == current_part)
              {
                /* A quoted exact term, like ="abc". */
                in_quote = 1;
                current_part++;
              }
            /* Else just a quote in a keyword, like ab"cd. */
            break;

          default:
            if (between)
              {
                /* Start of a part. */
                keyword = g_malloc0 (sizeof (keyword_t));
                current_part = filter;
                between = 0;
              }
            break;
        }
      filter++;
    }
  if (between == 0)
    {
      if (keyword == NULL)
        assert (0);
      else
        {
          keyword->quoted = in_quote;
          keyword->string = g_strdup (current_part);
          parse_keyword (keyword);
          if (keyword_applies (parts, keyword))
            array_add (parts, keyword);
          keyword = NULL;
        }
    }
  assert (keyword == NULL);

  {
    int index, first, max, sort;
    keyword_t *keyword;

    index = parts->len;
    first = max = sort = 0;
    while (index--)
      {
        keyword_t *item;
        item = (keyword_t*) g_ptr_array_index (parts, index);
        if (item->column && (strcmp (item->column, "first") == 0))
          first = 1;
        else if (item->column && (strcmp (item->column, "rows") == 0))
          max = 1;
        else if (item->column
                 && ((strcmp (item->column, "sort") == 0)
                     || (strcmp (item->column, "sort-reverse") == 0)))
          sort = 1;
      }

    if (first == 0)
      {
        keyword = g_malloc0 (sizeof (keyword_t));
        keyword->column = g_strdup ("first");
        keyword->string = g_strdup ("1");
        keyword->type = KEYWORD_TYPE_STRING;
        keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
        array_add (parts, keyword);
      }

    if (max == 0)
      {
        keyword = g_malloc0 (sizeof (keyword_t));
        keyword->column = g_strdup ("rows");
        /* If there was a filter, make max_return default to Rows Per
         * Page.  This keeps the pre-filters OMP behaviour when the filter
         * is empty, but is more convenenient for clients that set the
         * filter. */
        if (strlen (given_filter))
          keyword->string = g_strdup ("-2");
        else
          keyword->string = g_strdup ("-1");
        keyword->type = KEYWORD_TYPE_STRING;
        keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
        array_add (parts, keyword);
      }

    if (sort == 0)
      {
        keyword = g_malloc0 (sizeof (keyword_t));
        keyword->column = g_strdup ("sort");
        keyword->string = g_strdup ("name");
        keyword->type = KEYWORD_TYPE_STRING;
        keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
        array_add (parts, keyword);
      }
  }

  array_add (parts, NULL);

  return parts;
}

/**
 * @brief Get info from a filter.
 *
 * @param[in]   filter      Filter.
 * @param[out]  first       Number of first item.
 * @param[out]  max         Max number of rows.
 * @param[out]  sort_field  Sort field.
 * @param[out]  sort_order  Sort order.
 */
void
manage_filter_controls (const gchar *filter, int *first, int *max,
                        gchar **sort_field, int *sort_order)
{
  keyword_t **point;
  array_t *split;

  if (filter == NULL)
    {
      if (first)
        *first = 1;
      if (max)
        *max = -1;
      if (sort_field)
        *sort_field = g_strdup ("name");
      if (sort_order)
        *sort_order = 1;
      return;
    }

  split = split_filter (filter);
  point = (keyword_t**) split->pdata;
  if (first)
    {
      *first = 1;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "first") == 0))
            {
              *first = atoi (keyword->string);
              if (*first < 0)
                *first = 0;
              break;
            }
          point++;
        }
    }

  point = (keyword_t**) split->pdata;
  if (max)
    {
      *max = -1;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "rows") == 0))
            {
              *max = atoi (keyword->string);
              if (*max == -2)
                setting_value_int ("5f5a8712-8017-11e1-8556-406186ea4fc5",
                                   max);
              else if (*max < 1)
                *max = -1;
              break;
            }
          point++;
        }
    }

  point = (keyword_t**) split->pdata;
  if (sort_field || sort_order)
    {
      if (sort_field) *sort_field = NULL;
      if (sort_order) *sort_order = 1;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column
              && (strcmp (keyword->column, "sort") == 0))
            {
              if (sort_field) *sort_field = g_strdup (keyword->string);
              if (sort_order) *sort_order = 1;
              break;
            }
          if (keyword->column
              && (strcmp (keyword->column, "sort-reverse") == 0))
            {
              if (sort_field) *sort_field = g_strdup (keyword->string);
              if (sort_order) *sort_order = 0;
              break;
            }
          point++;
        }
      if (sort_field && (*sort_field == NULL))
        *sort_field = g_strdup ("name");
    }

  filter_free (split);
  return;
}

/**
 * @brief Get an int column from a filter split.
 *
 * @param[in]  point   Filter split.
 * @param[in]  column  Name of column.
 * @param[out] val     Value of column.
 *
 * @return 0 success, 1 fail.
 */
int
filter_control_int (keyword_t **point, const char *column, int *val)
{
  if (val)
    while (*point)
      {
        keyword_t *keyword;

        keyword = *point;
        if (keyword->column
            && (strcmp (keyword->column, column) == 0))
          {
            *val = atoi (keyword->string);
            return 0;
          }
        point++;
      }
  return 1;
}

/**
 * @brief Get a string column from a filter split.
 *
 * @param[in]  point   Filter split.
 * @param[in]  column  Name of column.
 * @param[out] string  Value of column, freshly allocated.
 *
 * @return 0 success, 1 fail.
 */
int
filter_control_str (keyword_t **point, const char *column, gchar **string)
{
  if (string)
    while (*point)
      {
        keyword_t *keyword;

        keyword = *point;
        if (keyword->column
            && (strcmp (keyword->column, column) == 0))
          {
            *string = g_strdup (keyword->string);
            return 0;
          }
        point++;
      }
  return 1;
}

/**
 * @brief Get info from a filter for report.
 *
 * @param[in]   filter      Filter.
 * @param[out]  first       Number of first item.
 * @param[out]  max         Max number of rows.
 * @param[out]  sort_field  Sort field.
 * @param[out]  sort_order  Sort order.
 * @param[out]  result_hosts_only  Whether to show only hosts with results.
 * @param[out]  min_cvss_base      Minimum CVSS base of included results.  All
 *                                 results if NULL.
 * @param[out]  min_qod        Minimum QoD base of included results.  All
 *                              results if NULL.
 * @param[out]  levels         String describing threat levels (message types)
 *                             to include in count (for example, "hmlgd" for
 *                             High, Medium, Low, loG and Debug).  All levels if
 *                             NULL.
 * @param[out]  delta_states   String describing delta states to include in count
 *                             (for example, "sngc" Same, New, Gone and Changed).
 *                             All levels if NULL.
 * @param[out]  search_phrase      Phrase that results must include.  All results
 *                                 if NULL or "".
 * @param[out]  search_phrase_exact  Whether search phrase is exact.
 * @param[out]  autofp             Whether to apply auto FP filter.
 * @param[out]  notes              Whether to include notes.
 * @param[out]  overrides          Whether to include overrides.
 * @param[out]  apply_overrides    Whether to apply overrides.
 * @param[out]  zone               Timezone.
 */
void
manage_report_filter_controls (const gchar *filter, int *first, int *max,
                               gchar **sort_field, int *sort_order,
                               int *result_hosts_only, gchar **min_cvss_base,
                               gchar **min_qod,
                               gchar **levels, gchar **delta_states,
                               gchar **search_phrase, int *search_phrase_exact,
                               int *autofp, int *notes, int *overrides,
                               int *apply_overrides, gchar **zone)
{
  keyword_t **point;
  array_t *split;
  int val;
  gchar *string;

  if (filter == NULL)
    return;

  split = split_filter (filter);
  point = (keyword_t**) split->pdata;
  if (first)
    {
      *first = 1;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "first") == 0))
            {
              *first = atoi (keyword->string);
              if (*first < 0)
                *first = 0;
              break;
            }
          point++;
        }
      /* Switch from 1 to 0 indexing. */

      (*first)--;
    }

  point = (keyword_t**) split->pdata;
  if (max)
    {
      *max = 100;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "rows") == 0))
            {
              *max = atoi (keyword->string);
              if (*max == -2)
                setting_value_int ("5f5a8712-8017-11e1-8556-406186ea4fc5",
                                   max);
              else if (*max < 1)
                *max = -1;
              break;
            }
          point++;
        }
    }

  point = (keyword_t**) split->pdata;
  if (sort_field || sort_order)
    {
      if (sort_field) *sort_field = NULL;
      if (sort_order) *sort_order = 1;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column
              && (strcmp (keyword->column, "sort") == 0))
            {
              if (sort_field) *sort_field = g_strdup (keyword->string);
              if (sort_order) *sort_order = 1;
              break;
            }
          if (keyword->column
              && (strcmp (keyword->column, "sort-reverse") == 0))
            {
              if (sort_field) *sort_field = g_strdup (keyword->string);
              if (sort_order) *sort_order = 0;
              break;
            }
          point++;
        }
      if (sort_field && (*sort_field == NULL))
        *sort_field = g_strdup ("name");
    }

  if (search_phrase)
    {
      GString *phrase;
      phrase = g_string_new ("");
      point = (keyword_t**) split->pdata;
      if (search_phrase_exact)
        *search_phrase_exact = 0;
      while (*point)
        {
          keyword_t *keyword;

          keyword = *point;
          if (keyword->column == NULL)
            {
              if (search_phrase_exact && keyword->equal)
                /* If one term is "exact" then the search is "exact", because
                 * for reports the filter terms are combined into a single
                 * search term. */
                *search_phrase_exact = 1;
              g_string_append_printf (phrase, "%s ", keyword->string);
            }
          point++;
        }
      *search_phrase = g_strchomp (phrase->str);
      g_string_free (phrase, FALSE);
    }

  if (result_hosts_only)
    {
      if (filter_control_int ((keyword_t **) split->pdata,
                              "result_hosts_only",
                              &val))
        *result_hosts_only = 1;
      else
        *result_hosts_only = val;
    }

  if (autofp)
    {
      if (filter_control_int ((keyword_t **) split->pdata,
                              "autofp",
                              &val))
        *autofp = 0;
      else
        *autofp = val;
    }

  if (notes)
    {
      if (filter_control_int ((keyword_t **) split->pdata,
                              "notes",
                              &val))
        *notes = 1;
      else
        *notes = val;
    }

  if (overrides)
    {
      if (filter_control_int ((keyword_t **) split->pdata,
                              "overrides",
                              &val))
        *overrides = 1;
      else
        *overrides = val;
    }

  if (apply_overrides)
    {
      if (filter_control_int ((keyword_t **) split->pdata,
                              "apply_overrides",
                              &val))
        {
          if (filter_control_int ((keyword_t **) split->pdata,
                                  "overrides",
                                  &val))
            *apply_overrides = 1;
          else
            *apply_overrides = val;
        }
      else
        *apply_overrides = val;
    }

  if (delta_states)
    {
      if (filter_control_str ((keyword_t **) split->pdata,
                              "delta_states",
                              &string))
        *delta_states = NULL;
      else
        *delta_states = string;
    }

  if (levels)
    {
      if (filter_control_str ((keyword_t **) split->pdata,
                              "levels",
                              &string))
        *levels = NULL;
      else
        *levels = string;
    }

  if (min_cvss_base)
    {
      if (filter_control_str ((keyword_t **) split->pdata,
                              "min_cvss_base",
                              &string))
        *min_cvss_base = NULL;
      else
        *min_cvss_base = string;
    }

  if (min_qod)
    {
      if (filter_control_str ((keyword_t **) split->pdata,
                              "min_qod",
                              &string))
        *min_qod = NULL;
      else
        *min_qod = string;
    }

  if (zone)
    {
      if (filter_control_str ((keyword_t **) split->pdata,
                              "timezone",
                              &string))
        *zone = NULL;
      else
        *zone = string;
    }

  filter_free (split);
  return;
}

/**
 * @brief Append relation to filter.
 *
 * @param[in]  clean     Filter.
 * @param[in]  keyword   Keyword
 * @param[in]  relation  Relation char.
 */
static void
append_relation (GString *clean, keyword_t *keyword, const char relation)
{
  if ((strcmp (keyword->column, "rows") == 0)
      && (strcmp (keyword->string, "-2") == 0))
    {
      int max;
      setting_value_int ("5f5a8712-8017-11e1-8556-406186ea4fc5",
                         &max);
      g_string_append_printf (clean,
                              " %s%c%i",
                              keyword->column,
                              relation,
                              max);
    }
  else if (keyword->quoted)
    g_string_append_printf (clean,
                            " %s%c\"%s\"",
                            keyword->column,
                            relation,
                            keyword->string);
  else
    g_string_append_printf (clean,
                            " %s%c%s",
                            keyword->column,
                            relation,
                            keyword->string);
}

/**
 * @brief Clean a filter, removing a keyword in the process.
 *
 * @param[in]  filter  Filter.
 * @param[in]  column  Keyword to remove, or NULL.
 *
 * @return Cleaned filter.
 */
gchar *
manage_clean_filter_remove (const gchar *filter, const gchar *column)
{
  GString *clean;
  keyword_t **point;
  array_t *split;

  if (filter == NULL)
    return g_strdup ("");

  clean = g_string_new ("");
  split = split_filter (filter);
  point = (keyword_t**) split->pdata;
  while (*point)
    {
      keyword_t *keyword;

      keyword = *point;
      if (keyword->column
          && column
          && strlen (column)
          && ((strcasecmp (keyword->column, column) == 0)
              || (keyword->column[0] == '_'
                  && strcasecmp (keyword->column + 1, column) == 0)))
        {
          /* Remove this keyword. */;
        }
      else if (keyword->column)
        switch (keyword->relation)
          {
            case KEYWORD_RELATION_COLUMN_EQUAL:
              append_relation (clean, keyword, '=');
              break;
            case KEYWORD_RELATION_COLUMN_APPROX:
              append_relation (clean, keyword, '~');
              break;
            case KEYWORD_RELATION_COLUMN_ABOVE:
              append_relation (clean, keyword, '>');
              break;
            case KEYWORD_RELATION_COLUMN_BELOW:
              append_relation (clean, keyword, '<');
              break;
            case KEYWORD_RELATION_COLUMN_REGEXP:
              append_relation (clean, keyword, ':');
              break;

            case KEYWORD_RELATION_APPROX:
              if (keyword->quoted)
                g_string_append_printf (clean, " \"%s\"", keyword->string);
              else
                g_string_append_printf (clean, " %s", keyword->string);
              break;
          }
      else
        if (keyword->quoted)
          g_string_append_printf (clean, " %s\"%s\"",
                                  keyword->equal ? "=" : "",
                                  keyword->string);
        else
          g_string_append_printf (clean, " %s%s",
                                  keyword->equal ? "=" : "",
                                  keyword->string);
      point++;
    }
  filter_free (split);
  return g_strstrip (g_string_free (clean, FALSE));
}

/**
 * @brief Clean a filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return Cleaned filter.
 */
gchar *
manage_clean_filter (const gchar *filter)
{
  return manage_clean_filter_remove (filter, NULL);
}

/**
 * @brief Return SQL join words for filter_clause.
 *
 * @param[in]  first         Whether keyword is first.
 * @param[in]  last_was_and  Whether last keyword was "and".
 * @param[in]  last_was_not  Whether last keyword was "not".
 *
 * @return SQL join words.
 */
static const char *
get_join (int first, int last_was_and, int last_was_not)
{
  const char *pre;
  if (first)
    {
      if (last_was_not)
        pre = "NOT ";
      else
        pre = "";
    }
  else
    {
      if (last_was_and)
        {
          if (last_was_not)
            pre = " AND NOT ";
          else
            pre = " AND ";
        }
      else
        {
          if (last_was_not)
            pre = " OR NOT ";
          else
            pre = " OR ";
        }
    }
  return pre;
}

/**
 * @brief Iterator column.
 */
typedef struct
{
  gchar *select;    ///< Column for SELECT.
  gchar *filter;    ///< Filter column name.  NULL to use select_column.
} column_t;

/**
 * @brief Get the SELECT column for a filter column.
 *
 * @param[in]  select_columns  SELECT columns.
 * @param[in]  filter_column   Filter column.
 *
 * @return Column for the SELECT statement.
 */
static gchar *
columns_select_column (column_t *select_columns, const char *filter_column)
{
  column_t *columns;
  columns = select_columns;
  while ((*columns).select)
    {
      if ((*columns).filter
          && strcmp ((*columns).filter, filter_column) == 0)
        return (*columns).select;
      if ((*columns).filter
          && *((*columns).filter)
          && *((*columns).filter) == '_'
          && strcmp (((*columns).filter) + 1, filter_column) == 0)
        return (*columns).select;
      columns++;
    }
  columns = select_columns;
  while ((*columns).select)
    {
      if (strcmp ((*columns).select, filter_column) == 0)
        return (*columns).select;
      columns++;
    }
  return NULL;
}

/**
 * @brief Get the SELECT column for a filter column.
 *
 * @param[in]  select_columns  SELECT columns.
 *
 * @return Column for the SELECT statement.
 */
static gchar *
columns_build_select (column_t *select_columns)
{
  if (select_columns == NULL)
    return g_strdup ("''");

  if ((*select_columns).select)
    {
      column_t *columns;
      GString *select;

      columns = select_columns;
      select = g_string_new ("");
      g_string_append (select, (*columns).select);
      if ((*columns).filter)
        g_string_append_printf (select, " AS %s", (*columns).filter);
      columns++;
      while ((*columns).select)
       {
         g_string_append_printf (select, ", %s", (*columns).select);
         if ((*columns).filter)
           g_string_append_printf (select, " AS %s", (*columns).filter);
         columns++;
       }
      return g_string_free (select, FALSE);
    }
  return g_strdup ("''");
}

/**
 * @brief Return SQL WHERE clause for restricting a SELECT to a filter term.
 *
 * @param[in]  type     Resource type.
 * @param[in]  filter   Filter term.
 * @param[out] trash    Whether the trash table is being queried.
 * @param[in]  columns  Columns in the SQL statement.
 * @param[out] order_return  If given then order clause.
 * @param[out] first_return  If given then first row.
 * @param[out] max_return    If given then max rows.
 * @param[out] permissions   When given then permissions string vector.
 * @param[out] owner_filter  When given then value of owner keyword.
 *
 * @return WHERE clause for filter if one is required, else NULL.
 */
static gchar *
filter_clause (const char* type, const char* filter,
               const char **filter_columns, column_t *select_columns,
               int trash, gchar **order_return, int *first_return,
               int *max_return, array_t **permissions, gchar **owner_filter)
{
  GString *clause, *order;
  keyword_t **point;
  int first_keyword, first_order, last_was_and, last_was_not, last_was_re, skip;
  array_t *split;

  if (filter == NULL)
    filter = "";

  while (*filter && isspace (*filter)) filter++;

  if (permissions)
    *permissions = make_array ();

  if (owner_filter)
    *owner_filter = NULL;

  /* Add SQL to the clause for each keyword or phrase. */

  if (max_return)
    *max_return = -1;

  clause = g_string_new ("");
  order = g_string_new ("");
  split = split_filter (filter);
  point = (keyword_t**) split->pdata;
  first_keyword = 1;
  last_was_and = 0;
  last_was_not = 0;
  last_was_re = 0;
  first_order = 1;
  while (*point)
    {
      gchar *quoted_keyword;
      int index;
      keyword_t *keyword;

      skip = 0;

      keyword = *point;

      if ((keyword->column == NULL)
          && (strlen (keyword->string) == 0))
        {
          point++;
          continue;
        }

      if ((keyword->column == NULL)
          && (strcasecmp (keyword->string, "or") == 0))
        {
          point++;
          continue;
        }

      if ((keyword->column == NULL)
          && (strcasecmp (keyword->string, "and") == 0))
        {
          last_was_and = 1;
          point++;
          continue;
        }

      if ((keyword->column == NULL)
          && (strcasecmp (keyword->string, "not") == 0))
        {
          last_was_not = 1;
          point++;
          continue;
        }

      if ((keyword->column == NULL)
          && (strcasecmp (keyword->string, "re") == 0))
        {
          last_was_re = 1;
          point++;
          continue;
        }

      if ((keyword->column == NULL)
          && (strcasecmp (keyword->string, "regexp") == 0))
        {
          last_was_re = 1;
          point++;
          continue;
        }

      /* Check for ordering parts, like sort=name or sort-reverse=string. */

      if (keyword->column && (strcasecmp (keyword->column, "sort") == 0))
        {
          if (strcmp (keyword->string, "ROWID")
              && (vector_find_filter (filter_columns, keyword->string) == 0))
            {
              point++;
              continue;
            }

          if (first_order)
            {
              if ((strcmp (type, "slave") == 0)
                  && (strcmp (keyword->string, "port") == 0))
                g_string_append_printf (order,
                                        " ORDER BY CAST (port AS INTEGER)"
                                        " ASC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_threat (%s) ASC",
                                          column);
                }
              else if (strcmp (keyword->string, "severity") == 0
                       || strcmp (keyword->string, "original_severity") == 0
                       || strcmp (keyword->string, "cvss") == 0
                       || strcmp (keyword->string, "cvss_base") == 0
                       || strcmp (keyword->string, "max_cvss") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  g_string_append_printf (order,
                                          " ORDER BY CASE CAST (%s AS text)"
                                          " WHEN '' THEN NULL"
                                          " ELSE CAST (%s AS REAL) END ASC",
                                          column,
                                          column);
                }
              else if (strcmp (keyword->string, "roles") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY"
                                          " CASE WHEN %s %s 'Admin.*'"
                                          " THEN '0' || %s"
                                          " ELSE '1' || %s END ASC",
                                          column,
                                          sql_regexp_op (),
                                          column,
                                          column);
                }
              else if ((strcmp (keyword->string, "created") == 0)
                       || (strcmp (keyword->string, "modified") == 0)
                       || (strcmp (keyword->string, "published") == 0)
                       || (strcmp (keyword->string, "qod") == 0)
                       || (strcmp (keyword->string, "cves") == 0)
                       || (strcmp (keyword->string, "high") == 0)
                       || (strcmp (keyword->string, "medium") == 0)
                       || (strcmp (keyword->string, "low") == 0)
                       || (strcmp (keyword->string, "log") == 0)
                       || (strcmp (keyword->string, "false_positive") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY %s ASC",
                                          column);
                }
              else if ((strcmp (keyword->string, "ips") == 0)
                       || (strcmp (keyword->string, "total") == 0)
                       || (strcmp (keyword->string, "tcp") == 0)
                       || (strcmp (keyword->string, "udp") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) ASC",
                                          column);
                }
              else if ((strcmp (type, "note")
                        && strcmp (type, "override"))
                       || (strcmp (keyword->string, "nvt")
                           && strcmp (keyword->string, "name")))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order, " ORDER BY lower (%s) ASC",
                                          column);
                }
              else
                /* Special case for notes text sorting. */
                g_string_append_printf (order,
                                        " ORDER BY nvt ASC,"
                                        "          lower (%ss%s.text) ASC",
                                        type,
                                        trash ? "_trash" : "");
              first_order = 0;
            }
          else
            /* To help the client split_filter restricts the filter to one
             * sorting term, preventing this from happening. */
            g_string_append_printf (order, ", %s ASC",
                                    keyword->string);
          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "sort-reverse") == 0))
        {
          if (strcmp (keyword->string, "ROWID")
              && (vector_find_filter (filter_columns, keyword->string) == 0))
            {
              point++;
              continue;
            }

          if (first_order)
            {
              if ((strcmp (type, "slave") == 0)
                  && (strcmp (keyword->string, "port") == 0))
                g_string_append_printf (order,
                                        " ORDER BY CAST (port AS INTEGER)"
                                        " DESC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_threat (%s) DESC",
                                          column);
                }
              else if (strcmp (keyword->string, "severity") == 0
                       || strcmp (keyword->string, "original_severity") == 0
                       || strcmp (keyword->string, "cvss") == 0
                       || strcmp (keyword->string, "cvss_base") == 0
                       || strcmp (keyword->string, "max_cvss") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  g_string_append_printf (order,
                                          " ORDER BY CASE CAST (%s AS text)"
                                          " WHEN '' THEN NULL"
                                          " ELSE CAST (%s AS REAL) END DESC",
                                          column,
                                          column);
                }
              else if (strcmp (keyword->string, "roles") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY"
                                          " CASE WHEN %s %s 'Admin.*'"
                                          " THEN '0' || %s"
                                          " ELSE '1' || %s END DESC",
                                          column,
                                          sql_regexp_op (),
                                          column,
                                          column);
                }
              else if ((strcmp (keyword->string, "created") == 0)
                       || (strcmp (keyword->string, "modified") == 0)
                       || (strcmp (keyword->string, "published") == 0)
                       || (strcmp (keyword->string, "qod") == 0)
                       || (strcmp (keyword->string, "cves") == 0)
                       || (strcmp (keyword->string, "high") == 0)
                       || (strcmp (keyword->string, "medium") == 0)
                       || (strcmp (keyword->string, "low") == 0)
                       || (strcmp (keyword->string, "log") == 0)
                       || (strcmp (keyword->string, "false_positive") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY %s DESC",
                                          column);
                }
              else if ((strcmp (keyword->string, "ips") == 0)
                       || (strcmp (keyword->string, "total") == 0)
                       || (strcmp (keyword->string, "tcp") == 0)
                       || (strcmp (keyword->string, "udp") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) DESC",
                                          column);
                }
              else if ((strcmp (type, "note")
                        && strcmp (type, "override"))
                       || (strcmp (keyword->string, "nvt")
                           && strcmp (keyword->string, "name")))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order, " ORDER BY lower (%s) DESC",
                                          column);
                }
              else
                /* Special case for notes text sorting. */
                g_string_append_printf (order,
                                        " ORDER BY nvt DESC,"
                                        "          lower (%ss%s.text) DESC",
                                        type,
                                        trash ? "_trash" : "");
              first_order = 0;
            }
          else
            /* To help the client split_filter restricts the filter to one
             * sorting term, preventing this from happening. */
            g_string_append_printf (order, ", %s DESC",
                                    keyword->string);
          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "first") == 0))
        {
          if (first_return)
            {
              /* Subtract 1 to switch from 1 to 0 indexing. */
              *first_return = atoi (keyword->string) - 1;
              if (*first_return < 0)
                *first_return = 0;
            }

          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "rows") == 0))
        {
          if (max_return)
            *max_return = atoi (keyword->string);

          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "permission") == 0))
        {
          if (permissions)
            array_add (*permissions, g_strdup (keyword->string));

          point++;
          continue;
        }
      /* Add tag criteria to clause: tag name with optional value */
      else if (keyword->column
               && (strcasecmp (keyword->column, "tag") == 0))
        {
          gchar **tag_split, *tag_name, *tag_value;
          int value_given, value_numeric;

          quoted_keyword = sql_quote (keyword->string);
          tag_split = g_strsplit (quoted_keyword, "=", 2);
          tag_name = g_strdup (tag_split[0] ? tag_split[0] : "");

          if (tag_split[0] && tag_split[1])
            {
              tag_value = g_strdup (tag_split[1]);
              value_given = 1;
            }
          else
            {
              tag_value = g_strdup ("");
              value_given = 0;
            }

          value_numeric = 0;
          if (value_given)
            {
              double test_d;
              value_numeric = (sscanf (tag_value, "%lf", &test_d) > 0);
            }

          if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL
              || keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE
              || keyword->relation == KEYWORD_RELATION_COLUMN_BELOW)
            {
              if (strcasecmp (type, "allinfo") == 0)
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name = '%s'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid"
                                        "         = allinfo.uuid"
                                        "   AND tags.resource_type"
                                        "         = allinfo.type"
                                        "   %s%s%s))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        tag_name,
                                        (value_given
                                          ? (value_numeric
                                              ? "AND CAST"
                                                " (tags.value AS REAL) = "
                                              : "AND CAST"
                                                " (tags.value AS TEXT) = '")
                                          : ""),
                                        value_given ? tag_value : "",
                                        (value_given && value_numeric == 0
                                          ? "'"
                                          : ""));
              else
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name = '%s'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid = %ss.uuid"
                                        "   AND tags.resource_type = '%s'"
                                        "   %s%s%s))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        tag_name,
                                        type,
                                        type,
                                        (value_given
                                          ? (value_numeric
                                              ? "AND CAST"
                                                " (tags.value AS REAL) = "
                                              : "AND CAST"
                                                " (tags.value AS TEXT) = '")
                                          : ""),
                                        value_given ? tag_value : "",
                                        (value_given && value_numeric == 0
                                          ? "'"
                                          : ""));
            }
          else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX)
            {
              if (strcasecmp (type, "allinfo") == 0)
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name LIKE '%%%%%s%%%%'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid"
                                        "         = allinfo.uuid"
                                        "   AND tags.resource_type"
                                        "         = allinfo.type"
                                        "   AND tags.value LIKE '%%%%%s%%%%'))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        tag_name,
                                        tag_value);
              else
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name LIKE '%%%%%s%%%%'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid = %ss.uuid"
                                        "   AND tags.resource_type = '%s'"
                                        "   AND tags.value LIKE '%%%%%s%%%%'))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        tag_name,
                                        type,
                                        type,
                                        tag_value);
            }
          else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP)
            {
              if (strcasecmp (type, "allinfo") == 0)
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name %s '%s'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid"
                                        "         = allinfo.uuid"
                                        "   AND tags.resource_type"
                                        "         = allinfo.type"
                                        "   AND tags.value"
                                        "       %s '%s'))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        sql_regexp_op (),
                                        tag_name,
                                        sql_regexp_op (),
                                        tag_value);
              else
                g_string_append_printf (clause,
                                        "%s"
                                        "(EXISTS"
                                        "  (SELECT * FROM tags"
                                        "   WHERE tags.name %s '%s'"
                                        "   AND tags.active != 0"
                                        "   AND tags.resource_uuid = %ss.uuid"
                                        "   AND tags.resource_type = '%s'"
                                        "   AND tags.value"
                                        "       %s '%s'))",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        sql_regexp_op (),
                                        tag_name,
                                        type,
                                        type,
                                        sql_regexp_op (),
                                        tag_value);
            }

          g_strfreev(tag_split);
          g_free(tag_name);
          g_free(tag_value);
          first_keyword = 0;
          last_was_and = 0;
          last_was_not = 0;
        }

      /* Add SQL to the clause for each column name. */

      quoted_keyword = NULL;

      if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL)
        {
          if (vector_find_filter (filter_columns, keyword->column) == 0)
            {
              last_was_and = 0;
              last_was_not = 0;
              point++;
              continue;
            }

          if (keyword->column
              && (strlen (keyword->column) > 3)
              && (strcmp (keyword->column + strlen (keyword->column) - 3, "_id")
                  == 0)
              && strcasecmp (keyword->column, "nvt_id"))
            {
              gchar *type_term;

              type_term = g_strndup (keyword->column,
                                     strlen (keyword->column) - 3);
              if (valid_type (type_term) == 0)
                {
                  g_free (type_term);
                  last_was_and = 0;
                  last_was_not = 0;
                  point++;
                  continue;
                }

              quoted_keyword = sql_quote (keyword->string);
              if (strcmp (quoted_keyword, ""))
                g_string_append_printf (clause,
                                        "%s(((SELECT id FROM %ss"
                                        "     WHERE %ss.uuid = '%s')"
                                        "     = %ss.%s"
                                        "     OR %ss.%s IS NULL"
                                        "     OR %ss.%s = 0)",
                                        get_join (first_keyword,
                                                  last_was_and,
                                                  last_was_not),
                                        type_term,
                                        type_term,
                                        quoted_keyword,
                                        type,
                                        type_term,
                                        type,
                                        type_term,
                                        type,
                                        type_term);
              else
                g_string_append_printf (clause,
                                        "%s((%ss.%s IS NULL"
                                        "   OR %ss.%s = 0)",
                                        get_join (first_keyword,
                                                  last_was_and,
                                                  last_was_not),
                                        type,
                                        type_term,
                                        type,
                                        type_term);

              g_free (type_term);
            }
          else if (keyword->column && strcmp (keyword->column, "owner"))
            {
              gchar *column;
              quoted_keyword = sql_quote (keyword->string);
              column = columns_select_column (select_columns, keyword->column);
              assert (column);
              if (keyword->type == KEYWORD_TYPE_INTEGER)
                g_string_append_printf (clause,
                                        "%s(CAST (%s AS NUMERIC) = %i",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        column,
                                        keyword->integer_value);
              else if (keyword->type == KEYWORD_TYPE_DOUBLE)
                g_string_append_printf (clause,
                                        "%s(CAST (%s AS NUMERIC) = %f",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        column,
                                        keyword->double_value);
              else if (strcmp (quoted_keyword, ""))
                g_string_append_printf (clause,
                                        "%s(CAST (%s AS TEXT) = '%s'",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        column,
                                        quoted_keyword);
              else
                g_string_append_printf (clause,
                                        "%s((%s IS NULL OR CAST (%s AS TEXT) = '%s')",
                                        get_join (first_keyword, last_was_and,
                                                  last_was_not),
                                        column,
                                        column,
                                        quoted_keyword);
            }
          else
            {
              /* Skip term.  Owner filtering is done via where_owned. */
              skip = 1;
              if (owner_filter && (*owner_filter == NULL))
                *owner_filter = g_strdup (keyword->string);
            }
        }
      else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX)
        {
          gchar *column;

          if (vector_find_filter (filter_columns, keyword->column) == 0)
            {
              last_was_and = 0;
              last_was_not = 0;
              point++;
              continue;
            }

          quoted_keyword = sql_quote (keyword->string);
          column = columns_select_column (select_columns, keyword->column);
          assert (column);
          g_string_append_printf (clause,
                                  "%s(CAST (%s AS TEXT) LIKE '%%%%%s%%%%'",
                                  get_join (first_keyword, last_was_and,
                                            last_was_not),
                                  column,
                                  quoted_keyword);
        }
      else if (keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE)
        {
          gchar *column;

          if (vector_find_filter (filter_columns, keyword->column) == 0)
            {
              last_was_and = 0;
              last_was_not = 0;
              point++;
              continue;
            }

          quoted_keyword = sql_quote (keyword->string);
          column = columns_select_column (select_columns, keyword->column);
          assert (column);
          if (keyword->type == KEYWORD_TYPE_INTEGER)
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS NUMERIC) > %i",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    keyword->integer_value);
          else if (keyword->type == KEYWORD_TYPE_DOUBLE)
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS NUMERIC) > %f",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    keyword->double_value);
          else
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS TEXT) > '%s'",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    quoted_keyword);
        }
      else if (keyword->relation == KEYWORD_RELATION_COLUMN_BELOW)
        {
          gchar *column;

          if (vector_find_filter (filter_columns, keyword->column) == 0)
            {
              last_was_and = 0;
              last_was_not = 0;
              point++;
              continue;
            }

          quoted_keyword = sql_quote (keyword->string);
          column = columns_select_column (select_columns, keyword->column);
          assert (column);
          if (keyword->type == KEYWORD_TYPE_INTEGER)
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS NUMERIC) < %i",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    keyword->integer_value);
          else if (keyword->type == KEYWORD_TYPE_DOUBLE)
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS NUMERIC) < %f",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    keyword->double_value);
          else
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS TEXT) < '%s'",
                                    get_join (first_keyword, last_was_and,
                                              last_was_not),
                                    column,
                                    quoted_keyword);
        }
      else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP)
        {
          gchar *column;

          if (vector_find_filter (filter_columns, keyword->column) == 0)
            {
              last_was_and = 0;
              last_was_not = 0;
              point++;
              continue;
            }

          quoted_keyword = sql_quote (keyword->string);
          column = columns_select_column (select_columns, keyword->column);
          assert (column);
          g_string_append_printf (clause,
                                  "%s(CAST (%s AS TEXT) %s '%s'",
                                  get_join (first_keyword, last_was_and,
                                            last_was_not),
                                  column,
                                  sql_regexp_op (),
                                  quoted_keyword);
        }
      else if (keyword->equal)
        {
          const char *filter_column;

          /* Keyword like "=example". */

          g_string_append_printf (clause,
                                  "%s(",
                                  (first_keyword
                                    ? ""
                                    : (last_was_and ? " AND " : " OR ")));

          quoted_keyword = sql_quote (keyword->string);
          if (last_was_not)
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;

                select_column = columns_select_column (select_columns,
                                                       filter_column);
                assert (select_column);

                if (keyword->type == KEYWORD_TYPE_INTEGER)
                  g_string_append_printf (clause,
                                          "%s"
                                          "(%s IS NULL"
                                          " OR CAST (%s AS NUMERIC)"
                                          " != %i)",
                                          (index ? " AND " : ""),
                                          select_column,
                                          select_column,
                                          keyword->integer_value);
                else if (keyword->type == KEYWORD_TYPE_DOUBLE)
                  g_string_append_printf (clause,
                                          "%s"
                                          "(%s IS NULL"
                                          " OR CAST (%s AS NUMERIC)"
                                          " != %f)",
                                          (index ? " AND " : ""),
                                          select_column,
                                          select_column,
                                          keyword->double_value);
                else
                  g_string_append_printf (clause,
                                          "%s"
                                          "(%s IS NULL"
                                          " OR CAST (%s AS TEXT)"
                                          " != '%s')",
                                          (index ? " AND " : ""),
                                          select_column,
                                          select_column,
                                          quoted_keyword);
              }
          else
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;

                select_column = columns_select_column (select_columns,
                                                       filter_column);
                assert (select_column);

                if (keyword->type == KEYWORD_TYPE_INTEGER)
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS NUMERIC)"
                                          " = %i",
                                          (index ? " OR " : ""),
                                          select_column,
                                          keyword->integer_value);
                else if (keyword->type == KEYWORD_TYPE_DOUBLE)
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS NUMERIC)"
                                          " = %f",
                                          (index ? " OR " : ""),
                                          select_column,
                                          keyword->double_value);
                else
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS TEXT)"
                                          " = '%s'",
                                          (index ? " OR " : ""),
                                          select_column,
                                          quoted_keyword);
              }
        }
      else
        {
          const char *filter_column;

          g_string_append_printf (clause,
                                  "%s(",
                                  (first_keyword
                                    ? ""
                                    : (last_was_and ? " AND " : " OR ")));

          quoted_keyword = sql_quote (keyword->string);
          if (last_was_not)
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;

                select_column = columns_select_column (select_columns,
                                                       filter_column);
                assert (select_column);

                g_string_append_printf (clause,
                                        "%s"
                                        "(%s IS NULL"
                                        " OR CAST (%s AS TEXT)"
                                        " NOT %s '%s%s%s')",
                                        (index ? " AND " : ""),
                                        select_column,
                                        select_column,
                                        last_was_re ? sql_regexp_op () : "LIKE",
                                        last_was_re ? "" : "%%",
                                        quoted_keyword,
                                        last_was_re ? "" : "%%");
              }
          else
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;

                select_column = columns_select_column (select_columns,
                                                       filter_column);
                assert (select_column);

                g_string_append_printf (clause,
                                        "%sCAST (%s AS TEXT)"
                                        " %s '%s%s%s'",
                                        (index ? " OR " : ""),
                                        select_column,
                                        last_was_re ? sql_regexp_op () : "LIKE",
                                        last_was_re ? "" : "%%",
                                        quoted_keyword,
                                        last_was_re ? "" : "%%");
              }
        }

      if (skip == 0)
        {
          g_string_append (clause, ")");
          first_keyword = 0;
          last_was_and = 0;
          last_was_not = 0;
          last_was_re = 0;
        }
      g_free (quoted_keyword);

      point++;
    }
  filter_free (split);

  if (order_return)
    *order_return = g_string_free (order, FALSE);

  if (max_return)
    {
      if (*max_return == -2)
        setting_value_int ("5f5a8712-8017-11e1-8556-406186ea4fc5",
                           max_return);
      else if (*max_return < 1)
        *max_return = -1;
    }

  if (strlen (clause->str))
    return g_string_free (clause, FALSE);

  g_string_free (clause, TRUE);
  return NULL;
}


/* Resources. */

/**
 * @brief Filter columns for GET iterator.
 */
#define ANON_GET_ITERATOR_FILTER_COLUMNS "uuid", \
 "created", "modified", "_owner"

/**
 * @brief Filter columns for GET iterator.
 */
#define GET_ITERATOR_FILTER_COLUMNS "uuid", "name", "comment", \
 "created", "modified", "_owner"

/**
 * @brief Columns for GET iterator, as a single string.
 *
 * @param[in]  prefix  Column prefix.
 */
#define GET_ITERATOR_COLUMNS_STRING                                \
  "id, uuid, name, comment, iso_time (creation_time),"             \
  " iso_time (modification_time), creation_time AS created,"       \
  " modification_time AS modified"

/**
 * @brief Columns for GET iterator.
 *
 * @param[in]  prefix  Column prefix.
 */
#define GET_ITERATOR_COLUMNS_PREFIX(prefix)                             \
  { prefix "id", NULL },                                                \
  { prefix "uuid", NULL },                                              \
  { prefix "name", NULL },                                              \
  { prefix "comment", NULL },                                           \
  { " iso_time (" prefix "creation_time)", NULL },                      \
  { " iso_time (" prefix "modification_time)", NULL },                  \
  { prefix "creation_time", "created" },                                \
  { prefix "modification_time", "modified" }

/**
 * @brief Columns for GET iterator.
 *
 * @param[in]  table  Table.
 */
#define GET_ITERATOR_COLUMNS(table)                                             \
  GET_ITERATOR_COLUMNS_PREFIX(""),                                              \
  {                                                                             \
    "(SELECT name FROM users AS inner_users"                                    \
    " WHERE inner_users.id = " G_STRINGIFY (table) ".owner)",                   \
    "_owner"                                                                    \
  },                                                                            \
  { "owner", NULL }

/**
 * @brief Number of columns for GET iterator.
 */
#define GET_ITERATOR_COLUMN_COUNT 10

/**
 * @brief Check whether a resource type name is valid.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
int
valid_type (const char* type)
{
  return (strcasecmp (type, "agent") == 0)
         || (strcasecmp (type, "alert") == 0)
         || (strcasecmp (type, "config") == 0)
         || (strcasecmp (type, "filter") == 0)
         || (strcasecmp (type, "group") == 0)
         || (strcasecmp (type, "info") == 0)
         || (strcasecmp (type, "lsc_credential") == 0)
         || (strcasecmp (type, "note") == 0)
         || (strcasecmp (type, "override") == 0)
         || (strcasecmp (type, "permission") == 0)
         || (strcasecmp (type, "port_list") == 0)
         || (strcasecmp (type, "report") == 0)
         || (strcasecmp (type, "report_format") == 0)
         || (strcasecmp (type, "result") == 0)
         || (strcasecmp (type, "role") == 0)
         || (strcasecmp (type, "scanner") == 0)
         || (strcasecmp (type, "schedule") == 0)
         || (strcasecmp (type, "slave") == 0)
         || (strcasecmp (type, "tag") == 0)
         || (strcasecmp (type, "target") == 0)
         || (strcasecmp (type, "task") == 0)
         || (strcasecmp (type, "user") == 0);
}

/**
 * @brief Return pretty name of type.
 *
 * @param[in]  type  Database name.
 *
 * @return 1 yes, 0 no.
 */
const char *
type_pretty_name (const char* type)
{
  if (strcasecmp (type, "agent") == 0)
    return "Agent";
  if (strcasecmp (type, "alert") == 0)
    return "Alert";
  if (strcasecmp (type, "config") == 0)
    return "Config";
  if (strcasecmp (type, "lsc_credential") == 0)
    return "Credential";
  if (strcasecmp (type, "filter") == 0)
    return "Filter";
  if (strcasecmp (type, "note") == 0)
    return "Note";
  if (strcasecmp (type, "override") == 0)
    return "Override";
  if (strcasecmp (type, "permission") == 0)
    return "Permission";
  if (strcasecmp (type, "port_list") == 0)
    return "Port List";
  if (strcasecmp (type, "report") == 0)
    return "Report";
  if (strcasecmp (type, "report_format") == 0)
    return "Report Format";
  if (strcasecmp (type, "result") == 0)
    return "Result";
  if (strcasecmp (type, "role") == 0)
    return "Role";
  if (strcasecmp (type, "scanner") == 0)
    return "Scanner";
  if (strcasecmp (type, "schedule") == 0)
    return "Schedule";
  if (strcasecmp (type, "slave") == 0)
    return "Slave";
  if (strcasecmp (type, "tag") == 0)
    return "Tag";
  if (strcasecmp (type, "target") == 0)
    return "Target";
  if (strcasecmp (type, "task") == 0)
    return "Task";
  if (strcasecmp (type, "info") == 0)
    return "SecInfo";
  return "";
}

/**
 * @brief Return DB name of type.
 *
 * @param[in]  type  Database or pretty name.
 *
 * @return Database name of type if possible, else NULL.
 */
const char *
type_db_name (const char* type)
{
  if (type == NULL)
    return NULL;

  if (valid_type (type))
    return type;

  if (strcasecmp (type, "Agent") == 0)
    return "agent";
  if (strcasecmp (type, "Alert") == 0)
    return "alert";
  if (strcasecmp (type, "Config") == 0)
    return "config";
  if (strcasecmp (type, "Credential") == 0)
    return "lsc_credential";
  if (strcasecmp (type, "Filter") == 0)
    return "filter";
  if (strcasecmp (type, "Note") == 0)
    return "note";
  if (strcasecmp (type, "Override") == 0)
    return "override";
  if (strcasecmp (type, "Permission") == 0)
    return "permission";
  if (strcasecmp (type, "Port List") == 0)
    return "port_list";
  if (strcasecmp (type, "Report") == 0)
    return "report";
  if (strcasecmp (type, "Report Format") == 0)
    return "report_format";
  if (strcasecmp (type, "Result") == 0)
    return "result";
  if (strcasecmp (type, "Role") == 0)
    return "role";
  if (strcasecmp (type, "Scanner") == 0)
    return "scanner";
  if (strcasecmp (type, "Schedule") == 0)
    return "schedule";
  if (strcasecmp (type, "Slave") == 0)
    return "slave";
  if (strcasecmp (type, "Tag") == 0)
    return "tag";
  if (strcasecmp (type, "Target") == 0)
    return "target";
  if (strcasecmp (type, "Task") == 0)
    return "task";
  if (strcasecmp (type, "SecInfo") == 0)
    return "info";
  return NULL;
}

/**
 * @brief Check whether a type has a name and comment.
 *
 * @param[in]  type          Type of resource.
 *
 * @return 1 yes, 0 no.
 */
int
type_named (const char *type)
{
  return strcasecmp (type, "note")
         && strcasecmp (type, "override");
}

/**
 * @brief Check whether a type has a comment.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
int
type_has_comment (const char *type)
{
  return strcasecmp (type, "report_format");
}

/**
 * @brief Check whether a resource type has an owner.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
int
type_owned (const char* type)
{
  return (strcasecmp (type, "info")
          && strcasecmp (type, "nvt")
          && strcasecmp (type, "cve")
          && strcasecmp (type, "cpe")
          && strcasecmp (type, "ovaldef")
          && strcasecmp (type, "cert_bund_adv")
          && strcasecmp (type, "dfn_cert_adv")
          && strcasecmp (type, "allinfo"));
}

/**
 * @brief Check whether the trash is in the real table.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_trash_in_table (const char *type)
{
  return strcasecmp (type, "task") == 0;
}

/**
 * @brief Find a resource given a UUID.
 *
 * @param[in]   type       Type of resource.
 * @param[in]   uuid       UUID of resource.
 * @param[out]  resource   Resource return, 0 if succesfully failed to find resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on error.
 */
gboolean
find_resource (const char* type, const char* uuid, resource_t* resource)
{
  gchar *quoted_uuid;
  quoted_uuid = sql_quote (uuid);
  if (user_owns_uuid (type, quoted_uuid, 0) == 0)
    {
      g_free (quoted_uuid);
      *resource = 0;
      return FALSE;
    }
  // TODO should really check type
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss WHERE uuid = '%s'%s;",
                     type,
                     quoted_uuid,
                     strcmp (type, "task") ? "" : " AND hidden < 2"))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Find a resource given a UUID and a permission.
 *
 * @param[in]   type        Type of resource.
 * @param[in]   uuid        UUID of resource.
 * @param[out]  resource    Resource return, 0 if succesfully failed to find
 *                          resource.
 * @param[in]   permission  Permission.
 * @param[in]   trash       Whether resource is in trashcan.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
gboolean
find_resource_with_permission (const char* type, const char* uuid,
                               resource_t* resource, const char *permission,
                               int trash)
{
  gchar *quoted_uuid;
  if (uuid == NULL)
    return TRUE;
  quoted_uuid = sql_quote (uuid);
  if (user_has_access_uuid (type, quoted_uuid, permission, trash) == 0)
    {
      g_free (quoted_uuid);
      *resource = 0;
      return FALSE;
    }
  // TODO should really check type
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss%s WHERE uuid = '%s'%s;",
                     type,
                     (strcmp (type, "task") && trash) ? "_trash" : "",
                     quoted_uuid,
                     strcmp (type, "task")
                      ? ""
                      : (trash ? " AND hidden = 2" : " AND hidden < 2")))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Find a resource given a name.
 *
 * @param[in]   type      Type of resource.
 * @param[in]   name      A resource name.
 * @param[out]  resource  Resource return, 0 if succesfully failed to find
 *                        resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
static gboolean
find_resource_by_name (const char* type, const char* name, resource_t *resource)
{
  gchar *quoted_name;
  quoted_name = sql_quote (name);
  // TODO should really check type
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss WHERE name = '%s'"
                     " ORDER BY id DESC;",
                     type,
                     quoted_name))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_name);
        return TRUE;
        break;
    }

  g_free (quoted_name);
  return FALSE;
}

/**
 * @brief Find a resource given a UUID and a permission.
 *
 * @param[in]   type        Type of resource.
 * @param[in]   name        Name of resource.
 * @param[out]  resource    Resource return, 0 if succesfully failed to find
 *                          resource.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
gboolean
find_resource_by_name_with_permission (const char *type, const char *name,
                                       resource_t *resource,
                                       const char *permission)
{
  gchar *quoted_name;
  assert (strcmp (type, "task"));
  if (name == NULL)
    return TRUE;
  quoted_name = sql_quote (name);
  // TODO should really check type
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss WHERE name = '%s'"
                     " ORDER BY id DESC;",
                     type,
                     quoted_name))
    {
      case 0:
        {
          gchar *uuid;

          uuid = sql_string ("SELECT uuid FROM %ss WHERE id = %llu;",
                             type, *resource);
          if (user_has_access_uuid (type, uuid, permission, 0) == 0)
            {
              g_free (uuid);
              g_free (quoted_name);
              *resource = 0;
              return FALSE;
            }
          g_free (uuid);
        }
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_name);
        return TRUE;
        break;
    }

  g_free (quoted_name);
  return FALSE;
}

/**
 * @brief Create a resource from an existing resource.
 *
 * @param[in]  type          Type of resource.
 * @param[in]  name          Name of new resource.  NULL to copy from existing.
 * @param[in]  comment       Comment on new resource.  NULL to copy from existing.
 * @param[in]  resource_id   UUID of existing resource.
 * @param[in]  columns       Extra columns in resource.
 * @param[in]  make_name_unique  When name NULL, whether to make existing name
 *                               unique.
 * @param[out] new_resource  Address for new resource, or NULL.
 * @param[out] old_resource  Address for existing resource, or NULL.
 *
 * @return 0 success, 1 resource exists already, 2 failed to find existing
 *         resource, 99 permission denied, -1 error.
 */
static int
copy_resource_lock (const char *type, const char *name, const char *comment,
                    const char *resource_id, const char *columns,
                    int make_name_unique, resource_t* new_resource,
                    resource_t *old_resource)
{
  gchar *quoted_name, *quoted_uuid, *uniquify, *command;
  int named;
  user_t owner;
  resource_t resource;
  resource_t new;

  if (resource_id == NULL)
    return -1;

  command = g_strdup_printf ("create_%s", type);
  if (user_may (command) == 0)
    {
      g_free (command);
      return 99;
    }
  g_free (command);

  command = g_strdup_printf ("get_%ss", type);
  if (find_resource_with_permission (type, resource_id, &resource, command, 0))
    {
      g_free (command);
      return -1;
    }
  g_free (command);

  if (resource == 0)
    return 2;

  if (find_user_by_name (current_credentials.username, &owner)
      || owner == 0)
    {
      return -1;
    }

  if (strcmp (type, "permission") == 0)
    {
      resource_t perm_resource;
      perm_resource = permission_resource (resource);
      if ((perm_resource == 0)
          && (user_can_everything (current_credentials.uuid) == 0))
        /* Only admins can copy permissions that apply to whole commands. */
        return 99;
    }

  named = type_named (type);

  if (named && name && *name && resource_with_name_exists (name, type, 0))
    return 1;
  if (name && *name)
    quoted_name = sql_quote (name);
  else
    quoted_name = NULL;
  quoted_uuid = sql_quote (resource_id);

  /* Copy the existing resource. */

  if (make_name_unique)
    uniquify = g_strdup_printf ("uniquify ('%s', name, %llu, '%cClone')",
                                type,
                                owner,
                                strcmp (type, "user") ? ' ' : '_');
  else
    uniquify = g_strdup ("name");
  if (named && comment && strlen (comment))
    {
      gchar *quoted_comment;
      quoted_comment = sql_nquote (comment, strlen (comment));
      sql ("INSERT INTO %ss"
           " (uuid, owner, name, comment, creation_time, modification_time%s%s)"
           " SELECT make_uuid (),"
           "        (SELECT id FROM users where users.uuid = '%s'),"
           "        %s%s%s, '%s', m_now (), m_now ()%s%s"
           " FROM %ss WHERE uuid = '%s';",
           type,
           columns ? ", " : "",
           columns ? columns : "",
           current_credentials.uuid,
           quoted_name ? "'" : "",
           quoted_name ? quoted_name : uniquify,
           quoted_name ? "'" : "",
           quoted_comment,
           columns ? ", " : "",
           columns ? columns : "",
           type,
           quoted_uuid);
      g_free (quoted_comment);
    }
  else if (named)
    sql ("INSERT INTO %ss"
         " (uuid, owner, name%s, creation_time, modification_time%s%s)"
         " SELECT make_uuid (),"
         "        (SELECT id FROM users where users.uuid = '%s'),"
         "        %s%s%s%s, m_now (), m_now ()%s%s"
         " FROM %ss WHERE uuid = '%s';",
         type,
         type_has_comment (type) ? ", comment" : "",
         columns ? ", " : "",
         columns ? columns : "",
         current_credentials.uuid,
         quoted_name ? "'" : "",
         quoted_name ? quoted_name : uniquify,
         quoted_name ? "'" : "",
         type_has_comment (type) ? ", comment" : "",
         columns ? ", " : "",
         columns ? columns : "",
         type,
         quoted_uuid);
  else
    sql ("INSERT INTO %ss"
         " (uuid, owner, creation_time, modification_time%s%s)"
         " SELECT make_uuid (), (SELECT id FROM users where users.uuid = '%s'),"
         "        m_now (), m_now ()%s%s"
         " FROM %ss WHERE uuid = '%s';",
         type,
         columns ? ", " : "",
         columns ? columns : "",
         current_credentials.uuid,
         columns ? ", " : "",
         columns ? columns : "",
         type,
         quoted_uuid);

  new = sql_last_insert_id ();

  /* Copy attached tags */
  sql ("INSERT INTO tags"
       " (uuid, owner, name, comment, creation_time, modification_time,"
       "  resource_type, resource, resource_uuid, resource_location,"
       "  active, value)"
       " SELECT make_uuid (), (SELECT id FROM users where users.uuid = '%s'),"
       "        name, comment, m_now (), m_now (),"
       "        resource_type, %llu, (SELECT uuid FROM %ss WHERE id = %llu),"
       "        resource_location, active, value"
       " FROM tags WHERE resource_type = '%s' AND resource = %llu"
       "           AND resource_location = " G_STRINGIFY (LOCATION_TABLE) ";",
       current_credentials.uuid,
       new,
       type,
       new,
       type,
       resource);

  if (new_resource)
    *new_resource = new;

  if (old_resource)
    *old_resource = resource;

  g_free (quoted_uuid);
  g_free (quoted_name);
  g_free (uniquify);
  if (sql_last_insert_id () == 0)
    return -1;
  return 0;
}

/**
 * @brief Create a resource from an existing resource.
 *
 * @param[in]  type          Type of resource.
 * @param[in]  name          Name of new resource.  NULL to copy from existing.
 * @param[in]  comment       Comment on new resource.  NULL to copy from existing.
 * @param[in]  resource_id   UUID of existing resource.
 * @param[in]  columns       Extra columns in resource.
 * @param[in]  make_name_unique  When name NULL, whether to make existing name
 *                               unique.
 * @param[out] new_resource  New resource.
 *
 * @return 0 success, 1 resource exists already, 2 failed to find existing
 *         resource, 99 permission denied, -1 error.
 */
int
copy_resource (const char *type, const char *name, const char *comment,
               const char *resource_id, const char *columns,
               int make_name_unique, resource_t* new_resource)
{
  int ret;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  ret = copy_resource_lock (type, name, comment, resource_id, columns,
                            make_name_unique, new_resource, NULL);

  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");

  return ret;
}

/**
 * @brief Get whether a resource exists.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource.
 * @param[in]  location  Location.
 *
 * @return 1 yes, 0 no, -1 error in type.
 */
int
resource_exists (const char *type, resource_t resource, int location)
{
  if (valid_db_resource_type (type) == 0)
    return -1;

  if (location == LOCATION_TABLE)
    return sql_int ("SELECT EXISTS (SELECT id FROM %ss WHERE id = %llu);",
                    type,
                    resource);
  return sql_int ("SELECT EXISTS (SELECT id FROM %ss%s WHERE id = %llu);",
                  type,
                  strcmp (type, "task") ? "_trash" : "",
                  resource);
}

/**
 * @brief Get the name of a resource.
 *
 * @param[in]  type      Type.
 * @param[in]  uuid      UUID.
 * @param[in]  location  Location.
 * @param[out] name      Name return.
 *
 * @return 0 success, 1 error in type.
 */
int
resource_name (const char *type, const char *uuid, int location, char **name)
{
  if (valid_db_resource_type (type) == 0)
    return 1;

  if (strcasecmp (type, "note") == 0)
    *name = sql_string ("SELECT 'Note for: '"
                        " || (SELECT name"
                        "     FROM nvts"
                        "     WHERE nvts.uuid = notes%s.nvt)"
                        " FROM notes%s"
                        " WHERE uuid = '%s';",
                        location == LOCATION_TABLE ? "" : "_trash",
                        location == LOCATION_TABLE ? "" : "_trash",
                        uuid);
  else if (strcasecmp (type, "override") == 0)
    *name = sql_string ("SELECT 'Override for: '"
                        " || (SELECT name"
                        "     FROM nvts"
                        "     WHERE nvts.uuid = overrides%s.nvt)"
                        " FROM overrides%s"
                        " WHERE uuid = '%s';",
                        location == LOCATION_TABLE ? "" : "_trash",
                        location == LOCATION_TABLE ? "" : "_trash",
                        uuid);
  else if (strcasecmp (type, "report") == 0)
    *name = sql_string ("SELECT (SELECT name FROM tasks WHERE id = task)"
                        " || ' - '"
                        " || (SELECT"
                        "       CASE (SELECT end_time FROM tasks"
                        "             WHERE id = task)"
                        "       WHEN 0 THEN 'N/A'"
                        "       ELSE (SELECT iso_time (end_time)"
                        "             FROM tasks WHERE id = task)"
                        "    END)"
                        " FROM reports"
                        " WHERE uuid = '%s';",
                        uuid);
  else if (strcasecmp (type, "result") == 0)
    *name = sql_string ("SELECT (SELECT name FROM tasks WHERE id = task)"
                        " || ' - '"
                        " || (SELECT name FROM nvts WHERE oid = nvt)"
                        " || ' - '"
                        " || (SELECT"
                        "       CASE (SELECT end_time FROM tasks"
                        "             WHERE id = task)"
                        "       WHEN 0 THEN 'N/A'"
                        "       ELSE (SELECT iso_time (end_time)"
                        "             FROM tasks WHERE id = task)"
                        "    END)"
                        " FROM results"
                        " WHERE uuid = '%s';",
                        uuid);
  else if (location == LOCATION_TABLE)
    *name = sql_string ("SELECT name"
                        " FROM %ss"
                        " WHERE uuid = '%s';",
                        type,
                        uuid);
  else if ((strcmp (type, "nvt"))
           && (strcmp (type, "cpe"))
           && (strcmp (type, "cve"))
           && (strcmp (type, "ovaldef"))
           && (strcmp (type, "cert_bund_adv"))
           && (strcmp (type, "dfn_cert_adv"))
           && (strcmp (type, "report"))
           && (strcmp (type, "result"))
           && (strcmp (type, "user")))
    *name = sql_string ("SELECT name"
                        " FROM %ss%s"
                        " WHERE uuid = '%s';",
                        type,
                        strcmp (type, "task") ? "_trash" : "",
                        uuid);

  return 0;
}

/**
 * @brief Initialise a GET iterator, including observed resources.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  type            Type of resource.
 * @param[in]  get             GET data.
 * @param[in]  select_columns         Columns for SQL.
 * @param[in]  trash_select_columns   Columns for SQL trash case.
 * @param[in]  filter_columns  Columns for filter.
 * @param[in]  distinct        Whether the query should be distinct.  Skipped
 *                             for trash and single resource.
 * @param[in]  extra_tables    Extra tables to join in FROM clause.
 * @param[in]  extra_where     Extra WHERE clauses.  Skipped for single
 *                             resource.
 * @param[in]  owned           Only get items owned by the current user.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1
 *         error.
 */
static int
init_get_iterator (iterator_t* iterator, const char *type,
                   const get_data_t *get, column_t *select_columns,
                   column_t *trash_select_columns,
                   const char **filter_columns, int distinct,
                   const char *extra_tables,
                   const char *extra_where, int owned)
{
  int first, max;
  gchar *clause, *order, *filter, *owned_clause;
  array_t *permissions;
  resource_t resource = 0;
  gchar *owner_filter;
  gchar *columns;

  assert (get);

  if (select_columns == NULL)
    {
      assert (0);
      return -1;
    }

  if (get->id && owned && (current_credentials.uuid == NULL))
    {
      gchar *quoted_uuid = sql_quote (get->id);
      switch (sql_int64 (&resource,
                         "SELECT id FROM %ss WHERE uuid = '%s';",
                         type, quoted_uuid))
        {
          case 0:
            break;
          case 1:        /* Too few rows in result of query. */
            g_free (quoted_uuid);
            return 1;
            break;
          default:       /* Programming error. */
            assert (0);
          case -1:
            g_free (quoted_uuid);
            return -1;
            break;
        }
      g_free (quoted_uuid);
    }
  else if (get->id && owned)
    {
      /* For now assume that the permission is "get_<type>".  Callers wishing
       * to iterate over a single resource with other permissions can use
       * uuid= in the filter (instead of passing get->id). */
      if (find_resource_with_permission (type, get->id, &resource, NULL,
                                         get->trash))
        return -1;
      if (resource == 0)
        return 1;
    }

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      if (get->filter_replacement)
        /* Replace the filter term with one given by the caller.  This is
         * used by GET_REPORTS to use the default filter with any task (when
         * given the special value of -3 in filt_id). */
        filter = g_strdup (get->filter_replacement);
      else
        filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  clause = filter_clause (type, filter ? filter : get->filter, filter_columns,
                          get->trash && trash_select_columns
                           ? trash_select_columns
                           : select_columns,
                          get->trash, &order, &first, &max, &permissions,
                          &owner_filter);

  g_free (filter);

  if (resource)
    /* Ownership test is done above by find function. */
    owned_clause = g_strdup (" t ()");
  else
    owned_clause = where_owned (type, get, owned, owner_filter, resource,
                                permissions);

  g_free (owner_filter);
  array_free (permissions);

  if (get->trash && trash_select_columns)
    columns = columns_build_select (trash_select_columns);
  else
    columns = columns_build_select (select_columns);

  if (resource && get->trash)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM %ss%s %s"
                   " WHERE id = %llu"
                   " AND %s"
                   "%s;",
                   columns,
                   type,
                   type_trash_in_table (type) ? "" : "_trash",
                   extra_tables ? extra_tables : "",
                   resource,
                   owned_clause,
                   order);
  else if (get->trash)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM %ss%s %s"
                   " WHERE"
                   "%s"
                   "%s"
                   "%s;",
                   columns,
                   type,
                   type_trash_in_table (type) ? "" : "_trash",
                   extra_tables ? extra_tables : "",
                   owned_clause,
                   extra_where ? extra_where : "",
                   order);
  else if (resource)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM %ss %s"
                   " WHERE id = %llu"
                   " AND %s"
                   "%s;",
                   columns,
                   type,
                   extra_tables ? extra_tables : "",
                   resource,
                   owned_clause,
                   order);
  else
    {
      init_iterator (iterator,
                   "%sSELECT %s"
                   " FROM %ss %s"
                   " WHERE"
                   " %s"
                   "%s%s%s%s%s"
                   " LIMIT %s OFFSET %i%s;",
                   distinct ? "SELECT DISTINCT * FROM (" : "",
                   columns,
                   type,
                   extra_tables ? extra_tables : "",
                   owned_clause,
                   clause ? " AND (" : "",
                   clause ? clause : "",
                   clause ? ")" : "",
                   extra_where ? extra_where : "",
                   order,
                   sql_select_limit (max),
                   first,
                   distinct ? ") AS subquery_for_distinct" : "");
    }

  g_free (columns);
  g_free (owned_clause);
  g_free (order);
  g_free (clause);
  return 0;
}

// FIX
column_t *
type_select_columns (const char *type, int);

/**
 * @brief Initialise a GET_AGGREGATES iterator, including observed resources.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  type            Type of resource.
 * @param[in]  get             GET data.
 * @param[in]  columns         Columns for SQL.
 * @param[in]  trash_columns   Columns for SQL trash case.
 * @param[in]  filter_columns  Columns for filter.
 * @param[in]  distinct        Whether the query should be distinct.  Skipped
 *                             for trash and single resource.
 * @param[in]  stat_column     Column to calculate statistics for.
 * @param[in]  group_column    Column to group data by.
 * @param[in]  extra_tables    Join tables.  Skipped for trash and single
 *                             resource.
 * @param[in]  extra_where     Extra WHERE clauses.  Skipped for single
 *                             resource.
 * @param[in]  no_pagination   Ignore max and first limits.
 * @param[in]  owned           Only get items owned by the current user.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter,
 *         3 invalid stat_column, 4 invalid group_column, 5 invalid type,
 *         6 trashcan not used by type, -1 error.
 */
int
init_aggregate_iterator (iterator_t* iterator, const char *type,
                         const get_data_t *get, int distinct,
                         const char *stat_column, const char *group_column,
                         const char *extra_tables, const char *extra_where)
{
  int owned;
  gchar *apply_overrides_str, *min_qod_str;
  int apply_overrides, min_qod;
  column_t *select_columns;
  gchar *columns;
  gchar *trash_columns;
  gchar *opts_table;
  const char **filter_columns;
  int first, max;
  gchar *from_table, *trash_extra, *clause, *filter_order, *filter;
  gchar *owned_clause;
  array_t *permissions;
  resource_t resource = 0;
  gchar *owner_filter;
  gchar *aggregate_select, *aggregate_group_by;
  gchar *outer_group_by;
  gchar *select_group_column, *select_stat_column;
  gchar *order;

  assert (get);

  if (get->id)
    g_warning ("%s: Called with an id parameter", __FUNCTION__);

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      if (get->filter_replacement)
        /* Replace the filter term with one given by the caller.  This is
         * used by GET_REPORTS to use the default filter with any task (when
         * given the special value of -3 in filt_id). */
        filter = g_strdup (get->filter_replacement);
      else
        filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  apply_overrides_str = filter_term_value (filter ? filter : get->filter, "apply_overrides");
  apply_overrides = apply_overrides_str ? atoi (apply_overrides_str) : 1;
  min_qod_str = filter_term_value (filter ? filter : get->filter, "min_qod");
  min_qod = min_qod_str ? atoi (min_qod_str) : MIN_QOD_DEFAULT;
  g_free (apply_overrides_str);
  g_free (min_qod_str);

  select_columns = type_select_columns (type, apply_overrides);
  columns = type_columns (type, apply_overrides);
  trash_columns = type_trash_columns (type, apply_overrides);
  filter_columns = type_filter_columns (type, apply_overrides);
  opts_table = type_opts_table (type, apply_overrides, min_qod);

  if (columns == NULL || filter_columns == NULL)
    {
      g_free (columns);
      g_free (trash_columns);
      return 5;
    }
  if (get->trash && trash_columns == NULL)
    {
      g_free (columns);
      g_free (trash_columns);
      return 6;
    }

  owned = type_owned (type);

  if (stat_column && vector_find_filter (filter_columns, stat_column) == 0)
    {
      g_free (columns);
      g_free (trash_columns);
      return 3;
    }
  if (group_column && vector_find_filter (filter_columns, group_column) == 0)
    {
      g_free (columns);
      g_free (trash_columns);
      return 4;
    }

  clause = filter_clause (type, filter ? filter : get->filter, filter_columns,
                          select_columns, get->trash, &filter_order,
                          &first, &max, &permissions, &owner_filter);

  g_free (filter);

  if (resource)
    /* Ownership test is done above by find function. */
    owned_clause = g_strdup (" 1");
  else
    owned_clause = where_owned (type, get, owned, owner_filter, resource,
                                permissions);

  if (strcasecmp (type, "TASK") == 0)
    {
      if (get->trash)
        trash_extra = g_strdup (" AND hidden = 2");
      else
        trash_extra = g_strdup (" AND hidden = 0");
    }
  else
    trash_extra = g_strdup ("");

  g_free (owner_filter);
  array_free (permissions);

  select_group_column = NULL;
  select_stat_column = NULL;

  if (group_column == NULL)
    {
      select_group_column = NULL;
    }
  else
    {
      int i = 0;
      while (select_columns[i].select != NULL)
        {
          if (strcmp (select_columns[i].select, group_column) == 0
              || (select_columns[i].filter
                  && strcmp (select_columns[i].filter, group_column) == 0))
            {
              select_group_column = g_strdup (select_columns[i].select);
              break;
            }
          i++;
        }
    }

  if (stat_column == NULL)
    select_stat_column = NULL;
  else
    {
      int i = 0;
      while (select_columns[i].select != NULL)
        {
          if (strcmp (select_columns[i].select, stat_column) == 0
              || (select_columns[i].filter
                  && strcmp (select_columns[i].filter, stat_column) == 0))
            {
              select_stat_column = g_strdup (select_columns[i].select);
              break;
            }
          i++;
        }
    }

  /* Round time fields to the next day to reduce amount of rows returned
   * This returns "pseudo-UTC" dates which are used by the GSA charts because
   *  the JavaScript Date objects do not support setting the timezone.
   */
  if (group_column && (strcmp (group_column, "created") == 0
                       || strcmp (group_column, "modified") == 0
                       || strcmp (group_column, "published") == 0))
    outer_group_by
      = g_strdup_printf ("GROUP BY CAST (strftime ('%%s',"
                         "               date(%s, 'unixepoch',"
                         "                    'localtime'),"
                         "               'localtime')"
                         "      AS INTEGER)",
                         "aggregate_group_value");
  else
    outer_group_by = g_strdup ("GROUP BY aggregate_group_value");

  order = g_strdup_printf ("ORDER BY %s ASC",
                           "aggregate_group_value");

  if (group_column && strcmp (group_column, ""))
    {
      if (stat_column && strcmp (stat_column, ""))
        aggregate_select = g_strdup_printf (" count(*) AS aggregate_count,"
                                            " min(%s) AS aggregate_min,"
                                            " max(%s) AS aggregate_max,"
                                            " avg(%s) * count(*)"
                                            "   AS aggregate_avg,"
                                            " sum(%s) AS aggregate_sum,"
                                            " %s as aggregate_group_value",
                                            select_stat_column,
                                            select_stat_column,
                                            select_stat_column,
                                            select_stat_column,
                                            select_group_column);
      else
        aggregate_select = g_strdup_printf (" count(*) AS aggregate_count,"
                                            " NULL AS aggregate_min,"
                                            " NULL AS aggregate_max,"
                                            " NULL AS aggregate_avg,"
                                            " NULL AS aggregate_sum,"
                                            " %s as aggregate_group_value",
                                            select_group_column);

      aggregate_group_by = g_strdup_printf (" GROUP BY %s",
                                            select_group_column);
    }
  else
    {
      if (stat_column && strcmp (stat_column, ""))
        aggregate_select = g_strdup_printf (" count(*) AS aggregate_count,"
                                            " min(%s) AS aggregate_min,"
                                            " max(%s) AS aggregate_max,"
                                            " avg(%s) * count(*)"
                                            "   AS aggregate_avg,"
                                            " sum(%s) AS aggregate_sum,"
                                            " NULL as aggregate_group_value",
                                            select_stat_column,
                                            select_stat_column,
                                            select_stat_column,
                                            select_stat_column);
      else
        {
          g_free (columns);
          g_free (trash_columns);
          g_free (owned_clause);
          g_free (trash_extra);
          g_free (order);
          g_free (clause);
          return -1;
        }

      aggregate_group_by = g_strdup ("");
    }

  from_table = type_table (type, get->trash);

  init_iterator (iterator,
                 "SELECT sum(aggregate_count),"
                 " min(aggregate_min), max (aggregate_max),"
                 " sum (aggregate_avg) / sum(aggregate_count),"
                 " sum (aggregate_sum), aggregate_group_value"
                 " FROM (SELECT%s %s, %s"
                 "       FROM %s%s%s"
                 "       WHERE"
                 "       %s%s"
                 "       %s%s%s%s"
                 "       %s)"
                 " %s %s;",
                 distinct ? " DISTINCT" : "",
                 aggregate_select,
                 columns,
                 from_table,
                 opts_table ? opts_table : "",
                 extra_tables ? extra_tables : "",
                 owned_clause,
                 trash_extra,
                 clause ? " AND (" : "",
                 clause ? clause : "",
                 clause ? ")" : "",
                 extra_where ? extra_where : "",
                 aggregate_group_by,
                 outer_group_by,
                 order);

  g_free (columns);
  g_free (trash_columns);
  g_free (owned_clause);
  g_free (trash_extra);
  g_free (filter_order);
  g_free (order);
  g_free (clause);
  g_free (aggregate_group_by);
  g_free (aggregate_select);
  g_free (outer_group_by);
  g_free (select_group_column);
  return 0;
}

/**
 * @brief Get the count from an aggregate iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The count of resources in the current group.
 */
int
aggregate_iterator_count (iterator_t* iterator)
{
  return sql_column_int (iterator->stmt, 0);
}

/**
 * @brief Get the minimum from an aggregate iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The minimum value in the current group.
 */
double
aggregate_iterator_min (iterator_t* iterator)
{
  return sql_column_double (iterator->stmt, 1);
}

/**
 * @brief Get the maximum from an aggregate iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum value in the current group.
 */
double
aggregate_iterator_max (iterator_t* iterator)
{
  return sql_column_double (iterator->stmt, 2);
}

/**
 * @brief Get the mean from an aggregate iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The mean value in the current group.
 */
double
aggregate_iterator_mean (iterator_t* iterator)
{
  return sql_column_double (iterator->stmt, 3);
}

/**
 * @brief Get the sum from a statistics iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The sum of values in the current group.
 */
double
aggregate_iterator_sum (iterator_t* iterator)
{
  return sql_column_double (iterator->stmt, 4);
}

/**
 * @brief Get the value of the group column from a statistics iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
aggregate_iterator_value (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = (const char*) sql_column_text (iterator->stmt, 5);
  return ret;
}

/**
 * @brief Count number of a particular resource.
 *
 * @param[in]  type              Type of resource.
 * @param[in]  get               GET params.
 * @param[in]  iterator_columns  Iterator columns.
 * @param[in]  trash_columns     Iterator columns for trashcan.
 * @param[in]  extra_columns     Extra columns.
 * @param[in]  distinct          Whether the query should be distinct.  Skipped
 *                               for trash and single resource.
 * @param[in]  extra_tables      Join tables.  Skipped for trash and single
 *                               resource.
 * @param[in]  extra_where       Extra WHERE clauses.  Skipped for trash and
 *                               single resource.
 * @param[in]  owned             Only count items owned by current user.
 *
 * @return Total number of resources in filtered set.
 */
static int
count (const char *type, const get_data_t *get, column_t *select_columns,
       column_t *trash_select_columns, const char **filter_columns, int distinct,
       const char *extra_tables, const char *extra_where, int owned)
{
  int ret;
  gchar *clause, *owned_clause, *owner_filter, *columns, *filter;
  array_t *permissions;

  assert (current_credentials.uuid);
  assert (get);

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return -1;
    }
  else
    filter = NULL;

  clause = filter_clause (type, filter ? filter : get->filter, filter_columns,
                          get->trash && trash_select_columns
                           ? trash_select_columns
                           : select_columns,
                          get->trash, NULL, NULL, NULL, &permissions,
                          &owner_filter);

  g_free (filter);

  owned_clause = where_owned (type, get, owned, owner_filter, 0, permissions);

  array_free (permissions);

  if (get->trash && trash_select_columns)
    columns = columns_build_select (trash_select_columns);
  else
    columns = columns_build_select (select_columns);

  if ((distinct == 0)
      && (extra_tables == NULL)
      && (clause == NULL)
      && (extra_where == NULL)
      && (strcmp (owned_clause, " t ()") == 0))
    ret = sql_int ("SELECT count (*) FROM %ss%s;",
                   type,
                   get->trash && strcmp (type, "task") ? "_trash" : "");
  else
    ret = sql_int ("SELECT count (%scount_id)"
                   " FROM (SELECT %ss%s.id AS count_id, %s"
                   "       FROM %ss%s%s"
                   "       WHERE %s"
                   "       %s%s%s) AS subquery;",
                   distinct ? "DISTINCT " : "",
                   type,
                   get->trash && strcmp (type, "task") ? "_trash" : "",
                   columns,
                   type,
                   get->trash && strcmp (type, "task") ? "_trash" : "",
                   extra_tables ? extra_tables : "",
                   owned_clause,
                   clause ? " AND " : "",
                   clause ? clause : "",
                   extra_where ? extra_where : "");

  g_free (columns);
  g_free (owned_clause);
  g_free (clause);
  return ret;
}

/**
 * @brief Count number of info of a given subtype with a given name.
 *
 * @param[in]  type  GET_INFO subtype.
 * @param[out] name  Name of the info item.
 *
 * @return Total number of get_info items of given type, -1 on error.
 */
int
info_name_count (const char *type, const char *name)
{
  gchar *quoted_name;
  int count;
  assert(type);
  assert(name);

  quoted_name = sql_quote (name);
  count =  sql_int ("SELECT COUNT(id)"
                    " FROM %ss"
                    " WHERE name = '%s';",
                    type,
                    quoted_name);
  g_free (quoted_name);

  return count;
}



/**
 * @brief Return the database version supported by this manager.
 *
 * @return Database version supported by this manager.
 */
int
manage_db_supported_version ()
{
  return OPENVASMD_DATABASE_VERSION;
}

/**
 * @brief Return the database version of the actual database.
 *
 * @return Database version read from database, -2 if database is empty,
 *         -1 on error.
 */
int
manage_db_version ()
{
  int number;
  char *version;

  if (manage_db_empty ())
    return -2;

  version = sql_string ("SELECT value FROM %s.meta"
                        " WHERE name = 'database_version' LIMIT 1;",
                        sql_schema ());
  if (version)
    {
      number = atoi (version);
      free (version);
      return number;
    }
  return -1;
}

/**
 * @brief Return the database version supported by this manager.
 *
 * @return Database version supported by this manager.
 */
int
manage_scap_db_supported_version ()
{
  return OPENVASMD_SCAP_DATABASE_VERSION;
}

/**
 * @brief Return the database version of the actual database.
 *
 * @return Database version read from database if possible, else -1.
 */
int
manage_scap_db_version ()
{
  if (manage_scap_loaded () == 0)
    return -1;

  int number;
  char *version = sql_string ("SELECT value FROM scap.meta"
                              " WHERE name = 'database_version' LIMIT 1;");
  if (version)
    {
      number = atoi (version);
      free (version);
      return number;
    }
  return -1;
}

/**
 * @brief Return the database version supported by this manager.
 *
 * @return Database version supported by this manager.
 */
int
manage_cert_db_supported_version ()
{
  return OPENVASMD_CERT_DATABASE_VERSION;
}

/**
 * @brief Return the database version of the actual database.
 *
 * @return Database version read from database if possible, else -1.
 */
int
manage_cert_db_version ()
{
  if (manage_cert_loaded () == 0)
    return -1;

  int number;
  char *version = sql_string ("SELECT value FROM cert.meta"
                              " WHERE name = 'database_version' LIMIT 1;");
  if (version)
    {
      number = atoi (version);
      free (version);
      return number;
    }
  return -1;
}

/**
 * @brief Returns associated name for a tcp/ip port.
 *
 * @param   number      Port number to get name for.
 * @param   protocol    Protocol type of port.
 *
 * @return  associated name for port if found, NULL otherwise.
 */
char *
manage_port_name (int number, const char *protocol)
{
  if (protocol == NULL || number <= 0 || number > 65535)
    return NULL;

  return sql_string ("SELECT name FROM port_names"
                     " WHERE number = %i AND protocol = '%s' LIMIT 1;",
                     number, protocol);
}

/**
 * @brief Returns formatted port number, protocol and iana name from
 * @brief field in "number/proto" form.
 *
 * @param   field       Number/Protocol string.
 *
 * @return  Formatted port name string, NULL if error.
 */
gchar *
port_name_formatted (const char *field)
{
  int number;
  char *protocol, *port_name;

  if (field == NULL)
    return NULL;

  protocol = g_newa (char, strlen (field));

  if (sscanf (field, "%i/%s",
              &number, protocol)
      != 2)
    return g_strdup (field);

  port_name = manage_port_name (number, protocol);
  if (port_name)
    {
      char *formatted = g_strdup_printf
                         ("%i/%s (IANA: %s)",
                          number,
                          protocol,
                          port_name);
      free (port_name);
      return formatted;
    }
  else
    return g_strdup (field);
}

/**
 * @brief Set the database version of the actual database.
 *
 * Caller must organise transaction.
 *
 * @param  version  New version number.
 */
void
set_db_version (int version)
{
  sql ("DELETE FROM %s.meta WHERE name = 'database_version';",
       sql_schema ());
  sql ("INSERT INTO %s.meta (name, value)"
       " VALUES ('database_version', '%i');",
       sql_schema (),
       version);
}


/**
 * @brief Encrypt, re-encrypt or decrypt all credentials
 *
 * All plaintext credentials in the lsc_credentials table are
 * encrypted, all already encrypted credentials are encrypted again
 * using the latest key.
 *
 * @param[in] decrypt_flag  If true decrypt all credentials.
 *
 * @return 0 success, -1 error.
 */
static int
encrypt_all_credentials (gboolean decrypt_flag)
{
  iterator_t iterator;
  unsigned long ntotal, nencrypted, nreencrypted, ndecrypted;

  init_iterator (&iterator,
                 "SELECT id, password, private_key FROM lsc_credentials");
  iterator.crypt_ctx = lsc_crypt_new ();

  sql_begin_immediate ();

  ntotal = nencrypted = nreencrypted = ndecrypted = 0;
  while (next (&iterator))
    {
      long long int rowid;
      const char *password, *privkey;

      ntotal++;
      if (!(ntotal % 10))
        g_message ("  %lu credentials so far processed", ntotal);

      rowid    = iterator_int64 (&iterator, 0);
      password = iterator_string (&iterator, 1);
      privkey  = iterator_string (&iterator, 2);

      /* If there is no password or private key, skip the row.  */
      if (!password && !privkey)
        continue;

      /* If the row is alread encrypted, retrieve the plaintext values
         first.  */
      if (privkey && !strcmp (privkey, ";;encrypted;;"))
        {
          const char *tmp = password;

          lsc_crypt_flush (iterator.crypt_ctx);
          password = lsc_crypt_get_password (iterator.crypt_ctx, tmp);
          privkey  = lsc_crypt_get_private_key (iterator.crypt_ctx, tmp);

          /* If there is no password or private key, skip the row.  */
          if (!password && !privkey)
            continue;

          nreencrypted++;
        }
      else
        {
          if (decrypt_flag)
            continue; /* Skip non-encrypted rows.  */

          nencrypted++;
        }

      if (decrypt_flag)
        {
          char *quoted_password, *quoted_privkey;

          quoted_password = sql_insert (password);
          quoted_privkey  = sql_insert (privkey);

          sql ("UPDATE lsc_credentials SET password = %s,"
               " private_key = %s,"
               " modification_time = m_now ()"
               " WHERE id = %llu;", quoted_password, quoted_privkey, rowid);
          g_free (quoted_password);
          g_free (quoted_privkey);
          ndecrypted++;
        }
      else
        {
          char *encblob;

          if (password && privkey)
            encblob = lsc_crypt_encrypt (iterator.crypt_ctx,
                                         "password", password,
                                         "private_key", privkey, NULL);
          else if (password)
            encblob = lsc_crypt_encrypt (iterator.crypt_ctx,
                                         "password", password, NULL);
          else
            encblob = lsc_crypt_encrypt (iterator.crypt_ctx,
                                         "private_key", privkey, NULL);

          if (!encblob)
            {
              sql ("ROLLBACK;");
              cleanup_iterator (&iterator);
              return -1;
            }
          sql ("UPDATE lsc_credentials SET password = '%s',"
               " private_key = ';;encrypted;;',"
               " modification_time = m_now ()"
               " WHERE id = %llu;", encblob, rowid);
          g_free (encblob);
        }
    }

  sql ("COMMIT;");

  if (decrypt_flag)
    g_message ("%lu out of %lu credentials decrypted",
               ndecrypted, ntotal);
  else
    g_message ("%lu out of %lu credentials encrypted and %lu re-encrypted",
               nencrypted, ntotal, nreencrypted);
  cleanup_iterator (&iterator);
  return 0;
}

/**
 * @brief Driver to encrypt or re-encrypt all credentials
 *
 * All plaintext credentials in the lsc_credentials table are
 * encrypted, all already encrypted credentials are encrypted again
 * using the latest key.
 *
 * @param[in] log_config    Log configuration.
 * @param[in] database      Location of manage database.
 * @param[in] decrypt_flag  If true decrypt all credentials.
 *
 * @return 0 success, -1 error,
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_encrypt_all_credentials (GSList *log_config, const gchar *database,
                                gboolean decrypt_flag)
{
  int ret;
  const gchar *db;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  ret = encrypt_all_credentials (decrypt_flag);

  cleanup_manage_process (TRUE);

  return ret;
}


/* Collation. */

/**
 * @brief Collate two message type strings.
 *
 * A lower threat is considered less than a higher threat, so Medium is
 * less than High.
 *
 * @param[in]  data     Dummy for callback.
 * @param[in]  one_len  Length of first string.
 * @param[in]  arg_one  First string.
 * @param[in]  two_len  Length of second string.
 * @param[in]  arg_two  Second string.
 *
 * @return -1, 0 or 1 if first is less than, equal to or greater than second.
 */
int
collate_message_type (void* data,
                      int one_len, const void* arg_one,
                      int two_len, const void* arg_two)
{
  const char* one = (const char*) arg_one;
  const char* two = (const char*) arg_two;

  if (strncmp (one, "Security Hole", one_len) == 0)
    {
      if (strncmp (two, "Security Hole", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Security Hole", two_len) == 0) return -1;

  if (strncmp (one, "Security Warning", one_len) == 0)
    {
      if (strncmp (two, "Security Warning", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Security Warning", two_len) == 0) return -1;

  if (strncmp (one, "Security Note", one_len) == 0)
    {
      if (strncmp (two, "Security Note", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Security Note", two_len) == 0) return -1;

  if (strncmp (one, "Log Message", one_len) == 0)
    {
      if (strncmp (two, "Log Message", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Log Message", two_len) == 0) return -1;

  if (strncmp (one, "Debug Message", one_len) == 0)
    {
      if (strncmp (two, "Debug Message", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Debug Message", two_len) == 0) return -1;

  if (strncmp (one, "Error Message", one_len) == 0)
    {
      if (strncmp (two, "Error Message", two_len) == 0)
        return 0;
      return 1;
    }
  if (strncmp (two, "Error Message", two_len) == 0) return -1;

  return strncmp (one, two, MIN (one_len, two_len));
}

/**
 * @brief Compare two number strings for collate_ip.
 *
 * @param[in]  one_arg  First string.
 * @param[in]  two_arg  Second string.
 *
 * @return -1, 0 or 1 if first is less than, equal to or greater than second.
 */
static int
collate_ip_compare (const char *one_arg, const char *two_arg)
{
  int one = atoi (one_arg);
  int two = atoi (two_arg);
  return one == two ? 0 : (one < two ? -1 : 1);
}

/**
 * @brief Collate two IP addresses.
 *
 * For example, 127.0.0.2 is less than 127.0.0.3 and 127.0.0.10.
 *
 * Only works correctly for IPv4 addresses.
 *
 * @param[in]  data     Dummy for callback.
 * @param[in]  one_len  Length of first IP (a string).
 * @param[in]  arg_one  First string.
 * @param[in]  two_len  Length of second IP (a string).
 * @param[in]  arg_two  Second string.
 *
 * @return -1, 0 or 1 if first is less than, equal to or greater than second.
 */
int
collate_ip (void* data,
            int one_len, const void* arg_one,
            int two_len, const void* arg_two)
{
  int ret, one_dot, two_dot;
  char one_a[4], one_b[4], one_c[4], one_d[4];
  char two_a[4], two_b[4], two_c[4], two_d[4];
  const char* one = (const char*) arg_one;
  const char* two = (const char*) arg_two;

  if ((sscanf (one, "%3[0-9].%3[0-9].%3[0-9].%n%3[0-9]",
               one_a, one_b, one_c, &one_dot, one_d)
       == 4)
      && (sscanf (two, "%3[0-9].%3[0-9].%3[0-9].%n%3[0-9]",
                  two_a, two_b, two_c, &two_dot, two_d)
          == 4))
    {
      int ret = collate_ip_compare (one_a, two_a);
      if (ret) return ret < 0 ? -1 : 1;

      ret = collate_ip_compare (one_b, two_b);
      if (ret) return ret < 0 ? -1 : 1;

      ret = collate_ip_compare (one_c, two_c);
      if (ret) return ret < 0 ? -1 : 1;

      /* Ensure that the last number is limited to digits in the arg. */
      one_d[one_len - one_dot] = '\0';
      two_d[two_len - two_dot] = '\0';

      ret = collate_ip_compare (one_d, two_d);
      if (ret) return ret < 0 ? -1 : 1;

      return 0;
    }

  ret = strncmp (one, two, MIN (one_len, two_len));
  return ret == 0 ? 0 : (ret < 0 ? -1 : 1);
}


/* Access control. */

/**
 * @brief Generate accessor for an SQL iterator.
 *
 * This convenience macro is used to generate an accessor returning a
 * const string pointer.
 *
 * @param[in]  name  Name of accessor.
 * @param[in]  col   Column number to access.
 */
#define DEF_ACCESS(name, col)                                     \
const char*                                                       \
name (iterator_t* iterator)                                       \
{                                                                 \
  const char *ret;                                                \
  if (iterator->done) return NULL;                                \
  ret = iterator_string (iterator, col);                          \
  return ret;                                                     \
}

/**
 * @brief Initialise a task user iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 */
void
init_task_user_iterator (iterator_t *iterator, task_t task)
{
  init_iterator (iterator,
                 "SELECT id, resource, subject,"
                 " (SELECT name FROM users"
                 "  WHERE users.id = permissions.subject)"
                 " FROM permissions"
                 /* Any permission implies 'get_tasks'. */
                 " WHERE resource_type = 'task'"
                 " AND resource = %llu"
                 " AND resource_location = " G_STRINGIFY (LOCATION_TABLE)
                 " AND subject_type = 'user'"
                 " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
                 task);
}

DEF_ACCESS (task_user_iterator_name, 3);

/**
 * @brief Initialise a task group iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 */
void
init_task_group_iterator (iterator_t *iterator, task_t task)
{
  init_iterator (iterator,
                 "SELECT DISTINCT 1, resource, subject,"
                 " (SELECT name FROM groups"
                 "  WHERE groups.id = permissions.subject),"
                 " (SELECT uuid FROM groups"
                 "  WHERE groups.id = permissions.subject)"
                 " FROM permissions"
                 /* Any permission implies 'get_tasks'. */
                 " WHERE resource_type = 'task'"
                 " AND resource = %llu"
                 " AND resource_location = " G_STRINGIFY (LOCATION_TABLE)
                 " AND subject_type = 'group'"
                 " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
                 task);
}

DEF_ACCESS (task_group_iterator_name, 3);

DEF_ACCESS (task_group_iterator_uuid, 4);

/**
 * @brief Initialise a task role iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 */
void
init_task_role_iterator (iterator_t *iterator, task_t task)
{
  init_iterator (iterator,
                 "SELECT DISTINCT 1, resource, subject,"
                 " (SELECT name FROM roles"
                 "  WHERE roles.id = permissions.subject),"
                 " (SELECT uuid FROM roles"
                 "  WHERE roles.id = permissions.subject)"
                 " FROM permissions"
                 /* Any permission implies 'get'. */
                 " WHERE resource_type = 'task'"
                 " AND resource = %llu"
                 " AND resource_location = " G_STRINGIFY (LOCATION_TABLE)
                 " AND subject_type = 'role'",
                 task);
}

DEF_ACCESS (task_role_iterator_name, 3);

DEF_ACCESS (task_role_iterator_uuid, 4);


/* Events and Alerts. */

/**
 * @brief Find a alert for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of alert.
 * @param[out]  alert       Alert return, 0 if succesfully failed to find alert.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find alert), TRUE on error.
 */
gboolean
find_alert_with_permission (const char* uuid, alert_t* alert,
                            const char *permission)
{
  return find_resource_with_permission ("alert", uuid, alert, permission, 0);
}

/**
 * @brief Validate an email address.
 *
 * @param[in]  address  Email address.
 *
 * @return 0 success, 1 failure.
 */
static int
validate_email (const char* address)
{
  gchar **split, *point;

  assert (address);

  split = g_strsplit (address, "@", 0);

  if (split[0] == NULL || split[1] == NULL || split[2])
    {
      g_strfreev (split);
      return 1;
    }

  /* Local part. */
  point = split[0];
  while (*point)
    if (isalnum (*point)
        || strchr ("!#$%&'*+-/=?^_`{|}~", *point)
        || ((*point == '.')
            && (point > split[0])
            && point[1]
            && (point[1] != '.')
            && (point[-1] != '.')))
      point++;
    else
      {
        g_strfreev (split);
        return 1;
      }

  /* Domain. */
  point = split[1];
  while (*point)
    if (isalnum (*point)
        || strchr ("-_", *point)  /* RFC actually forbids _. */
        || ((*point == '.')
            && (point > split[1])
            && point[1]
            && (point[1] != '.')
            && (point[-1] != '.')))
      point++;
    else
      {
        g_strfreev (split);
        return 1;
      }

  g_strfreev (split);
  return 0;
}

/**
 * @brief Validate an email address list.
 *
 * @param[in]  list  Comma separated list of email addresses.
 *
 * @return 0 success, 1 failure.
 */
static int
validate_email_list (const char *list)
{
  gchar **split, **point;

  assert (list);

  split = g_strsplit (list, ",", 0);

  if (split[0] == NULL)
    {
      g_strfreev (split);
      return 1;
    }

  point = split;
  while (*point)
    {
      const char *address;
      address = *point;
      while (*address && (*address == ' ')) address++;
      if (validate_email (address))
        {
          g_strfreev (split);
          return 1;
        }
      point++;
    }

  g_strfreev (split);
  return 0;
}

/**
 * @brief Validate condition data for an alert.
 *
 * @param[in]  name      Name.
 * @param[in]  data      Data to validate.
 * @param[in]  condition The condition.
 *
 * @return 0 on success, 1 unexpected data name, 2 syntax error in data,
 *         -1 internal error.
 */
static int
validate_alert_condition_data (gchar *name, gchar* data,
                               alert_condition_t condition)
{
  if (condition == ALERT_CONDITION_ALWAYS)
    return 1;
  if (condition == ALERT_CONDITION_SEVERITY_AT_LEAST)
    {
      if (strcmp (name, "severity"))
        return 1;

      if (g_regex_match_simple ("^(-1(\\.0)?|[0-9](\\.[0-9])?|10(\\.0))$",
                                data ? data : "",
                                0,
                                0)
          == 0)
        return 2;
    }
  else if (condition == ALERT_CONDITION_SEVERITY_CHANGED)
    {
      if (strcmp (name, "direction"))
        return 1;

      if (g_regex_match_simple ("^(increased|decreased|changed)$",
                                data ? data : "",
                                0,
                                0)
          == 0)
        return 2;
    }


  return 0;
}

/**
 * @brief Create an alert.
 *
 * @param[in]  name            Name of alert.
 * @param[in]  comment         Comment on alert.
 * @param[in]  filter_id       Filter.
 * @param[in]  event           Type of event.
 * @param[in]  event_data      Type-specific event data.
 * @param[in]  condition       Event condition.
 * @param[in]  condition_data  Condition-specific data.
 * @param[in]  method          Escalation method.
 * @param[in]  method_data     Data for escalation method.
 * @param[out] alert       Created alert on success.
 *
 * @return 0 success, 1 escalation exists already, 2 validation of email failed,
 *         3 failed to find filter, 4 type must be "result" if specified,
 *         5 unexpected condition data name, 6 syntax error in condition data,
 *         7 email subject too long, 8 email message too long,
 *         99 permission denied, -1 error.
 */
int
create_alert (const char* name, const char* comment, const char* filter_id,
              event_t event, GPtrArray* event_data,
              alert_condition_t condition, GPtrArray* condition_data,
              alert_method_t method, GPtrArray* method_data,
              alert_t *alert)
{
  int index;
  gchar *item, *quoted_comment;
  gchar *quoted_name;
  filter_t filter;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (user_may ("create_alert") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  filter = 0;
  if (filter_id && strcmp (filter_id, "0"))
    {
      char *type;

      if (find_filter_with_permission (filter_id, &filter, "get_filters"))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (filter == 0)
        {
          sql ("ROLLBACK;");
          return 3;
        }

      /* Filter type must be result if specified. */

      type = sql_string ("SELECT type FROM filters WHERE id = %llu;",
                         filter);
      if (type && strcasecmp (type, "result"))
        {
          free (type);
          sql ("ROLLBACK;");
          return 4;
        }
      free (type);
    }

  if (resource_with_name_exists (name, "alert", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  quoted_name = sql_quote (name);
  quoted_comment = sql_quote (comment ?: "");

  sql ("INSERT INTO alerts (uuid, owner, name, comment, event, condition,"
       " method, filter, creation_time, modification_time)"
       " VALUES (make_uuid (),"
       " (SELECT id FROM users WHERE users.uuid = '%s'),"
       " '%s', '%s', %i, %i, %i, %llu, m_now (), m_now ());",
       current_credentials.uuid,
       quoted_name,
       quoted_comment,
       event,
       condition,
       method,
       filter);

  g_free (quoted_comment);
  g_free (quoted_name);

  *alert = sql_last_insert_id ();

  index = 0;
  while ((item = (gchar*) g_ptr_array_index (condition_data, index++)))
    {
      int validation_result;
      gchar *name = sql_quote (item);
      gchar *data = sql_quote (item + strlen (item) + 1);

      validation_result = validate_alert_condition_data (name, data, condition);

      if (validation_result)
        {
          g_free (name);
          g_free (data);
          sql ("ROLLBACK;");

          switch (validation_result)
            {
              case 1:
                return 5;
              case 2:
                return 6;
              default:
                return -1;
            }
        }

      sql ("INSERT INTO alert_condition_data (alert, name, data)"
           " VALUES (%llu, '%s', '%s');",
           *alert,
           name,
           data);
      g_free (name);
      g_free (data);
    }

  index = 0;
  while ((item = (gchar*) g_ptr_array_index (event_data, index++)))
    {
      gchar *name = sql_quote (item);
      gchar *data = sql_quote (item + strlen (item) + 1);
      sql ("INSERT INTO alert_event_data (alert, name, data)"
           " VALUES (%llu, '%s', '%s');",
           *alert,
           name,
           data);
      g_free (name);
      g_free (data);
    }

  index = 0;
  while ((item = (gchar*) g_ptr_array_index (method_data, index++)))
    {
      gchar *name = sql_quote (item);
      gchar *data = sql_quote (item + strlen (item) + 1);

      if (method == ALERT_METHOD_EMAIL
          && strcmp (name, "to_address") == 0
          && validate_email_list (data))
        {
          g_free (name);
          g_free (data);
          sql ("ROLLBACK;");
          return 2;
        }

      if (method == ALERT_METHOD_EMAIL
          && strcmp (name, "from_address") == 0
          && validate_email (data))
        {
          g_free (name);
          g_free (data);
          sql ("ROLLBACK;");
          return 2;
        }

      if (method == ALERT_METHOD_EMAIL
          && strcmp (name, "subject") == 0
          && strlen (data) > 80)
        {
          g_free (name);
          g_free (data);
          sql ("ROLLBACK;");
          return 7;
        }

      if (method == ALERT_METHOD_EMAIL
          && strcmp (name, "message") == 0
          && strlen (data) > 1000)
        {
          g_free (name);
          g_free (data);
          sql ("ROLLBACK;");
          return 8;
        }

      sql ("INSERT INTO alert_method_data (alert, name, data)"
           " VALUES (%llu, '%s', '%s');",
           *alert,
           name,
           data);
      g_free (name);
      g_free (data);
    }

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create an alert from an existing alert.
 *
 * @param[in]  name          Name of new alert. NULL to copy from existing.
 * @param[in]  comment       Comment on new alert. NULL to copy from
 *                           existing.
 * @param[in]  alert_id      UUID of existing alert.
 * @param[out] new_alert     New alert.
 *
 * @return 0 success, 1 alert exists already, 2 failed to find existing
 *         alert, 99 permission denied, -1 error.
 */
int
copy_alert (const char* name, const char* comment, const char* alert_id,
            alert_t* new_alert)
{
  int ret;
  alert_t new, old;

  assert (current_credentials.uuid);

  if (alert_id == NULL)
    return -1;

  sql_begin_immediate ();

  ret = copy_resource_lock ("alert", name, comment, alert_id,
                            "event, condition, method, filter",
                            1, &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  /* Copy the alert condition data */
  sql ("INSERT INTO alert_condition_data (alert, name, data)"
       " SELECT %llu, name, data FROM alert_condition_data"
       "  WHERE alert = %llu;",
       new,
       old);

  /* Copy the alert event data */
  sql ("INSERT INTO alert_event_data (alert, name, data)"
       " SELECT %llu, name, data FROM alert_event_data"
       "  WHERE alert = %llu;",
       new,
       old);

  /* Copy the alert method data */
  sql ("INSERT INTO alert_method_data (alert, name, data)"
       " SELECT %llu, name, data FROM alert_method_data"
       "  WHERE alert = %llu;",
       new,
       old);

  sql ("COMMIT;");
  if (new_alert) *new_alert = new;
  return 0;
}

/**
 * @brief Modify an alert.
 *
 * @param[in]   alert_id        UUID of alert.
 * @param[in]   name            Name of alert.
 * @param[in]   comment         Comment on alert.
 * @param[in]   filter_id       Filter.
 * @param[in]   event           Type of event.
 * @param[in]   event_data      Type-specific event data.
 * @param[in]   condition       Event condition.
 * @param[in]   condition_data  Condition-specific data.
 * @param[in]   method          Escalation method.
 * @param[in]   method_data     Data for escalation method.
 *
 * @return 0 success, 1 failed to find alert, 2 alert with new name exists,
 *         3 alert_id required, 4 failed to find filter, 5 filter type must be
 *         result if specified, 6 Provided email address not valid,
 *         7 unexpected condition data name, 8 syntax error in condition data,
 *         9 email subject too long, 10 email message too long,
 *         99 permission denied, -1 internal error.
 */
int
modify_alert (const char *alert_id, const char *name, const char *comment,
              const char *filter_id, event_t event, GPtrArray *event_data,
              alert_condition_t condition, GPtrArray *condition_data,
              alert_method_t method, GPtrArray *method_data)
{
  int index;
  gchar *quoted_name, *quoted_comment, *item;
  alert_t alert;
  filter_t filter;

  if (alert_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_alert") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  alert = 0;
  if (find_alert_with_permission (alert_id, &alert, "modify_alert"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (alert == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether an alert with the same name exists already. */
  if (resource_with_name_exists (name, "alert", alert))
    {
      sql ("ROLLBACK;");
      return 2;
    }

  /* Check filter. */
  filter = 0;
  if (filter_id && strcmp (filter_id, "0"))
    {
      char *type;

      if (find_filter_with_permission (filter_id, &filter, "get_filters"))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (filter == 0)
        {
          sql ("ROLLBACK;");
          return 4;
        }

      /* Filter type must be report if specified. */

      type = sql_string ("SELECT type FROM filters WHERE id = %llu;",
                         filter);
      if (type && strcasecmp (type, "result"))
        {
          free (type);
          sql ("ROLLBACK;");
          return 5;
        }
      free (type);
    }

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ? comment : "");

  sql ("UPDATE alerts SET"
       " name = '%s',"
       " comment = '%s',"
       " filter = %llu,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       filter,
       alert);

  g_free (quoted_comment);
  g_free (quoted_name);

  /* Modify alert event */
  if (event != EVENT_ERROR)
    {
      sql ("UPDATE alerts set event = %i WHERE id = %llu", event, alert);
      sql ("DELETE FROM alert_event_data WHERE alert = %llu", alert);
      index = 0;
      while ((item = (gchar*) g_ptr_array_index (event_data, index++)))
        {
          gchar *name = sql_quote (item);
          gchar *data = sql_quote (item + strlen (item) + 1);
          sql ("INSERT INTO alert_event_data (alert, name, data)"
               " VALUES (%llu, '%s', '%s');",
               alert,
               name,
               data);
          g_free (name);
          g_free (data);
        }
    }

  /* Modify alert condition */
  if (condition != ALERT_CONDITION_ERROR)
    {
      sql ("UPDATE alerts set condition = %i WHERE id = %llu",
           condition,
           alert);
      sql ("DELETE FROM alert_condition_data WHERE alert = %llu", alert);
      index = 0;
      while ((item = (gchar*) g_ptr_array_index (condition_data, index++)))
        {
          int validation_result;
          gchar *name = sql_quote (item);
          gchar *data = sql_quote (item + strlen (item) + 1);

          validation_result = validate_alert_condition_data (name, data,
                                                             condition);

          if (validation_result)
            {
              g_free (name);
              g_free (data);
              sql ("ROLLBACK;");

              switch (validation_result)
                {
                  case 1:
                    return 7;
                  case 2:
                    return 8;
                  default:
                    return -1;
                }
            }

          sql ("INSERT INTO alert_condition_data (alert, name, data)"
               " VALUES (%llu, '%s', '%s');",
               alert,
               name,
               data);
          g_free (name);
          g_free (data);
        }
    }

  /* Modify alert method */
  if (method != ALERT_METHOD_ERROR)
    {
      sql ("UPDATE alerts set method = %i WHERE id = %llu", method, alert);
      sql ("DELETE FROM alert_method_data WHERE alert = %llu", alert);
      index = 0;
      while ((item = (gchar*) g_ptr_array_index (method_data, index++)))
        {
          gchar *name = sql_quote (item);
          gchar *data = sql_quote (item + strlen (item) + 1);

          if (method == ALERT_METHOD_EMAIL
              && strcmp (name, "to_address") == 0
              && validate_email_list (data))
            {
              g_free (name);
              g_free (data);
              sql ("ROLLBACK;");
              return 6;
            }

          if (method == ALERT_METHOD_EMAIL
              && strcmp (name, "from_address") == 0
              && validate_email (data))
            {
              g_free (name);
              g_free (data);
              sql ("ROLLBACK;");
              return 6;
            }

          if (method == ALERT_METHOD_EMAIL
              && strcmp (name, "subject") == 0
              && strlen (data) > 80)
            {
              g_free (name);
              g_free (data);
              sql ("ROLLBACK;");
              return 9;
            }

          if (method == ALERT_METHOD_EMAIL
              && strcmp (name, "message") == 0
              && strlen (data) > 1000)
            {
              g_free (name);
              g_free (data);
              sql ("ROLLBACK;");
              return 10;
            }

          sql ("INSERT INTO alert_method_data (alert, name, data)"
               " VALUES (%llu, '%s', '%s');",
               alert,
               name,
               data);
          g_free (name);
          g_free (data);
        }
    }

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete an alert.
 *
 * @param[in]  alert_id  UUID of alert.
 * @param[in]  ultimate      Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the alert, 2 failed
 *         to find target, 99 permission denied, -1 error.
 */
int
delete_alert (const char *alert_id, int ultimate)
{
  alert_t alert = 0;

  sql_begin_immediate ();

  if (user_may ("delete_alert") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_alert_with_permission (alert_id, &alert, "delete_alert"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (alert == 0)
    {
      if (find_trash ("alert", alert_id, &alert))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (alert == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM task_alerts"
                   " WHERE alert = %llu"
                   " AND alert_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   alert))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("alert", alert, LOCATION_TRASH);
      tags_set_orphans ("alert", alert, LOCATION_TRASH);

      sql ("DELETE FROM alert_condition_data_trash WHERE alert = %llu;",
           alert);
      sql ("DELETE FROM alert_event_data_trash WHERE alert = %llu;",
           alert);
      sql ("DELETE FROM alert_method_data_trash WHERE alert = %llu;",
           alert);
      sql ("DELETE FROM alerts_trash WHERE id = %llu;", alert);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      alert_t trash_alert;

      if (sql_int ("SELECT count(*) FROM task_alerts"
                   " WHERE alert = %llu"
                   " AND alert_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (SELECT hidden < 2 FROM tasks"
                   "      WHERE id = task_alerts.task);",
                   alert))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO alerts_trash"
           " (uuid, owner, name, comment, event, condition, method, filter,"
           "  filter_location, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, event, condition, method,"
           "        filter, " G_STRINGIFY (LOCATION_TABLE) ", creation_time,"
           "        m_now ()"
           " FROM alerts WHERE id = %llu;",
           alert);

      trash_alert = sql_last_insert_id ();

      sql ("INSERT INTO alert_condition_data_trash"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_condition_data WHERE alert = %llu;",
           trash_alert,
           alert);

      sql ("INSERT INTO alert_event_data_trash"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_event_data WHERE alert = %llu;",
           trash_alert,
           alert);

      sql ("INSERT INTO alert_method_data_trash"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_method_data WHERE alert = %llu;",
           trash_alert,
           alert);

      /* Update the location of the alert in any trashcan tasks. */
      sql ("UPDATE task_alerts"
           " SET alert = %llu,"
           "     alert_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE alert = %llu"
           " AND alert_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_alert,
           alert);

      permissions_set_locations ("alert", alert, trash_alert,
                                 LOCATION_TRASH);
      tags_set_locations ("alert", alert, trash_alert,
                          LOCATION_TRASH);
    }
  else if (sql_int ("SELECT count(*) FROM task_alerts"
                    " WHERE alert = %llu"
                    " AND alert_location = " G_STRINGIFY (LOCATION_TABLE) ";",
                    alert))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  else
    {
      permissions_set_orphans ("alert", alert, LOCATION_TABLE);
      tags_set_orphans ("alert", alert, LOCATION_TABLE);
    }

  sql ("DELETE FROM alert_condition_data WHERE alert = %llu;",
       alert);
  sql ("DELETE FROM alert_event_data WHERE alert = %llu;", alert);
  sql ("DELETE FROM alert_method_data WHERE alert = %llu;", alert);
  sql ("DELETE FROM alerts WHERE id = %llu;", alert);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return the UUID of an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return UUID of alert.
 */
char *
alert_uuid (alert_t alert)
{
  return sql_string ("SELECT uuid FROM alerts WHERE id = %llu;",
                     alert);
}

/**
 * @brief Return the UUID of the filter of an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return UUID if there's a filter, else NULL.
 */
char *
alert_filter_id (alert_t alert)
{
  return sql_string ("SELECT"
                     " (CASE WHEN (SELECT filter IS NULL OR filter = 0"
                     "             FROM alerts WHERE id = %llu)"
                     "  THEN NULL"
                     "  ELSE (SELECT uuid FROM filters"
                     "        WHERE id = (SELECT filter FROM alerts"
                     "                    WHERE id = %llu))"
                     "  END);",
                     alert,
                     alert);
}

/**
 * @brief Return the condition associated with an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return Condition.
 */
static alert_condition_t
alert_condition (alert_t alert)
{
  return sql_int ("SELECT condition FROM alerts WHERE id = %llu;",
                  alert);
}

/**
 * @brief Return the method associated with an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return Method.
 */
static alert_method_t
alert_method (alert_t alert)
{
  return sql_int ("SELECT method FROM alerts WHERE id = %llu;",
                  alert);
}

/**
 * @brief Filter columns for alert iterator.
 */
#define ALERT_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, "event", "condition", "method",               \
   "filter",  NULL }

/**
 * @brief Alert iterator columns.
 */
#define ALERT_ITERATOR_COLUMNS                                                \
 {                                                                            \
   GET_ITERATOR_COLUMNS (alerts),                                             \
   { "event", NULL },                                                         \
   { "condition", NULL},                                                      \
   { "method", NULL },                                                        \
   { "filter", NULL},                                                         \
   { G_STRINGIFY (LOCATION_TABLE), NULL},                                     \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Alert iterator columns for trash case.
 */
#define ALERT_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                            \
   GET_ITERATOR_COLUMNS (alerts_trash),                                       \
   { "event", NULL },                                                         \
   { "condition", NULL},                                                      \
   { "method", NULL },                                                        \
   { "filter", NULL},                                                         \
   { "filter_location", NULL},                                                \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Count the number of alerts.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of alerts filtered set.
 */
int
alert_count (const get_data_t *get)
{
  static const char *filter_columns[] = ALERT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ALERT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ALERT_ITERATOR_TRASH_COLUMNS;
  return count ("alert", get, columns, trash_columns, filter_columns, 0, 0, 0,
                  TRUE);
}

/**
 * @brief Return whether a alert is in use by a task.
 *
 * @param[in]  alert  Alert.
 *
 * @return 1 if in use, else 0.
 */
int
alert_in_use (alert_t alert)
{
  return !!sql_int ("SELECT count (*) FROM task_alerts WHERE alert = %llu;",
                    alert);
}

/**
 * @brief Return whether a trashcan alert is in use by a task.
 *
 * @param[in]  alert  Alert.
 *
 * @return 1 if in use, else 0.
 */
int
trash_alert_in_use (alert_t alert)
{
  return !!sql_int ("SELECT count(*) FROM task_alerts"
                    " WHERE alert = %llu"
                    " AND alert_location = " G_STRINGIFY (LOCATION_TRASH),
                    alert);
}

/**
 * @brief Return whether a alert is writable.
 *
 * @param[in]  alert  Alert.
 *
 * @return 1 if writable, else 0.
 */
int
alert_writable (alert_t alert)
{
    return 1;
}

/**
 * @brief Return whether a trashcan alert is writable.
 *
 * @param[in]  alert  Alert.
 *
 * @return 1 if writable, else 0.
 */
int
trash_alert_writable (alert_t alert)
{
    return 1;
}

/**
 * @brief Initialise an alert iterator, including observed alerts.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find alert, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_alert_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = ALERT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ALERT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ALERT_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "alert",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Return the event from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Event of the alert or NULL if iteration is complete.
 */
int
alert_iterator_event (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT);
  return ret;
}

/**
 * @brief Return the condition from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Condition of the alert or NULL if iteration is complete.
 */
int
alert_iterator_condition (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  return ret;
}

/**
 * @brief Return the method from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Method of the alert or NULL if iteration is complete.
 */
int
alert_iterator_method (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
  return ret;
}

/**
 * @brief Return the filter from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Filter of the alert or NULL if iteration is complete.
 */
filter_t
alert_iterator_filter (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return (filter_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
}

/**
 * @brief Return the filter UUID from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID of filter of the alert or NULL if iteration is complete.
 */
char *
alert_iterator_filter_uuid (iterator_t* iterator)
{
  filter_t filter;

  if (iterator->done) return NULL;

  filter = alert_iterator_filter (iterator);
  if (filter)
    {
      if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4)
          == LOCATION_TABLE)
        return filter_uuid (filter);
      return trash_filter_uuid (filter);
    }
  return NULL;
}

/**
 * @brief Return the filter name from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of filter of the alert or NULL if iteration is complete.
 */
char *
alert_iterator_filter_name (iterator_t* iterator)
{
  filter_t filter;

  if (iterator->done) return NULL;

  filter = alert_iterator_filter (iterator);
  if (filter)
    {
      if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4)
          == LOCATION_TABLE)
        return filter_name (filter);
      return trash_filter_name (filter);
    }
  return NULL;
}

/**
 * @brief Return the location of an alert iterator filter.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 0 in table, 1 in trash.
 */
int
alert_iterator_filter_trash (iterator_t* iterator)
{
  if (iterator->done) return 0;
  if (alert_iterator_filter (iterator)
      && (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4)
          == LOCATION_TRASH))
    return 1;
  return 0;
}

/**
 * @brief Return the filter readable state from an alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether filter is readable.
 */
int
alert_iterator_filter_readable (iterator_t* iterator)
{
  filter_t filter;

  if (iterator->done) return 0;

  filter = alert_iterator_filter (iterator);
  if (filter)
    {
      char *uuid;
      uuid = alert_iterator_filter_uuid (iterator);
      if (uuid)
        {
          int readable;
          readable = user_has_access_uuid
                      ("filter", uuid, "get_filters",
                       iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4)
                       == LOCATION_TRASH);
          free (uuid);
          return readable;
        }
    }
  return 0;
}

/**
 * @brief Initialise an alert data iterator.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  alert  Alert.
 * @param[in]  trash      Whether to iterate over trashcan alert data.
 * @param[in]  table      Type of data: "condition", "event" or "method",
 *                        corresponds to substring of the table to select
 *                        from.
 */
void
init_alert_data_iterator (iterator_t *iterator, alert_t alert,
                              int trash, const char *table)
{
  init_iterator (iterator,
                 "SELECT name, data FROM alert_%s_data%s"
                 " WHERE alert = %llu;",
                 table,
                 trash ? "_trash" : "",
                 alert);
}

/**
 * @brief Return the name from an alert data iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of the alert data or NULL if iteration is complete.
 */
const char*
alert_data_iterator_name (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 0);
  return ret;
}

/**
 * @brief Return the data from an alert data iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 *
 * @return Data of the alert data or NULL if iteration is complete.
 */
const char*
alert_data_iterator_data (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 1);
  return ret;
}

/**
 * @brief Return data associated with an alert.
 *
 * @param[in]  alert  Alert.
 * @param[in]  type       Type of data: "condition", "event" or "method".
 * @param[in]  name       Name of the data.
 *
 * @return Freshly allocated data if it exists, else NULL.
 */
char *
alert_data (alert_t alert, const char *type, const char *name)
{
  gchar *quoted_name;
  char *data;

  assert (strcmp (type, "condition") == 0
          || strcmp (type, "event") == 0
          || strcmp (type, "method") == 0);

  quoted_name = sql_quote (name);
  data = sql_string ("SELECT data FROM alert_%s_data"
                     " WHERE alert = %llu AND name = '%s';",
                     type,
                     alert,
                     quoted_name);
  g_free (quoted_name);
  return data;
}

/**
 * @brief Initialise a task alert iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 * @param[in]  event     Event.
 */
void
init_task_alert_iterator (iterator_t* iterator, task_t task, event_t event)
{
  gchar *owned_clause;
  get_data_t get;
  array_t *permissions;

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_alerts"));
  owned_clause = where_owned ("alert", &get, 0, "any", 0, permissions);
  array_free (permissions);

  if (event)
    init_iterator (iterator,
                   "SELECT alerts.id, alerts.uuid, alerts.name"
                   " FROM alerts, task_alerts"
                   " WHERE task_alerts.task = %llu AND event = %i"
                   " AND task_alerts.alert = alerts.id"
                   " AND %s;",
                   task,
                   event,
                   owned_clause);
  else
    init_iterator (iterator,
                   "SELECT alerts.id, alerts.uuid, alerts.name"
                   " FROM alerts, task_alerts"
                   " WHERE task_alerts.task = %llu"
                   " AND task_alerts.alert = alerts.id"
                   " AND %s;",
                   task,
                   owned_clause);

  g_free (owned_clause);
}

/**
 * @brief Get the alert from a task alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return alert.
 */
alert_t
task_alert_iterator_alert (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the UUID from a task alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_alert_iterator_uuid, 1);

/**
 * @brief Get the name from a task alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_alert_iterator_name, 2);

/**
 * @brief Send an email.
 *
 * @param[in]  to_address    Address to send to.
 * @param[in]  from_address  Address to send to.
 * @param[in]  subject       Subject of email.
 * @param[in]  body          Body of email.
 * @param[in]  attachment    Attachment in line broken base64, or NULL.
 * @param[in]  attachment_type  Attachment MIME type, or NULL.
 * @param[in]  attachment_name  Base file name of the attachment, or NULL.
 * @param[in]  attachment_extension  Attachment file extension, or NULL.
 *
 * @return 0 success, -1 error.
 */
static int
email (const char *to_address, const char *from_address, const char *subject,
       const char *body, const gchar *attachment, const char *attachment_type,
       const char *attachment_name, const char *attachment_extension)
{
  int ret, content_fd, to_fd;
  gchar *command;
  GError *error = NULL;
  char content_file_name[] = "/tmp/openvasmd-content-XXXXXX";
  char to_file_name[] = "/tmp/openvasmd-to-XXXXXX";
  FILE *content_file;

  content_fd = mkstemp (content_file_name);
  if (content_fd == -1)
    {
      g_warning ("%s: mkstemp: %s\n", __FUNCTION__, strerror (errno));
      return -1;
    }

  tracef ("   EMAIL to %s from %s subject: %s, body: %s",
          to_address, from_address, subject, body);

  content_file = fdopen (content_fd, "w");
  if (content_file == NULL)
    {
      g_warning ("%s: %s", __FUNCTION__, strerror (errno));
      close (content_fd);
      return -1;
    }

  if (fprintf (content_file,
               "To: %s\n"
               "From: %s\n"
               "Subject: %s\n"
               "%s%s%s"
               "\n"
               "%s"
               "%s\n",
               to_address,
               from_address ? from_address
                            : "automated@openvas.org",
               subject,
               (attachment
                 ? "MIME-Version: 1.0\n"
                   "Content-Type: multipart/mixed;"
                   " boundary=\""
                 : ""),
               /* @todo Future callers may give email containing this string. */
               (attachment ? "=-=-=-=-=" : ""),
               (attachment ? "\"\n" : ""),
               (attachment ? "--=-=-=-=-=\n"
                             "Content-Type: text/plain; charset=utf-8\n"
                             "Content-Transfer-Encoding: 8bit\n"
                             "Content-Disposition: inline\n"
                             "\n"
                           : ""),
               body)
      < 0)
    {
      g_warning ("%s: output error", __FUNCTION__);
      fclose (content_file);
      return -1;
    }

  if (attachment)
    {
      int len;

      if (fprintf (content_file,
                   "--=-=-=-=-=\n"
                   "Content-Type: %s\n"
                   "Content-Disposition: attachment;"
                   " filename=\"%s.%s\"\n"
                   "Content-Transfer-Encoding: base64\n"
                   "Content-Description: OpenVAS report\n\n",
                   attachment_type,
                   attachment_name,
                   attachment_extension)
          < 0)
        {
          g_warning ("%s: output error", __FUNCTION__);
          fclose (content_file);
          return -1;
        }

      len = strlen (attachment);
      while (len)
        if (len > 72)
          {
            if (fprintf (content_file,
                         "%.*s\n",
                         72,
                         attachment)
                < 0)
              {
                g_warning ("%s: output error", __FUNCTION__);
                fclose (content_file);
                return -1;
              }
            attachment += 72;
            len -= 72;
          }
        else
          {
            if (fprintf (content_file,
                         "%s\n",
                         attachment)
                < 0)
              {
                g_warning ("%s: output error", __FUNCTION__);
                fclose (content_file);
                return -1;
              }
            break;
          }

      if (fprintf (content_file,
                   "--=-=-=-=-=--\n")
          < 0)
        {
          g_warning ("%s: output error", __FUNCTION__);
          fclose (content_file);
          return -1;
        }
    }

  while (fflush (content_file))
    if (errno == EINTR)
      continue;
    else
      {
        g_warning ("%s", strerror (errno));
        fclose (content_file);
        return -1;
      }

  to_fd = mkstemp (to_file_name);
  if (to_fd == -1)
    {
      g_warning ("%s: mkstemp: %s\n", __FUNCTION__, strerror (errno));
      fclose (content_file);
      return -1;
    }

  g_file_set_contents (to_file_name, to_address, strlen (to_address), &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      fclose (content_file);
      close (to_fd);
      return -1;
    }

  command = g_strdup_printf ("xargs -a %s -I XXX"
                             " /usr/sbin/sendmail XXX < %s"
                             " > /dev/null 2>&1",
                             to_file_name,
                             content_file_name);

  tracef ("   command: %s\n", command);

  ret = system (command);
  if ((ret == -1) || WEXITSTATUS (ret))
    {
      g_warning ("%s: system failed with ret %i, %i, %s\n",
                 __FUNCTION__,
                 ret,
                 WEXITSTATUS (ret),
                 command);
      g_free (command);
      fclose (content_file);
      close (to_fd);
      unlink (content_file_name);
      unlink (to_file_name);
      return -1;
    }
  g_free (command);
  fclose (content_file);
  close (to_fd);
  unlink (content_file_name);
  unlink (to_file_name);
  return 0;
}

/**
 * @brief GET an HTTP resource.
 *
 * @param[in]  url  URL.
 *
 * @return 0 success, -1 error.
 */
static int
http_get (const char *url)
{
  int ret;
  gchar *standard_out = NULL;
  gchar *standard_err = NULL;
  gint exit_status;
  gchar **cmd;

  tracef ("   HTTP_GET %s", url);

  cmd = (gchar **) g_malloc (5 * sizeof (gchar *));
  cmd[0] = g_strdup ("/usr/bin/wget");
  cmd[1] = g_strdup ("-O");
  cmd[2] = g_strdup ("-");
  cmd[3] = g_strdup (url);
  cmd[4] = NULL;
  g_debug ("%s: Spawning in /tmp/: %s %s %s %s\n",
           __FUNCTION__, cmd[0], cmd[1], cmd[2], cmd[3]);
  if ((g_spawn_sync ("/tmp/",
                     cmd,
                     NULL,                  /* Environment. */
                     G_SPAWN_SEARCH_PATH,
                     NULL,                  /* Setup function. */
                     NULL,
                     &standard_out,
                     &standard_err,
                     &exit_status,
                     NULL)
       == FALSE)
      || (WIFEXITED (exit_status) == 0)
      || WEXITSTATUS (exit_status))
    {
      g_debug ("%s: wget failed: %d (WIF %i, WEX %i)",
               __FUNCTION__,
               exit_status,
               WIFEXITED (exit_status),
               WEXITSTATUS (exit_status));
      g_debug ("%s: stdout: %s\n", __FUNCTION__, standard_out);
      g_debug ("%s: stderr: %s\n", __FUNCTION__, standard_err);
      ret = -1;
    }
  else
    {
      if (strlen (standard_out) > 80)
        standard_out[80] = '\0';
      g_debug ("   HTTP_GET %s: %s", url, standard_out);
      ret = 0;
    }

  g_free (cmd[0]);
  g_free (cmd[1]);
  g_free (cmd[2]);
  g_free (cmd[3]);
  g_free (cmd[4]);
  g_free (cmd);
  g_free (standard_out);
  g_free (standard_err);
  return ret;
}

/**
 * @brief Send a report to a Sourcefire Defense Center.
 *
 * @param[in]  ip         IP of center.
 * @param[in]  port       Port of center.
 * @param[in]  pkcs12_64  PKCS12 content in base64.
 * @param[in]  report     Report in "Sourcefire" format.
 *
 * @return 0 success, -1 error.
 */
static int
send_to_sourcefire (const char *ip, const char *port, const char *pkcs12_64,
                    const char *report)
{
  gchar *script, *script_dir;
  gchar *report_file, *pkcs12_file, *pkcs12;
  gchar *clean_ip, *clean_port;
  char report_dir[] = "/tmp/openvasmd_escalate_XXXXXX";
  GError *error;
  gsize pkcs12_len;

  if ((report == NULL) || (ip == NULL) || (port == NULL))
    return -1;

  tracef ("send to sourcefire: %s:%s", ip, port);
  tracef ("report: %s", report);

  /* Setup files. */

  if (mkdtemp (report_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
      return -1;
    }

  report_file = g_strdup_printf ("%s/report.csv", report_dir);

  error = NULL;
  g_file_set_contents (report_file, report, strlen (report), &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      g_free (report_file);
      return -1;
    }

  pkcs12_file = g_strdup_printf ("%s/pkcs12", report_dir);

  if (strlen (pkcs12_64))
    pkcs12 = (gchar*) g_base64_decode (pkcs12_64, &pkcs12_len);
  else
    {
      pkcs12 = g_strdup ("");
      pkcs12_len = 0;
    }

  error = NULL;
  g_file_set_contents (pkcs12_file, pkcs12, pkcs12_len, &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      g_free (report_file);
      g_free (pkcs12_file);
      return -1;
    }

  /* Setup file names. */

  script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                 "openvasmd",
                                 "global_alert_methods",
                                 "cd1f5a34-6bdc-11e0-9827-002264764cea",
                                 NULL);

  script = g_build_filename (script_dir, "alert", NULL);

  if (!g_file_test (script, G_FILE_TEST_EXISTS))
    {
      g_free (report_file);
      g_free (pkcs12_file);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

  {
    gchar *command;
    char *previous_dir;
    int ret;

    /* Change into the script directory. */

    /** @todo NULL arg is glibc extension. */
    previous_dir = getcwd (NULL, 0);
    if (previous_dir == NULL)
      {
        g_warning ("%s: Failed to getcwd: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (report_file);
        g_free (pkcs12_file);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }

    if (chdir (script_dir))
      {
        g_warning ("%s: Failed to chdir: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (report_file);
        g_free (pkcs12_file);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }
    g_free (script_dir);

    /* Call the script. */

    clean_ip = g_shell_quote (ip);
    clean_port = g_shell_quote (port);

    command = g_strdup_printf ("%s %s %s %s %s > /dev/null"
                               " 2> /dev/null",
                               script,
                               clean_ip,
                               clean_port,
                               pkcs12_file,
                               report_file);
    g_free (script);
    g_free (clean_ip);
    g_free (clean_port);

    g_debug ("   command: %s\n", command);

    if (getuid () == 0)
      {
        pid_t pid;
        struct passwd *nobody;

        /* Run the command with lower privileges in a fork. */

        nobody = getpwnam ("nobody");
        if ((nobody == NULL)
            || chown (report_dir, nobody->pw_uid, nobody->pw_gid)
            || chown (report_file, nobody->pw_uid, nobody->pw_gid)
            || chown (pkcs12_file, nobody->pw_uid, nobody->pw_gid))
          {
            g_warning ("%s: Failed to set permissions for user nobody: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            g_free (report_file);
            g_free (pkcs12_file);
            g_free (previous_dir);
            return -1;
          }
        g_free (report_file);
        g_free (pkcs12_file);

        pid = fork ();
        switch (pid)
          {
          case 0:
              {
                /* Child.  Drop privileges, run command, exit. */
                cleanup_manage_process (FALSE);

                if (setgroups (0,NULL))
                  {
                    g_warning ("%s (child): setgroups: %s\n",
                               __FUNCTION__, strerror (errno));
                    exit (EXIT_FAILURE);
                  }
                if (setgid (nobody->pw_gid))
                  {
                    g_warning ("%s (child): setgid: %s\n",
                               __FUNCTION__,
                               strerror (errno));
                    exit (EXIT_FAILURE);
                  }
                if (setuid (nobody->pw_uid))
                  {
                    g_warning ("%s (child): setuid: %s\n",
                               __FUNCTION__,
                               strerror (errno));
                    exit (EXIT_FAILURE);
                  }

                /* RATS: ignore, command is defined above. */
                ret = system (command);
                /* Ignore the shell command exit status, because we've not
                 * specified what it must be in the past. */
                if (ret == -1)
                  {
                    g_warning ("%s (child):"
                               " system failed with ret %i, %i, %s\n",
                               __FUNCTION__,
                               ret,
                               WEXITSTATUS (ret),
                               command);
                    exit (EXIT_FAILURE);
                  }

                exit (EXIT_SUCCESS);
              }

          case -1:
            /* Parent when error. */

            g_warning ("%s: Failed to fork: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            if (chdir (previous_dir))
              g_warning ("%s: and chdir failed\n",
                         __FUNCTION__);
            g_free (previous_dir);
            g_free (command);
            return -1;
            break;

          default:
              {
                int status;

                /* Parent on success.  Wait for child, and check result. */

                g_free (command);

                while (waitpid (pid, &status, 0) < 0)
                  {
                    if (errno == ECHILD)
                      {
                        g_warning ("%s: Failed to get child exit status",
                                   __FUNCTION__);
                        if (chdir (previous_dir))
                          g_warning ("%s: and chdir failed\n",
                                     __FUNCTION__);
                        g_free (previous_dir);
                        return -1;
                      }
                    if (errno == EINTR)
                      continue;
                    g_warning ("%s: wait: %s",
                               __FUNCTION__,
                               strerror (errno));
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed\n",
                                 __FUNCTION__);
                    g_free (previous_dir);
                    return -1;
                  }
                if (WIFEXITED (status))
                  switch (WEXITSTATUS (status))
                    {
                    case EXIT_SUCCESS:
                      break;
                    case EXIT_FAILURE:
                    default:
                      g_warning ("%s: child failed, %s\n",
                                 __FUNCTION__,
                                 command);
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      return -1;
                    }
                else
                  {
                    g_warning ("%s: child failed, %s\n",
                               __FUNCTION__,
                               command);
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed\n",
                                 __FUNCTION__);
                    g_free (previous_dir);
                    return -1;
                  }

                /* Child succeeded, continue to process result. */

                break;
              }
          }
      }
    else
      {
        /* Just run the command as the current user. */
        g_free (report_file);
        g_free (pkcs12_file);

        /* RATS: ignore, command is defined above. */
        ret = system (command);
        /* Ignore the shell command exit status, because we've not
         * specified what it must be in the past. */
        if (ret == -1)
          {
            g_warning ("%s: system failed with ret %i, %i, %s\n",
                       __FUNCTION__,
                       ret,
                       WEXITSTATUS (ret),
                       command);
            if (chdir (previous_dir))
              g_warning ("%s: and chdir failed\n",
                         __FUNCTION__);
            g_free (previous_dir);
            g_free (command);
            return -1;
          }

        g_free (command);
      }

    /* Change back to the previous directory. */

    if (chdir (previous_dir))
      {
        g_warning ("%s: Failed to chdir back: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (previous_dir);
        return -1;
      }
    g_free (previous_dir);

    /* Remove the directory. */

    openvas_file_remove_recurse (report_dir);

    return 0;
  }
}

/**
 * @brief Send a report to a verinice.PRO server.
 *
 * @param[in]  url          URL of the server.
 * @param[in]  username     Username for server access.
 * @param[in]  password     Password for server access.
 * @param[in]  archive      Verinice archive that should be sent.
 * @param[in]  archive_size Size of the verinice archive
 *
 * @return 0 success, -1 error.
 */
static int
send_to_verinice (const char *url, const char *username, const char *password,
                  const char *archive, int archive_size)
{
  gchar *script, *script_dir;
  gchar *archive_file;
  gchar *clean_url, *clean_username, *clean_password;
  char archive_dir[] = "/tmp/openvasmd_alert_XXXXXX";
  GError *error;

  if ((archive == NULL) || (url == NULL))
    return -1;

  tracef ("send to verinice: %s", url);
  tracef ("archive: %s", archive);

  /* Setup files. */

  if (mkdtemp (archive_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
      return -1;
    }

  archive_file = g_strdup_printf ("%s/archive.vna", archive_dir);

  error = NULL;
  g_file_set_contents (archive_file, archive, archive_size, &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      g_free (archive_file);
      return -1;
    }

  /* Setup file names. */
  script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                 "openvasmd",
                                 "global_alert_methods",
                                 "f9d97653-f89b-41af-9ba1-0f6ee00e9c1a",
                                 NULL);

  script = g_build_filename (script_dir, "alert", NULL);

  if (!g_file_test (script, G_FILE_TEST_EXISTS))
    {
      g_warning ("%s: Failed to find alert script: %s\n",
           __FUNCTION__,
           script);
      g_free (archive_file);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

  {
    gchar *command;
    gchar *log_command; /* Command with password removed. */
    char *previous_dir;
    int ret;

    /* Change into the script directory. */

    /** @todo NULL arg is glibc extension. */
    previous_dir = getcwd (NULL, 0);
    if (previous_dir == NULL)
      {
        g_warning ("%s: Failed to getcwd: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (archive_file);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }

    if (chdir (script_dir))
      {
        g_warning ("%s: Failed to chdir: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (archive_file);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }
    g_free (script_dir);

    /* Call the script. */

    clean_url = g_shell_quote (url);
    clean_username = g_shell_quote (username);
    clean_password = g_shell_quote (password);

    command = g_strdup_printf ("%s %s %s %s %s > /dev/null"
                               " 2> /dev/null",
                               script,
                               clean_url,
                               clean_username,
                               clean_password,
                               archive_file);
    log_command = g_strdup_printf ("%s %s %s ****** %s > /dev/null"
                                   " 2> /dev/null",
                                   script,
                                   clean_url,
                                   clean_username,
                                   archive_file);
    g_free (script);
    g_free (clean_url);
    g_free (clean_username);
    g_free (clean_password);

    g_debug ("   command: %s\n", log_command);

    if (getuid () == 0)
      {
        pid_t pid;
        struct passwd *nobody;

        /* Run the command with lower privileges in a fork. */

        nobody = getpwnam ("nobody");
        if ((nobody == NULL)
            || chown (archive_dir, nobody->pw_uid, nobody->pw_gid)
            || chown (archive_file, nobody->pw_uid, nobody->pw_gid))
          {
            g_warning ("%s: Failed to set permissions for user nobody: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            g_free (previous_dir);
            g_free (archive_file);
            g_free (command);
            g_free (log_command);
            return -1;
          }
        g_free (archive_file);

        pid = fork ();
        switch (pid)
          {
          case 0:
              {
                /* Child.  Drop privileges, run command, exit. */

                cleanup_manage_process (FALSE);

                if (setgroups (0,NULL))
                  {
                    g_warning ("%s (child): setgroups: %s\n",
                               __FUNCTION__, strerror (errno));
                    exit (EXIT_FAILURE);
                  }
                if (setgid (nobody->pw_gid))
                  {
                    g_warning ("%s (child): setgid: %s\n",
                               __FUNCTION__,
                               strerror (errno));
                    exit (EXIT_FAILURE);
                  }
                if (setuid (nobody->pw_uid))
                  {
                    g_warning ("%s (child): setuid: %s\n",
                               __FUNCTION__,
                               strerror (errno));
                    exit (EXIT_FAILURE);
                  }

                /* RATS: ignore, command is defined above. */
                ret = system (command);
                /* Ignore the shell command exit status, because we've not
                 * specified what it must be in the past. */
                if (ret == -1)
                  {
                    g_warning ("%s (child):"
                               " system failed with ret %i, %i, %s\n",
                               __FUNCTION__,
                               ret,
                               WEXITSTATUS (ret),
                               log_command);
                    exit (EXIT_FAILURE);
                  }

                exit (EXIT_SUCCESS);
              }

          case -1:
            /* Parent when error. */

            g_warning ("%s: Failed to fork: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            if (chdir (previous_dir))
              g_warning ("%s: and chdir failed\n",
                         __FUNCTION__);
            g_free (previous_dir);
            g_free (command);
            g_free (log_command);
            return -1;
            break;

          default:
              {
                int status;

                /* Parent on success.  Wait for child, and check result. */

                while (waitpid (pid, &status, 0) < 0)
                  {
                    if (errno == ECHILD)
                      {
                        g_warning ("%s: Failed to get child exit status",
                                   __FUNCTION__);
                        if (chdir (previous_dir))
                          g_warning ("%s: and chdir failed\n",
                                     __FUNCTION__);
                        g_free (previous_dir);
                        return -1;
                      }
                    if (errno == EINTR)
                      continue;
                    g_warning ("%s: wait: %s",
                               __FUNCTION__,
                               strerror (errno));
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed\n",
                                 __FUNCTION__);
                    g_free (previous_dir);
                    return -1;
                  }
                if (WIFEXITED (status))
                  switch (WEXITSTATUS (status))
                    {
                    case EXIT_SUCCESS:
                      break;
                    case EXIT_FAILURE:
                    default:
                      g_warning ("%s: child failed, %s\n",
                                 __FUNCTION__,
                                 log_command);
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      return -1;
                    }
                else
                  {
                    g_warning ("%s: child failed, %s\n",
                               __FUNCTION__,
                               log_command);
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed\n",
                                 __FUNCTION__);
                    g_free (previous_dir);
                    return -1;
                  }

                /* Child succeeded, continue to process result. */

                break;
              }
          }
      }
    else
      {
        /* Just run the command as the current user. */
        g_free (archive_file);

        /* RATS: ignore, command is defined above. */
        ret = system (command);
        /* Ignore the shell command exit status, because we've not
         * specified what it must be in the past. */
        if (ret == -1)
          {
            g_warning ("%s: system failed with ret %i, %i, %s\n",
                       __FUNCTION__,
                       ret,
                       WEXITSTATUS (ret),
                       log_command);
            if (chdir (previous_dir))
              g_warning ("%s: and chdir failed\n",
                         __FUNCTION__);
            g_free (previous_dir);
            g_free (command);
            return -1;
          }

      }

    g_free (command);
    g_free (log_command);

    /* Change back to the previous directory. */

    if (chdir (previous_dir))
      {
        g_warning ("%s: Failed to chdir back: %s\n",
                   __FUNCTION__,
                   strerror (errno));
        g_free (previous_dir);
        return -1;
      }
    g_free (previous_dir);

    /* Remove the directory. */

    openvas_file_remove_recurse (archive_dir);

    return 0;
  }
}

/**
 * @brief Default max number of bytes of reports included in email alerts.
 */
#define MAX_CONTENT_LENGTH 20000

/**
 * @brief Maximum number of bytes of reports included in email alerts.
 *
 * A value less or equal to 0 allows any size.
 */
static int max_content_length = MAX_CONTENT_LENGTH;

/**
 * @brief Default max number of bytes of reports attached to email alerts.
 */
#define MAX_ATTACH_LENGTH 1048576

/**
 * @brief Maximum number of bytes of reports attached to email alerts.
 *
 * A value less or equal to 0 allows any size.
 */
static int max_attach_length = MAX_ATTACH_LENGTH;

/**
 * @brief Format string for simple notice alert email.
 */
#define SIMPLE_NOTICE_FORMAT                                                  \
 "%s.\n"                                                                      \
 "\n"                                                                         \
 "After the event %s,\n"                                                      \
 "the following condition was met: %s\n"                                      \
 "\n"                                                                         \
 "This email escalation is not configured to provide more details.\n"         \
 "Full details are stored on the scan engine.\n"                              \
 "\n"                                                                         \
 "\n"                                                                         \
 "Note:\n"                                                                    \
 "This email was sent to you as a configured security scan escalation.\n"     \
 "Please contact your local system administrator if you think you\n"          \
 "should not have received it.\n"

/**
 * @brief Print an alert subject.
 *
 * @param[in]  subject     Format string for subject.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  task        Task.
 *
 * @return Freshly allocated subject.
 */
static gchar *
alert_subject_print (const gchar *subject, event_t event,
                     const void *event_data, task_t task)
{
  int formatting;
  const gchar *point, *end;
  GString *new_subject;

  assert (subject);

  new_subject = g_string_new ("");
  for (formatting = 0, point = subject, end = (subject + strlen (subject));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_subject, '$');
              break;
            case 'e':
              {
                gchar *event_desc;
                event_desc = event_description (event, event_data,
                                                NULL);
                g_string_append (new_subject, event_desc);
                g_free (event_desc);
                break;
              }
            case 'n':
              {
                char *name = task_name (task);
                g_string_append (new_subject, name);
                free (name);
                break;
              }
            default:
              g_string_append_c (new_subject, '$');
              g_string_append_c (new_subject, *point);
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_subject, *point);

  return g_string_free (new_subject, FALSE);
}

/**
 * @brief Print an alert message.
 *
 * @param[in]  message      Format string for message.
 * @param[in]  event        Event.
 * @param[in]  event_data   Event data.
 * @param[in]  task         Task.
 * @param[in]  format_name  Report format name.
 * @param[in]  filter       Filter.
 * @param[in]  term         Filter term.
 * @param[in]  content      The report, for inlining.
 * @param[in]  content_length  Length of content.
 * @param[in]  truncated       Whether the report was truncated.
 * @param[in]  max_length      Max allowed length of content.
 *
 * @return Freshly allocated message.
 */
static gchar *
alert_message_print (const gchar *message, event_t event,
                     const void *event_data, task_t task,
                     alert_t alert, alert_condition_t condition,
                     gchar *format_name, filter_t filter,
                     const gchar *term, const gchar *zone,
                     const gchar *host_summary, const gchar *content,
                     gsize content_length, int truncated, int max_length)
{
  int formatting;
  const gchar *point, *end;
  GString *new_message;

  assert (message);

  new_message = g_string_new ("");
  for (formatting = 0, point = message, end = (message + strlen (message));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_message, '$');
              break;
            case 'c':
              {
                gchar *condition_desc;
                condition_desc = alert_condition_description
                                  (condition, alert);
                g_string_append (new_message, condition_desc);
                g_free (condition_desc);
                break;
              }
            case 'e':
              {
                gchar *event_desc;
                event_desc = event_description (event, event_data,
                                                NULL);
                g_string_append (new_message, event_desc);
                g_free (event_desc);
                break;
              }
            case 'H':
              {
                /* Host summary. */

                g_string_append (new_message,
                                 host_summary ? host_summary : "N/A");
                break;
              }
            case 'i':
              {
                if (content)
                  {
                    g_string_append_printf (new_message,
                                            "%.*s",
                                            /* Cast for 64 bit. */
                                            (int) MIN (content_length,
                                                       max_content_length),
                                            content);
                    if (content_length > max_content_length)
                      g_string_append_printf (new_message,
                                              "\n... (report truncated after"
                                              " %i characters)\n",
                                              max_content_length);
                  }

                break;
              }
            case 'n':
              {
                char *name = task_name (task);
                g_string_append (new_message, name);
                free (name);
                break;
              }
            case 'r':
              {
                /* Report format name. */

                g_string_append (new_message,
                                 format_name ? format_name : "N/A");
                break;
              }
            case 'F':
              {
                /* Name of filter. */

                if (filter)
                  {
                    char *name = filter_name (filter);
                    g_string_append (new_message, name);
                    free (name);
                  }
                else
                  g_string_append (new_message, "N/A");
                break;
              }
            case 'f':
              {
                /* Filter term. */

                g_string_append (new_message, term ? term : "N/A");
                break;
              }
            case 't':
              {
                if (truncated)
                  g_string_append_printf (new_message,
                                          "Note: This report exceeds the"
                                          " maximum length of %i characters"
                                          " and thus\n"
                                          "was truncated.\n",
                                          max_length);
                break;
              }
            case 'z':
              {
                /* Timezone. */

                g_string_append (new_message, zone ? zone : "N/A");
                break;
              }

            case 'R':
            default:
              g_string_append_c (new_message, '$');
              g_string_append_c (new_message, *point);
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_message, *point);

  return g_string_free (new_message, FALSE);
}

/**
 * @brief Escalate an event.
 *
 * @param[in]  alert   Alert.
 * @param[in]  task        Task.
 * @param[in]  report      Report.  0 for most recent report.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[in]  sort_order         Whether to sort ascending or descending.
 * @param[in]  sort_field         Field to sort on, or NULL for "type".
 * @param[in]  result_hosts_only  Whether to show only hosts with results.
 * @param[in]  min_cvss_base      Minimum CVSS base of included results.  All
 *                                results if NULL.
 * @param[in]  min_qod        Minimum QoD of included results.  All
 *                            results if NULL.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in]  delta_states       Delta states.  Allows caller to specify the
 *                                value for the delta_states param in the filter
 *                                string.
 * @param[in]  apply_overrides    Whether to apply overrides.
 * @param[in]  search_phrase      Phrase that results must include.  All results
 *                                if NULL or "".
 * @param[in]  autofp             Whether to apply auto FP filter.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  first_result       The result to start from.  The results are 0
 *                                indexed.
 * @param[in]  max_results        The maximum number of results returned.
 * @param[in]  zone               Timezone.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter.
 */
static int
escalate_2 (alert_t alert, task_t task, report_t report, event_t event,
            const void* event_data, alert_method_t method,
            alert_condition_t condition,
            /* Report filtering. */
            int sort_order, const char* sort_field,
            int result_hosts_only, const char *min_cvss_base,
            const char *min_qod,
            const char *levels, const char *delta_states, int apply_overrides,
            const char *search_phrase, int autofp, int notes, int notes_details,
            int overrides, int overrides_details, int first_result,
            int max_results, const char *zone)
{
  g_log ("event alert", G_LOG_LEVEL_MESSAGE,
         "The alert for task %s was triggered "
         "(Event: %s, Condition: %s)",
         task_name (task),
         event_description (event, event_data, NULL),
         alert_condition_description (condition, alert));

  switch (method)
    {
      case ALERT_METHOD_EMAIL:
        {
          char *to_address;
          char *format_name;
          format_name = NULL;

          to_address = alert_data (alert, "method", "to_address");

          if (to_address)
            {
              int ret;
              gchar *body, *subject;
              char *name, *notice, *from_address, *filt_id;
              gchar *base64, *type, *extension;
              filter_t filter;

              base64 = NULL;
              type = NULL;
              extension = NULL;

              from_address = alert_data (alert,
                                             "method",
                                             "from_address");

              notice = alert_data (alert, "method", "notice");
              filt_id = alert_filter_id (alert);
              filter = 0;
              if (filt_id)
                {
                  if (find_filter_with_permission (filt_id, &filter,
                                                   "get_filters"))
                    return -1;
                  if (filter == 0)
                    return -3;
                }

              name = task_name (task);
              if (notice && strcmp (notice, "0") == 0)
                {
                  gchar *event_desc, *condition_desc, *report_content;
                  gchar *alert_subject, *message;
                  gchar *term, *report_zone, *host_summary;
                  char *format_uuid;
                  report_format_t report_format = 0;
                  gsize content_length;

                  /* Message with inlined report. */

                  if (report == 0)
                    switch (sql_int64 (&report,
                                       "SELECT max (id) FROM reports"
                                       " WHERE task = %llu",
                                       task))
                      {
                        case 0:
                          if (report)
                            break;
                        case 1:        /* Too few rows in result of query. */
                        case -1:
                          free (notice);
                          free (name);
                          free (to_address);
                          free (from_address);
                          return -1;
                          break;
                        default:       /* Programming error. */
                          assert (0);
                          return -1;
                      }

                  format_uuid = alert_data (alert,
                                            "method",
                                            "notice_report_format");
                  if (((format_uuid == NULL)
                       || find_report_format_with_permission
                           (format_uuid, &report_format, "get_report_formats")
                       || (report_format == 0))
                      /* Fallback to TXT. */
                      && (find_report_format_with_permission
                           ("a3810a62-1f62-11e1-9219-406186ea4fc5",
                            &report_format,
                            "get_report_formats")
                          || (report_format == 0)))
                    {
                      g_free (format_uuid);
                      free (filt_id);
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      return -2;
                    }
                  g_free (format_uuid);

                  event_desc = event_description (event, event_data, NULL);
                  term = NULL;
                  report_zone = NULL;
                  host_summary = NULL;
                  report_content = manage_report (report, report_format,
                                                  filt_id,
                                                  sort_order, sort_field,
                                                  result_hosts_only,
                                                  min_cvss_base, min_qod,
                                                  levels, delta_states,
                                                  apply_overrides,
                                                  search_phrase, autofp,
                                                  notes, notes_details,
                                                  overrides,
                                                  overrides_details,
                                                  first_result, max_results,
                                                  NULL, /* Type. */
                                                  &content_length,
                                                  NULL,    /* Extension. */
                                                  NULL,   /* Content type. */
                                                  zone,
                                                  &term,
                                                  &report_zone,
                                                  &host_summary);
                  if (report_content == NULL)
                    {
                      free (event_desc);
                      free (filt_id);
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      g_free (term);
                      g_free (report_zone);
                      g_free (host_summary);
                      return -1;
                    }
                  format_name = report_format_name (report_format);
                  condition_desc = alert_condition_description (condition,
                                                                alert);
                  subject = g_strdup_printf ("[OpenVAS-Manager] Task '%s': %s",
                                             name ? name : "Internal Error",
                                             event_desc);
                  alert_subject = alert_data (alert, "method", "subject");
                  if (alert_subject && strlen (alert_subject))
                    {
                      g_free (subject);
                      subject = alert_subject_print (alert_subject, event,
                                                     event_data, task);
                      alert_subject = NULL;
                    }
                  g_free (alert_subject);

                  message = alert_data (alert, "method", "message");
                  if (message == NULL || strlen (message) == 0)
                    {
                      g_free (message);
                      message = g_strdup (ALERT_MESSAGE_INCLUDE);
                    }
                  body = alert_message_print (message, event, event_data,
                                              task, alert, condition,
                                              format_name, filter,
                                              term, report_zone,
                                              host_summary, report_content,
                                              content_length,
                                              content_length
                                              > max_content_length,
                                              max_content_length);
                  g_free (message);
                  g_free (report_content);
                  g_free (event_desc);
                  g_free (condition_desc);
                  g_free (term);
                  g_free (report_zone);
                  g_free (host_summary);
                }
              else if (notice && strcmp (notice, "2") == 0)
                {
                  gchar *event_desc, *condition_desc, *report_content;
                  char *format_uuid;
                  report_format_t report_format = 0;
                  gsize content_length;
                  gchar *alert_subject, *message;
                  gchar *term, *report_zone, *host_summary;

                  /* Message with attached report. */

                  if (report == 0)
                    switch (sql_int64 (&report,
                                       "SELECT max (id) FROM reports"
                                       " WHERE task = %llu",
                                       task))
                      {
                        case 0:
                          if (report)
                            break;
                        case 1:        /* Too few rows in result of query. */
                        case -1:
                          free (notice);
                          free (name);
                          free (to_address);
                          free (from_address);
                          return -1;
                          break;
                        default:       /* Programming error. */
                          assert (0);
                          return -1;
                      }

                  format_uuid = alert_data (alert,
                                            "method",
                                            "notice_attach_format");
                  if (((format_uuid == NULL)
                       || find_report_format_with_permission
                           (format_uuid, &report_format, "get_report_formats")
                       || (report_format == 0))
                      /* Fallback to TXT. */
                      && (find_report_format_with_permission
                           ("19f6f1b3-7128-4433-888c-ccc764fe6ed5",
                            &report_format,
                            "get_report_formats")
                          || (report_format == 0)))
                    {
                      g_free (format_uuid);
                      free (filt_id);
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      return -2;
                    }
                  g_free (format_uuid);

                  event_desc = event_description (event, event_data, NULL);
                  term = NULL;
                  report_zone = NULL;
                  host_summary = NULL;
                  report_content = manage_report (report, report_format,
                                                  filt_id,
                                                  sort_order, sort_field,
                                                  result_hosts_only,
                                                  min_cvss_base, min_qod,
                                                  levels, delta_states,
                                                  apply_overrides,
                                                  search_phrase, autofp,
                                                  notes, notes_details,
                                                  overrides, overrides_details,
                                                  first_result, max_results,
                                                  NULL, /* Type. */
                                                  &content_length,
                                                  &extension,
                                                  &type,
                                                  zone,
                                                  &term,
                                                  &report_zone,
                                                  &host_summary);
                  if (report_content == NULL)
                    {
                      g_free (event_desc);
                      free (filt_id);
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      return -1;
                    }
                  format_name = report_format_name (report_format);
                  condition_desc = alert_condition_description (condition,
                                                                    alert);
                  subject = g_strdup_printf ("[OpenVAS-Manager] Task '%s': %s",
                                             name ? name : "Internal Error",
                                             event_desc);
                  alert_subject = alert_data (alert, "method", "subject");
                  if (alert_subject && strlen (alert_subject))
                    {
                      g_free (subject);
                      subject = alert_subject_print (alert_subject, event,
                                                     event_data, task);
                      alert_subject = NULL;
                    }
                  g_free (alert_subject);
                  if (max_attach_length <= 0
                      || content_length <= max_attach_length)
                    base64 = g_base64_encode ((guchar*) report_content,
                                              content_length);
                  g_free (report_content);
                  message = alert_data (alert, "method", "message");
                  if (message == NULL || strlen (message) == 0)
                    {
                      g_free (message);
                      message = g_strdup (ALERT_MESSAGE_ATTACH);
                    }
                  body = alert_message_print (message, event, event_data,
                                              task, alert, condition,
                                              format_name, filter,
                                              term, report_zone,
                                              host_summary, NULL, 0,
                                              base64 == NULL,
                                              max_attach_length);
                  g_free (message);
                  g_free (event_desc);
                  g_free (condition_desc);
                  g_free (term);
                  g_free (report_zone);
                  g_free (host_summary);
                }
              else
                {
                  gchar *event_desc, *generic_desc, *condition_desc;
                  gchar *alert_subject, *message;

                  /* Simple notice message. */
                  format_name = NULL;
                  event_desc = event_description (event, event_data, name);
                  generic_desc = event_description (event, event_data, NULL);
                  condition_desc = alert_condition_description (condition,
                                                                    alert);

                  subject = g_strdup_printf ("[OpenVAS-Manager] Task '%s':"
                                             " An event occurred",
                                             name);

                  alert_subject = alert_data (alert, "method", "subject");
                  if (alert_subject && strlen (alert_subject))
                    {
                      g_free (subject);
                      subject = alert_subject_print (alert_subject, event,
                                                     event_data, task);
                      alert_subject = NULL;
                    }
                  g_free (alert_subject);

                  message = alert_data (alert, "method", "message");
                  if (message && strlen (message))
                    body = alert_message_print (message, event, event_data,
                                                task, alert, condition,
                                                NULL, 0, NULL, NULL, NULL,
                                                NULL, 0, 0, 0);
                  else
                    body = g_strdup_printf (SIMPLE_NOTICE_FORMAT,
                                            event_desc,
                                            generic_desc,
                                            condition_desc);
                  g_free (message);
                  g_free (event_desc);
                  g_free (generic_desc);
                  g_free (condition_desc);
                }
              free (filt_id);
              free (notice);

              gchar *fname_format, *file_name;
              gchar *report_id, *creation_time, *modification_time;

              fname_format
                = sql_string ("SELECT value FROM settings"
                              " WHERE name"
                              "       = 'Report Export File Name'"
                              " AND ((owner IS NULL)"
                              "      OR (owner ="
                              "          (SELECT id FROM users"
                              "           WHERE users.uuid = '%s')))"
                              " ORDER BY owner DESC LIMIT 1;",
                              current_credentials.uuid);

              report_id = report_uuid (report);

              creation_time
                = sql_string ("SELECT iso_time (start_time)"
                              " FROM reports"
                              " WHERE id = %llu",
                              report);

              modification_time
                = sql_string ("SELECT iso_time (end_time)"
                              " FROM reports"
                              " WHERE id = %llu",
                              report);

              file_name
                = openvas_export_file_name (fname_format,
                                            current_credentials.username,
                                            "report", report_id,
                                            creation_time, modification_time,
                                            name, format_name);
              ret = email (to_address, from_address, subject, body, base64,
                           type, file_name ? file_name : "openvas-report",
                           extension);

              free (name);
              free (format_name);
              g_free (base64);
              free (to_address);
              free (from_address);
              g_free (subject);
              g_free (body);
              g_free (fname_format);
              g_free (file_name);
              g_free (report_id);
              g_free (creation_time);
              g_free (modification_time);
              return ret;
            }
          return -1;
        }
      case ALERT_METHOD_HTTP_GET:
        {
          char *url;

          url = alert_data (alert, "method", "URL");

          if (url)
            {
              int ret, formatting;
              gchar *point, *end;
              GString *new_url;

              new_url = g_string_new ("");
              for (formatting = 0, point = url, end = (url + strlen (url));
                   point < end;
                   point++)
                if (formatting)
                  {
                    switch (*point)
                      {
                        case '$':
                          g_string_append_c (new_url, '$');
                          break;
                        case 'c':
                          {
                            gchar *condition_desc;
                            condition_desc = alert_condition_description
                                              (condition, alert);
                            g_string_append (new_url, condition_desc);
                            g_free (condition_desc);
                            break;
                          }
                        case 'e':
                          {
                            gchar *event_desc;
                            event_desc = event_description (event, event_data,
                                                            NULL);
                            g_string_append (new_url, event_desc);
                            g_free (event_desc);
                            break;
                          }
                        case 'n':
                          {
                            char *name = task_name (task);
                            g_string_append (new_url, name);
                            free (name);
                            break;
                          }
                        default:
                          g_string_append_c (new_url, '$');
                          g_string_append_c (new_url, *point);
                          break;
                      }
                    formatting = 0;
                  }
                else if (*point == '$')
                  formatting = 1;
                else
                  g_string_append_c (new_url, *point);

              ret = http_get (new_url->str);
              g_string_free (new_url, TRUE);
              g_free (url);
              return ret;
            }
          return -1;
        }
      case ALERT_METHOD_SOURCEFIRE:
        {
          char *ip, *port, *pkcs12, *filt_id;
          gchar *report_content;
          gsize content_length;
          report_format_t report_format;
          int ret;
          filter_t filter;

          if (lookup_report_format ("Sourcefire", &report_format)
              || (report_format == 0))
            return -2;

          if (report == 0)
            switch (sql_int64 (&report,
                               "SELECT max (id) FROM reports"
                               " WHERE task = %llu",
                               task))
              {
                case 0:
                  if (report)
                    break;
                case 1:        /* Too few rows in result of query. */
                case -1:
                  return -1;
                  break;
                default:       /* Programming error. */
                  assert (0);
                  return -1;
              }

          filt_id = alert_filter_id (alert);
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          report_content = manage_report (report, report_format,
                                          filt_id,
                                          sort_order, sort_field,
                                          result_hosts_only,
                                          min_cvss_base, min_qod, levels,
                                          delta_states, apply_overrides,
                                          search_phrase, autofp,
                                          notes, notes_details, overrides,
                                          overrides_details, first_result,
                                          max_results,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          zone,
                                          NULL,
                                          NULL,
                                          NULL);
          if (report_content == NULL)
            return -1;

          ip = alert_data (alert, "method", "defense_center_ip");
          port = alert_data (alert, "method", "defense_center_port");
          if (port == NULL)
            port = g_strdup ("8307");
          pkcs12 = alert_data (alert, "method", "pkcs12");

          tracef ("  sourcefire   ip: %s", ip);
          tracef ("  sourcefire port: %s", port);
          tracef ("sourcefire pkcs12: %s", pkcs12);

          ret = send_to_sourcefire (ip, port, pkcs12, report_content);

          free (filt_id);
          free (ip);
          g_free (port);
          free (pkcs12);
          g_free (report_content);

          return ret;
        }
      case ALERT_METHOD_SYSLOG:
        {
          char *submethod;
          gchar *message, *event_desc, *level;

          event_desc = event_description (event, event_data, NULL);
          message = g_strdup_printf ("%s: %s", event_name (event), event_desc);
          g_free (event_desc);

          submethod = alert_data (alert, "method", "submethod");
          level = g_strdup_printf ("event %s", submethod);
          g_free (submethod);

          tracef ("  syslog level: %s", level);
          tracef ("syslog message: %s", message);

          g_log (level, G_LOG_LEVEL_MESSAGE, "%s", message);

          g_free (level);
          g_free (message);

          return 0;
        }
      case ALERT_METHOD_VERINICE:
        {
          char *url, *username, *password, *filt_id;
          gchar *report_content, *format_uuid;
          gsize content_length;
          report_format_t report_format;
          int ret;
          filter_t filter;

          format_uuid = alert_data (alert,
                                    "method",
                                    "verinice_server_report_format");
          if (format_uuid && strlen (format_uuid))
            {
              if (find_report_format_with_permission (format_uuid,
                                                      &report_format,
                                                      "get_report_formats")
                  || (report_format == 0))
                {
                  g_warning ("Could not find Verinice RFP '%s'", format_uuid);
                  g_free (format_uuid);
                  return -2;
                }
              g_free (format_uuid);
            }
          else if (lookup_report_format ("Verinice ISM", &report_format)
              || (report_format == 0))
            {
              g_warning ("Could not find default verinice RFP");
              return -2;
            }

          if (report == 0)
            switch (sql_int64 (&report,
                               "SELECT max (id) FROM reports"
                               " WHERE task = %llu",
                               task))
              {
                case 0:
                  if (report)
                    break;
                case 1:        /* Too few rows in result of query. */
                case -1:
                  return -1;
                  break;
                default:       /* Programming error. */
                  assert (0);
                  return -1;
              }

          filt_id = alert_filter_id (alert);
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          report_content = manage_report (report, report_format,
                                          filt_id,
                                          sort_order, sort_field,
                                          result_hosts_only,
                                          min_cvss_base, min_qod, levels,
                                          delta_states, apply_overrides,
                                          search_phrase, autofp,
                                          notes, notes_details, overrides,
                                          overrides_details,
                                          first_result, max_results,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          zone,
                                          NULL,
                                          NULL,
                                          NULL);
          if (report_content == NULL)
            {
              g_warning ("Empty Report");
              return -1;
            }

          url = alert_data (alert, "method", "verinice_server_url");
          username = alert_data (alert, "method", "verinice_server_username");
          password = alert_data (alert, "method", "verinice_server_password");

          tracef ("    verinice  url: %s", url);
          tracef ("verinice username: %s", username);

          ret = send_to_verinice (url, username, password, report_content,
                                  content_length);

          free (filt_id);
          free (url);
          g_free (report_content);

          return ret;
        }
      case ALERT_METHOD_ERROR:
      default:
        break;
    }
  return -1;
}

/**
 * @brief Escalate an event with preset report filtering.
 *
 * @param[in]  alert   Alert.
 * @param[in]  task        Task.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 *
 * @return 0 success, -1 error, -2 failed to find report format for alert,
 *         -3 failed to find filter for alert.
 */
static int
escalate_1 (alert_t alert, task_t task, event_t event,
            const void* event_data, alert_method_t method,
            alert_condition_t condition)
{
  return escalate_2 (alert, task, 0, event, event_data, method, condition,
                     1,       /* Ascending. */
                     NULL,    /* Sort field. */
                     0,       /* Result hosts only. */
                     NULL,    /* Min CVSS base. */
                     NULL,    /* Min QoD. */
                     NULL,    /* Levels. */
                     NULL,    /* Delta states. */
                     1,       /* Apply overrides. */
                     NULL,    /* Search phrase. */
                     0,       /* Auto FP. */
                     1,       /* Notes. */
                     1,       /* Notes details. */
                     1,       /* Overrides. */
                     1,       /* Overrides details. */
                     0,       /* First results. */
                     /* Max results. */
                     (method == ALERT_METHOD_EMAIL ? 1000 : -1),
                     NULL);   /* Timezone. */
}

/**
 * @brief Escalate an alert with task and event data.
 *
 * @param[in]  alert_id    Alert UUID.
 * @param[in]  task_id     Task UUID.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 *
 * @return 0 success, 1 failed to find alert, 2 failed to find task,
 *         99 permission denied, -1 error, -2 failed to find report format
 *         for alert, -3 failed to find filter for alert.
 */
int
manage_alert (const char *alert_id, const char *task_id, event_t event,
              const void* event_data)
{
  alert_t alert;
  task_t task;
  alert_condition_t condition;
  alert_method_t method;

  if (user_may ("test_alert") == 0)
    return 99;

  if (find_alert_with_permission (alert_id, &alert, "test_alert"))
    return -1;
  if (alert == 0)
    return 1;

  if (find_task_with_permission (task_id, &task, NULL))
    return -1;
  if (task == 0)
    return 2;

  condition = alert_condition (alert);
  method = alert_method (alert);
  return escalate_1 (alert, task, event, event_data, method, condition);
}

/**
 * @brief Return whether an event applies to a task and an alert.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  task        Task.
 * @param[in]  alert       Alert.
 *
 * @return 1 if event applies, else 0.
 */
static int
event_applies (event_t event, const void *event_data, task_t task,
               alert_t alert)
{
  switch (event)
    {
      case EVENT_TASK_RUN_STATUS_CHANGED:
        {
          int ret;
          char *alert_event_data;

          alert_event_data = alert_data (alert, "event", "status");
          if (alert_event_data == NULL)
            return 0;
          ret = (task_run_status (task) == (task_status_t) event_data)
                && (strcmp (alert_event_data,
                            run_status_name_internal ((task_status_t)
                                                      event_data))
                    == 0);
          free (alert_event_data);
          return ret;
        }
      default:
        return 0;
    }
}

/**
 * @brief Return whether the condition of an alert is met by a task.
 *
 * @param[in]  task       Task.
 * @param[in]  alert  Alert.
 * @param[in]  condition  Condition.
 *
 * @return 1 if met, else 0.
 */
static int
condition_met (task_t task, alert_t alert,
               alert_condition_t condition)
{
  switch (condition)
    {
      case ALERT_CONDITION_ALWAYS:
        return 1;
        break;
      case ALERT_CONDITION_SEVERITY_AT_LEAST:
        {
          char *condition_severity_str;

          /* True if the threat level of the last finished report is at
           * least the given level. */

          condition_severity_str = alert_data (alert, "condition", "severity");

          if (condition_severity_str)
            {
              double condition_severity_dbl, task_severity_dbl;

              condition_severity_dbl = g_ascii_strtod (condition_severity_str,
                                                       0);
              task_severity_dbl = task_severity_double (task, 1,
                                                        MIN_QOD_DEFAULT, 0);

              if (task_severity_dbl >= condition_severity_dbl)
                {
                  free (condition_severity_str);
                  return 1;
                }
            }
          free (condition_severity_str);
          break;
        }
      case ALERT_CONDITION_SEVERITY_CHANGED:
        {
          char *direction;
          double last_severity, second_last_severity;

          /* True if the threat level of the last finished report changed
           * in the given direction with respect to the second last finished
           * report. */

          direction = alert_data (alert, "condition", "direction");
          last_severity = task_severity_double (task, 1,
                                                MIN_QOD_DEFAULT, 0);
          second_last_severity = task_severity_double (task, 1,
                                                       MIN_QOD_DEFAULT, 1);
          if (direction
              && last_severity > SEVERITY_MISSING
              && second_last_severity > SEVERITY_MISSING)
            {
              double cmp = last_severity - second_last_severity;
              tracef ("cmp: %f\n", cmp);
              tracef ("direction: %s\n", direction);
              tracef ("last_level: %1.1f\n", last_severity);
              tracef ("second_last_level: %1.1f\n", second_last_severity);
              if (((strcasecmp (direction, "changed") == 0) && cmp)
                  || ((strcasecmp (direction, "increased") == 0) && (cmp > 0))
                  || ((strcasecmp (direction, "decreased") == 0) && (cmp < 0)))
                {
                  free (direction);
                  return 1;
                }
            }
          else if (direction
                   && last_severity > SEVERITY_MISSING)
            {
              tracef ("direction: %s\n", direction);
              tracef ("last_level: %1.1f\n", last_severity);
              tracef ("second_last_level NULL\n");
              if ((strcasecmp (direction, "changed") == 0)
                  || (strcasecmp (direction, "increased") == 0))
                {
                  free (direction);
                  return 1;
                }
            }
          free (direction);
          break;
        }
      default:
        break;
    }
  return 0;
}

/**
 * @brief Produce an event.
 *
 * @param[in]  task        Task.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event type specific details.
 */
static void
event (task_t task, event_t event, void* event_data)
{
  iterator_t alerts;
  GArray *alerts_triggered;
  guint index;

  tracef ("   EVENT %i on task %llu", event, task);

  alerts_triggered = g_array_new (TRUE, TRUE, sizeof (alert_t));

  init_task_alert_iterator (&alerts, task, event);
  while (next (&alerts))
    {
      alert_t alert = task_alert_iterator_alert (&alerts);
      if (event_applies (event, event_data, task, alert))
        {
          alert_condition_t condition;

          condition = alert_condition (alert);
          if (condition_met (task, alert, condition))
            g_array_append_val (alerts_triggered, alert);
        }
    }
  cleanup_iterator (&alerts);

  /* Run the alerts outside the iterator, because they make take some
   * time and the iterator would prevent update processes (OMP MODIFY_XXX,
   * CREATE_XXX, ...) from locking the database. */
  index = alerts_triggered->len;
  while (index--)
    {
      alert_t alert;
      alert_condition_t condition;

      alert = g_array_index (alerts_triggered, alert_t, index);
      condition = alert_condition (alert);
      escalate_1 (alert,
                  task,
                  event,
                  event_data,
                  alert_method (alert),
                  condition);
    }

  g_array_free (alerts_triggered, TRUE);
}

/**
 * @brief Initialise an alert task iterator.
 *
 * Iterate over all tasks that use the alert.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  alert  Alert.
 * @param[in]  ascending  Whether to sort ascending or descending.
 */
void
init_alert_task_iterator (iterator_t* iterator, alert_t alert,
                              int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (alert);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT tasks.name, tasks.uuid, %s FROM tasks, task_alerts"
                 " WHERE tasks.id = task_alerts.task"
                 " AND task_alerts.alert = %llu"
                 " AND hidden = 0"
                 " ORDER BY tasks.name %s;",
                 available,
                 alert,
                 ascending ? "ASC" : "DESC");

  g_free (available);
}

/**
 * @brief Return the name from an alert task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of the task or NULL if iteration is complete.
 */
const char*
alert_task_iterator_name (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 0);
  return ret;
}

/**
 * @brief Return the uuid from an alert task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID of the task or NULL if iteration is complete.
 */
const char*
alert_task_iterator_uuid (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 1);
  return ret;
}

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
alert_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}


/* Task functions. */

/**
 * @brief Append value to field of task.
 *
 * @param[in]  task   Task.
 * @param[in]  field  Field.
 * @param[in]  value  Value.
 */
static void
append_to_task_string (task_t task, const char* field, const char* value)
{
  char* current;
  gchar* quote;
  current = sql_string ("SELECT %s FROM tasks WHERE id = %llu;",
                        field,
                        task);
  if (current)
    {
      gchar* new = g_strconcat ((const gchar*) current, value, NULL);
      free (current);
      quote = sql_nquote (new, strlen (new));
      g_free (new);
    }
  else
    quote = sql_nquote (value, strlen (value));
  sql ("UPDATE tasks SET %s = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       field,
       quote,
       task);
  g_free (quote);
}

/**
 * @brief Filter columns for task iterator.
 */
#define TASK_ITERATOR_FILTER_COLUMNS                                          \
 { GET_ITERATOR_FILTER_COLUMNS, "status", "total", "first_report",            \
   "last_report", "threat", "trend", "severity", "schedule", "next_due",      \
   "first", "last", NULL }

#define TASK_ITERATOR_COLUMNS                                               \
 {                                                                          \
   GET_ITERATOR_COLUMNS (tasks),                                            \
   { "run_status", NULL },                                                  \
   {                                                                        \
     "(SELECT count(*) FROM reports"                                        \
     " WHERE task = tasks.id)",                                             \
     "total"                                                                \
   },                                                                       \
   {                                                                        \
     "(SELECT uuid FROM reports WHERE task = tasks.id"                      \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " AND scan_run_status = 1"                                             \
     " ORDER BY date ASC LIMIT 1)",                                         \
     "first_report"                                                         \
   },                                                                       \
   { "task_threat_level (id, opts.override, opts.min_qod)", "threat" },     \
   { "task_trend (id, opts.override, opts.min_qod)", "trend" },             \
   { "run_status_name (run_status)", "status" },                            \
   {                                                                        \
     "(SELECT uuid FROM reports WHERE task = tasks.id"                      \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " AND scan_run_status = 1"                                             \
     " ORDER BY date DESC LIMIT 1)",                                        \
     "last_report"                                                          \
   },                                                                       \
   { "task_severity (id, opts.override, opts.min_qod)", "severity" },       \
   {                                                                        \
     "(SELECT count(*) FROM reports"                                        \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " WHERE task = tasks.id AND scan_run_status = 1)",                     \
     NULL                                                                   \
   },                                                                       \
   { "hosts_ordering", NULL },                                              \
   { "scanner", NULL },                                                     \
   {                                                                        \
     "(SELECT schedules.name FROM schedules"                                \
     " WHERE schedules.id = tasks.schedule)",                               \
     "schedule"                                                             \
   },                                                                       \
   {                                                                        \
     "(CASE WHEN schedule_next_time IS NULL"                                \
     " THEN -1"                                                             \
     " WHEN schedule_next_time = 0 AND tasks.schedule > 0"                  \
     " THEN (SELECT first_time"                                             \
     "       FROM schedules"                                                \
     "       WHERE schedules.id = tasks.schedule)"                          \
     " ELSE schedule_next_time"                                             \
     " END)",                                                               \
     "next_due"                                                             \
   },                                                                       \
   {                                                                        \
     "(SELECT date FROM reports WHERE task = tasks.id"                      \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " AND scan_run_status = 1"                                             \
     " ORDER BY date ASC LIMIT 1)",                                         \
     "first"                                                                \
   },                                                                       \
   {                                                                        \
     "(SELECT date FROM reports WHERE task = tasks.id"                      \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " AND scan_run_status = 1"                                             \
     " ORDER BY date DESC LIMIT 1)",                                        \
     "last"                                                                 \
   },                                                                       \
   { NULL, NULL }                                                           \
 }

/**
 * @brief Generate the extra_tables string for a task iterator.
 *
 * @param[in]  override  Whether to apply overrides.
 * @param[in]  min_qod   Minimum QoD of results to count.
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
task_iterator_opts_table (int override, int min_qod)
{
  return g_strdup_printf (", (SELECT"
                          "   %d AS override,"
                          "   %d AS min_qod)"
                          "  AS opts",
                          override,
                          min_qod);
}

/**
 * @brief Initialise a task iterator, limited to current user's tasks.
 *
 * @param[in]  iterator    Task iterator.
 * @param[in]  trash       Whether to iterate over trashcan tasks.
 */
static void
init_user_task_iterator (iterator_t* iterator, int trash)
{
  static column_t select_columns[] = TASK_ITERATOR_COLUMNS;
  gchar *extra_tables;
  gchar *columns;

  extra_tables = task_iterator_opts_table (0, MIN_QOD_DEFAULT);

  columns = columns_build_select (select_columns);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM tasks%s"
                 " WHERE ((owner IS NULL)"
                 "        OR (owner = (SELECT id FROM users"
                 "                     WHERE users.uuid = '%s')))"
                 "%s;",
                 columns,
                 extra_tables,
                 current_credentials.uuid,
                 trash ? " AND hidden = 2" : " AND hidden < 2");

  g_free (extra_tables);
  g_free (columns);
}

/**
 * @brief Initialise a task iterator.
 *
 * @param[in]  iterator    Task iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_task_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = TASK_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TASK_ITERATOR_COLUMNS;
  char *filter;
  gchar *value;
  int overrides, min_qod;
  gchar *extra_tables;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;
  value = filter_term_value (filter ? filter : get->filter, "apply_overrides");
  overrides = value && strcmp (value, "0");
  g_free (value);

  value = filter_term_value (filter ? filter : get->filter, "min_qod");
  if (value == NULL || sscanf (value, "%d", &min_qod) != 1)
    min_qod = MIN_QOD_DEFAULT;
  g_free (value);
  free (filter);

  extra_tables = task_iterator_opts_table (overrides, min_qod);

  ret = init_get_iterator (iterator,
                            "task",
                            get,
                            /* Columns. */
                            columns,
                            /* Columns for trashcan. */
                            columns,
                            filter_columns,
                            0,
                            extra_tables,
                            (get->id
                             && (strcmp (get->id, MANAGE_EXAMPLE_TASK_UUID)
                                 == 0))
                             ? " AND hidden = 1"
                             : (get->trash
                                 ? " AND hidden = 2"
                                 : " AND hidden = 0"),
                            current_credentials.uuid ? TRUE : FALSE);

  g_free (extra_tables);
  return ret;
}

/**
 * @brief Get the run status from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task run status.
 */
task_status_t
task_iterator_run_status (iterator_t* iterator)
{
  task_status_t ret;
  if (iterator->done) return TASK_STATUS_INTERNAL_ERROR;
  ret = (unsigned int) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT);
  return ret;
}

/**
 * @brief Get the number of reports of a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Count of all task reports.
 */
int
task_iterator_total_reports (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the first report UUID from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return First report UUID.
 */
const char *
task_iterator_first_report (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
}

/**
 * @brief Get the run status name from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task run status name.
 */
const char *
task_iterator_run_status_name (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
}

/**
 * @brief Get the last report UUID from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Last report UUID.
 */
const char *
task_iterator_last_report (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

/**
 * @brief Get the number of reports of a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Count of all task reports.
 */
int
task_iterator_finished_reports (iterator_t *iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
}

/**
 * @brief Get the hosts ordering value from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task hosts ordering.
 */
const char *
task_iterator_hosts_ordering (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
}

/**
 * @brief Get the UUID of task scanner from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task scanner if found, NULL otherwise.
 */
scanner_t
task_iterator_scanner (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 10);
}

/**
 * @brief Return whether a task is in use by a task.
 *
 * @param[in]  task  Task.
 *
 * @return 0.
 */
int
task_in_use (task_t task)
{
  task_status_t status;
  status = task_run_status (task);
  return status == TASK_STATUS_DELETE_REQUESTED
         || status == TASK_STATUS_DELETE_WAITING
         || status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED
         || status == TASK_STATUS_DELETE_ULTIMATE_WAITING
         || status == TASK_STATUS_REQUESTED
         || status == TASK_STATUS_RUNNING
         || status == TASK_STATUS_STOP_REQUESTED_GIVEUP
         || status == TASK_STATUS_STOP_REQUESTED
         || status == TASK_STATUS_STOP_WAITING;
}

/**
 * @brief Return whether a trashcan task is referenced by a task.
 *
 * @param[in]  task  Task.
 *
 * @return 0.
 */
int
trash_task_in_use (task_t task)
{
  return task_in_use (task);
}

/**
 * @brief Return whether a task is an Alterable Task.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if Alterable, else 0.
 */
int
task_alterable (task_t task)
{
  return sql_int ("SELECT alterable FROM tasks"
                  " WHERE id = %llu",
                  task);
}

/**
 * @brief Return whether a task is writable.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if writable, else 0.
 */
int
task_writable (task_t task)
{
  return sql_int ("SELECT hidden = 0 FROM tasks"
                  " WHERE id = %llu",
                  task);
}

/**
 * @brief Return whether a trashcan task is writable.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if writable, else 0.
 */
int
trash_task_writable (task_t task)
{
  return sql_int ("SELECT hidden = 2 FROM tasks"
                  " WHERE id = %llu",
                  task);
}

/**
 * @brief Initialize the manage library for a process.
 *
 * Open the SQL database, attach secondary databases, and define functions.
 *
 * @param[in]  update_nvt_cache  0 operate normally, -1 just update NVT cache,
 *                               -2 just rebuild NVT cache.
 * @param[in]  database          Location of manage database.
 */
void
init_manage_process (int update_nvt_cache, const gchar *database)
{
  gchar *mgr_dir;
  int ret;

  if (sql_is_open ())
    {
      if (update_nvt_cache == -2)
        {
          if (progress)
            progress ();
          sql_begin_exclusive ();
          sql ("DELETE FROM nvt_cves;");
          sql ("DELETE FROM nvts;");
          if (progress)
            progress ();
          sql ("DELETE FROM nvt_preferences;");
        }
      return;
    }

  /* Ensure the mgr directory exists. */
  mgr_dir = g_build_filename (OPENVAS_STATE_DIR, "mgr", NULL);
  ret = g_mkdir_with_parents (mgr_dir, 0755 /* "rwxr-xr-x" */);
  g_free (mgr_dir);
  if (ret == -1)
    {
      g_warning ("%s: failed to create mgr directory: %s\n",
                 __FUNCTION__,
                 strerror (errno));
      abort ();
    }

  {
    struct stat state;
    int err;

    err = stat (database ? database : sql_default_database (),
                &state);
    if (err)
      switch (errno)
        {
          case ENOENT:
            break;
          default:
            g_warning ("%s: failed to stat database: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            abort ();
        }
    else if (state.st_mode & (S_IXUSR | S_IRWXG | S_IRWXO))
      {
        g_warning ("%s: database permissions are too loose, repairing\n",
                   __FUNCTION__);
        if (chmod (database ? database : sql_default_database (),
                   S_IRUSR | S_IWUSR))
          {
            g_warning ("%s: chmod failed: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            abort ();
          }
      }
  }

  /* Open the database. */
  if (sql_open (database))
    {
      g_warning ("%s: sql_open failed\n", __FUNCTION__);
      abort ();
    }

  if (update_nvt_cache)
    {
      if (update_nvt_cache == -2)
        {
          if (progress)
            progress ();
          sql_begin_exclusive ();
          sql ("DELETE FROM nvt_cves;");
          sql ("DELETE FROM nvts;");
          if (progress)
            progress ();
          sql ("DELETE FROM nvt_preferences;");
        }
    }
  else
    {
      /* Attach the SCAP and CERT databases. */

      manage_attach_databases ();
    }

  /* Define functions for SQL. */

  if (manage_create_sql_functions ())
    {
      g_warning ("%s: failed to create functions", __FUNCTION__);
      abort ();
    }
}

/**
 * @brief Reinitialize the manage library for a process.
 *
 * This is mandatory after a fork, to not carry open databases around (refer
 * to database documentation).
 */
void
reinit_manage_process ()
{
  cleanup_manage_process (FALSE);
  init_manage_process (0, task_db_name);
}

/**
 * @brief Setup config preferences for a config.
 *
 * @param[in]  config         The config.
 * @param[in]  safe_checks    safe_checks option: 1 for "yes", 0 for "no".
 * @param[in]  optimize_test  optimize_test option: 1 for "yes", 0 for "no".
 * @param[in]  port_range     port_range option: 1 for "yes", 0 for "no".
 */
static void
setup_full_config_prefs (config_t config, int safe_checks,
                         int optimize_test, int port_range)
{
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'max_hosts', '20');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'max_checks', '4');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'cgi_path', '/cgi-bin:/scripts');",
       config);
  if (port_range)
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'port_range', '1-65535');",
         config);
  else
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'port_range', 'default');",
         config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'auto_enable_dependencies', 'yes');",
       config);
  if (optimize_test)
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'optimize_test', 'yes');",
         config);
  else
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'optimize_test', 'no');",
         config);
  if (safe_checks)
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'safe_checks', 'yes');",
         config);
  else
    sql ("INSERT into config_preferences (config, type, name, value)"
         " VALUES (%i, 'SERVER_PREFS', 'safe_checks', 'no');",
         config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'use_mac_addr', 'no');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'unscanned_closed', 'yes');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'unscanned_closed_udp', 'yes');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'log_whole_attack', 'no');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'checks_read_timeout', '5');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'network_scan', 'no');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'non_simult_ports', '139, 445');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'plugins_timeout', '320');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'SERVER_PREFS', 'nasl_no_signature_check', 'yes');",
       config);

  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'PLUGINS_PREFS',"
       " 'Ping Host[checkbox]:Mark unrechable Hosts as dead (not scanning)',"
       " 'yes');",
       config);
  sql ("INSERT into config_preferences (config, type, name, value)"
       " VALUES (%i, 'PLUGINS_PREFS',"
       " 'Login configurations[checkbox]:NTLMSSP',"
       " 'yes');",
       config);
}

/**
 * @brief Update the memory cache of NVTs.
 */
static void
update_nvti_cache ()
{
  iterator_t nvts;

  nvtis_free (nvti_cache);

  nvti_cache = nvtis_new ();

  init_nvt_iterator (&nvts, (nvt_t) 0, (config_t) 0, NULL, NULL, 1, NULL);
  while (next (&nvts))
    {
      nvti_t *nvti = nvti_new ();
      nvti_set_oid (nvti, nvt_iterator_oid (&nvts));
      nvti_set_name (nvti, nvt_iterator_name (&nvts));
      nvti_set_family (nvti, nvt_iterator_family (&nvts));
      nvti_set_cvss_base (nvti, nvt_iterator_cvss_base (&nvts));
      nvti_set_cve (nvti, nvt_iterator_cve (&nvts));
      nvti_set_bid (nvti, nvt_iterator_bid (&nvts));
      nvti_set_xref (nvti, nvt_iterator_xref (&nvts));
      nvti_set_tag (nvti, nvt_iterator_tag (&nvts));
      nvtis_add (nvti_cache, nvti);
    }
  cleanup_iterator (&nvts);
}

/**
 * @brief Update the memory cache of NVTs, if this has been requested.
 */
void
manage_update_nvti_cache ()
{
  if (sql_int ("SELECT value FROM %s.meta"
               " WHERE name = 'update_nvti_cache';",
               sql_schema ()))
    {
      update_nvti_cache ();
      sql ("UPDATE %s.meta SET value = 0 WHERE name = 'update_nvti_cache';",
           sql_schema ());
    }
}

/**
 * @brief Insert a port range.
 */
#define RANGE(type, start, end)                                      \
  sql ("INSERT INTO port_ranges"                                     \
       " (uuid, port_list, type, start, \"end\", comment, exclude)"  \
       " VALUES"                                                     \
       " (make_uuid (), %llu, %i,"                                   \
       "  '" G_STRINGIFY (start) "',"                                \
       "  '" G_STRINGIFY (end) "',"                                  \
       "  '', 0)",                                                   \
       list,                                                         \
       type)

/**
 * @brief Make port ranges.
 *
 * Caller must lock the db.
 *
 * @param[in]  list  Port list.
 */
static void
make_port_ranges_openvas_default (port_list_t list)
{
  RANGE (PORT_PROTOCOL_TCP, 1, 5);
  RANGE (PORT_PROTOCOL_TCP, 7, 7);
  RANGE (PORT_PROTOCOL_TCP, 9, 9);
  RANGE (PORT_PROTOCOL_TCP, 11, 11);
  RANGE (PORT_PROTOCOL_TCP, 13, 13);
  RANGE (PORT_PROTOCOL_TCP, 15, 15);
  RANGE (PORT_PROTOCOL_TCP, 17, 25);
  RANGE (PORT_PROTOCOL_TCP, 27, 27);
  RANGE (PORT_PROTOCOL_TCP, 29, 29);
  RANGE (PORT_PROTOCOL_TCP, 31, 31);
  RANGE (PORT_PROTOCOL_TCP, 33, 33);
  RANGE (PORT_PROTOCOL_TCP, 35, 35);
  RANGE (PORT_PROTOCOL_TCP, 37, 39);
  RANGE (PORT_PROTOCOL_TCP, 41, 59);
  RANGE (PORT_PROTOCOL_TCP, 61, 224);
  RANGE (PORT_PROTOCOL_TCP, 242, 248);
  RANGE (PORT_PROTOCOL_TCP, 256, 268);
  RANGE (PORT_PROTOCOL_TCP, 280, 287);
  RANGE (PORT_PROTOCOL_TCP, 308, 322);
  RANGE (PORT_PROTOCOL_TCP, 333, 333);
  RANGE (PORT_PROTOCOL_TCP, 344, 700);
  RANGE (PORT_PROTOCOL_TCP, 702, 702);
  RANGE (PORT_PROTOCOL_TCP, 704, 707);
  RANGE (PORT_PROTOCOL_TCP, 709, 711);
  RANGE (PORT_PROTOCOL_TCP, 721, 721);
  RANGE (PORT_PROTOCOL_TCP, 723, 723);
  RANGE (PORT_PROTOCOL_TCP, 729, 731);
  RANGE (PORT_PROTOCOL_TCP, 740, 742);
  RANGE (PORT_PROTOCOL_TCP, 744, 744);
  RANGE (PORT_PROTOCOL_TCP, 747, 754);
  RANGE (PORT_PROTOCOL_TCP, 758, 765);
  RANGE (PORT_PROTOCOL_TCP, 767, 767);
  RANGE (PORT_PROTOCOL_TCP, 769, 777);
  RANGE (PORT_PROTOCOL_TCP, 780, 783);
  RANGE (PORT_PROTOCOL_TCP, 786, 787);
  RANGE (PORT_PROTOCOL_TCP, 799, 801);
  RANGE (PORT_PROTOCOL_TCP, 808, 808);
  RANGE (PORT_PROTOCOL_TCP, 810, 810);
  RANGE (PORT_PROTOCOL_TCP, 828, 829);
  RANGE (PORT_PROTOCOL_TCP, 847, 848);
  RANGE (PORT_PROTOCOL_TCP, 860, 860);
  RANGE (PORT_PROTOCOL_TCP, 871, 871);
  RANGE (PORT_PROTOCOL_TCP, 873, 873);
  RANGE (PORT_PROTOCOL_TCP, 886, 888);
  RANGE (PORT_PROTOCOL_TCP, 898, 898);
  RANGE (PORT_PROTOCOL_TCP, 900, 904);
  RANGE (PORT_PROTOCOL_TCP, 911, 913);
  RANGE (PORT_PROTOCOL_TCP, 927, 927);
  RANGE (PORT_PROTOCOL_TCP, 950, 950);
  RANGE (PORT_PROTOCOL_TCP, 953, 953);
  RANGE (PORT_PROTOCOL_TCP, 975, 975);
  RANGE (PORT_PROTOCOL_TCP, 989, 1002);
  RANGE (PORT_PROTOCOL_TCP, 1005, 1005);
  RANGE (PORT_PROTOCOL_TCP, 1008, 1008);
  RANGE (PORT_PROTOCOL_TCP, 1010, 1010);
  RANGE (PORT_PROTOCOL_TCP, 1023, 1027);
  RANGE (PORT_PROTOCOL_TCP, 1029, 1036);
  RANGE (PORT_PROTOCOL_TCP, 1040, 1040);
  RANGE (PORT_PROTOCOL_TCP, 1042, 1042);
  RANGE (PORT_PROTOCOL_TCP, 1045, 1045);
  RANGE (PORT_PROTOCOL_TCP, 1047, 1112);
  RANGE (PORT_PROTOCOL_TCP, 1114, 1117);
  RANGE (PORT_PROTOCOL_TCP, 1119, 1120);
  RANGE (PORT_PROTOCOL_TCP, 1122, 1127);
  RANGE (PORT_PROTOCOL_TCP, 1139, 1139);
  RANGE (PORT_PROTOCOL_TCP, 1154, 1155);
  RANGE (PORT_PROTOCOL_TCP, 1161, 1162);
  RANGE (PORT_PROTOCOL_TCP, 1168, 1170);
  RANGE (PORT_PROTOCOL_TCP, 1178, 1178);
  RANGE (PORT_PROTOCOL_TCP, 1180, 1181);
  RANGE (PORT_PROTOCOL_TCP, 1183, 1188);
  RANGE (PORT_PROTOCOL_TCP, 1194, 1194);
  RANGE (PORT_PROTOCOL_TCP, 1199, 1231);
  RANGE (PORT_PROTOCOL_TCP, 1233, 1286);
  RANGE (PORT_PROTOCOL_TCP, 1288, 1774);
  RANGE (PORT_PROTOCOL_TCP, 1776, 2028);
  RANGE (PORT_PROTOCOL_TCP, 2030, 2030);
  RANGE (PORT_PROTOCOL_TCP, 2032, 2035);
  RANGE (PORT_PROTOCOL_TCP, 2037, 2038);
  RANGE (PORT_PROTOCOL_TCP, 2040, 2065);
  RANGE (PORT_PROTOCOL_TCP, 2067, 2083);
  RANGE (PORT_PROTOCOL_TCP, 2086, 2087);
  RANGE (PORT_PROTOCOL_TCP, 2089, 2152);
  RANGE (PORT_PROTOCOL_TCP, 2155, 2155);
  RANGE (PORT_PROTOCOL_TCP, 2159, 2167);
  RANGE (PORT_PROTOCOL_TCP, 2170, 2177);
  RANGE (PORT_PROTOCOL_TCP, 2180, 2181);
  RANGE (PORT_PROTOCOL_TCP, 2190, 2191);
  RANGE (PORT_PROTOCOL_TCP, 2199, 2202);
  RANGE (PORT_PROTOCOL_TCP, 2213, 2213);
  RANGE (PORT_PROTOCOL_TCP, 2220, 2223);
  RANGE (PORT_PROTOCOL_TCP, 2232, 2246);
  RANGE (PORT_PROTOCOL_TCP, 2248, 2255);
  RANGE (PORT_PROTOCOL_TCP, 2260, 2260);
  RANGE (PORT_PROTOCOL_TCP, 2273, 2273);
  RANGE (PORT_PROTOCOL_TCP, 2279, 2289);
  RANGE (PORT_PROTOCOL_TCP, 2294, 2311);
  RANGE (PORT_PROTOCOL_TCP, 2313, 2371);
  RANGE (PORT_PROTOCOL_TCP, 2381, 2425);
  RANGE (PORT_PROTOCOL_TCP, 2427, 2681);
  RANGE (PORT_PROTOCOL_TCP, 2683, 2824);
  RANGE (PORT_PROTOCOL_TCP, 2826, 2854);
  RANGE (PORT_PROTOCOL_TCP, 2856, 2924);
  RANGE (PORT_PROTOCOL_TCP, 2926, 3096);
  RANGE (PORT_PROTOCOL_TCP, 3098, 3299);
  RANGE (PORT_PROTOCOL_TCP, 3302, 3321);
  RANGE (PORT_PROTOCOL_TCP, 3326, 3366);
  RANGE (PORT_PROTOCOL_TCP, 3372, 3403);
  RANGE (PORT_PROTOCOL_TCP, 3405, 3545);
  RANGE (PORT_PROTOCOL_TCP, 3547, 3707);
  RANGE (PORT_PROTOCOL_TCP, 3709, 3765);
  RANGE (PORT_PROTOCOL_TCP, 3767, 3770);
  RANGE (PORT_PROTOCOL_TCP, 3772, 3800);
  RANGE (PORT_PROTOCOL_TCP, 3802, 3802);
  RANGE (PORT_PROTOCOL_TCP, 3845, 3871);
  RANGE (PORT_PROTOCOL_TCP, 3875, 3876);
  RANGE (PORT_PROTOCOL_TCP, 3885, 3885);
  RANGE (PORT_PROTOCOL_TCP, 3900, 3900);
  RANGE (PORT_PROTOCOL_TCP, 3928, 3929);
  RANGE (PORT_PROTOCOL_TCP, 3939, 3939);
  RANGE (PORT_PROTOCOL_TCP, 3959, 3959);
  RANGE (PORT_PROTOCOL_TCP, 3970, 3971);
  RANGE (PORT_PROTOCOL_TCP, 3984, 3987);
  RANGE (PORT_PROTOCOL_TCP, 3999, 4036);
  RANGE (PORT_PROTOCOL_TCP, 4040, 4042);
  RANGE (PORT_PROTOCOL_TCP, 4045, 4045);
  RANGE (PORT_PROTOCOL_TCP, 4080, 4080);
  RANGE (PORT_PROTOCOL_TCP, 4096, 4100);
  RANGE (PORT_PROTOCOL_TCP, 4111, 4111);
  RANGE (PORT_PROTOCOL_TCP, 4114, 4114);
  RANGE (PORT_PROTOCOL_TCP, 4132, 4134);
  RANGE (PORT_PROTOCOL_TCP, 4138, 4138);
  RANGE (PORT_PROTOCOL_TCP, 4141, 4145);
  RANGE (PORT_PROTOCOL_TCP, 4154, 4154);
  RANGE (PORT_PROTOCOL_TCP, 4160, 4160);
  RANGE (PORT_PROTOCOL_TCP, 4199, 4200);
  RANGE (PORT_PROTOCOL_TCP, 4242, 4242);
  RANGE (PORT_PROTOCOL_TCP, 4300, 4300);
  RANGE (PORT_PROTOCOL_TCP, 4321, 4321);
  RANGE (PORT_PROTOCOL_TCP, 4333, 4333);
  RANGE (PORT_PROTOCOL_TCP, 4343, 4351);
  RANGE (PORT_PROTOCOL_TCP, 4353, 4358);
  RANGE (PORT_PROTOCOL_TCP, 4369, 4369);
  RANGE (PORT_PROTOCOL_TCP, 4400, 4400);
  RANGE (PORT_PROTOCOL_TCP, 4442, 4457);
  RANGE (PORT_PROTOCOL_TCP, 4480, 4480);
  RANGE (PORT_PROTOCOL_TCP, 4500, 4500);
  RANGE (PORT_PROTOCOL_TCP, 4545, 4547);
  RANGE (PORT_PROTOCOL_TCP, 4555, 4555);
  RANGE (PORT_PROTOCOL_TCP, 4557, 4557);
  RANGE (PORT_PROTOCOL_TCP, 4559, 4559);
  RANGE (PORT_PROTOCOL_TCP, 4567, 4568);
  RANGE (PORT_PROTOCOL_TCP, 4600, 4601);
  RANGE (PORT_PROTOCOL_TCP, 4658, 4662);
  RANGE (PORT_PROTOCOL_TCP, 4672, 4672);
  RANGE (PORT_PROTOCOL_TCP, 4752, 4752);
  RANGE (PORT_PROTOCOL_TCP, 4800, 4802);
  RANGE (PORT_PROTOCOL_TCP, 4827, 4827);
  RANGE (PORT_PROTOCOL_TCP, 4837, 4839);
  RANGE (PORT_PROTOCOL_TCP, 4848, 4849);
  RANGE (PORT_PROTOCOL_TCP, 4868, 4869);
  RANGE (PORT_PROTOCOL_TCP, 4885, 4885);
  RANGE (PORT_PROTOCOL_TCP, 4894, 4894);
  RANGE (PORT_PROTOCOL_TCP, 4899, 4899);
  RANGE (PORT_PROTOCOL_TCP, 4950, 4950);
  RANGE (PORT_PROTOCOL_TCP, 4983, 4983);
  RANGE (PORT_PROTOCOL_TCP, 4987, 4989);
  RANGE (PORT_PROTOCOL_TCP, 4998, 4998);
  RANGE (PORT_PROTOCOL_TCP, 5000, 5011);
  RANGE (PORT_PROTOCOL_TCP, 5020, 5025);
  RANGE (PORT_PROTOCOL_TCP, 5031, 5031);
  RANGE (PORT_PROTOCOL_TCP, 5042, 5042);
  RANGE (PORT_PROTOCOL_TCP, 5050, 5057);
  RANGE (PORT_PROTOCOL_TCP, 5060, 5061);
  RANGE (PORT_PROTOCOL_TCP, 5064, 5066);
  RANGE (PORT_PROTOCOL_TCP, 5069, 5069);
  RANGE (PORT_PROTOCOL_TCP, 5071, 5071);
  RANGE (PORT_PROTOCOL_TCP, 5081, 5081);
  RANGE (PORT_PROTOCOL_TCP, 5093, 5093);
  RANGE (PORT_PROTOCOL_TCP, 5099, 5102);
  RANGE (PORT_PROTOCOL_TCP, 5137, 5137);
  RANGE (PORT_PROTOCOL_TCP, 5145, 5145);
  RANGE (PORT_PROTOCOL_TCP, 5150, 5152);
  RANGE (PORT_PROTOCOL_TCP, 5154, 5154);
  RANGE (PORT_PROTOCOL_TCP, 5165, 5165);
  RANGE (PORT_PROTOCOL_TCP, 5190, 5193);
  RANGE (PORT_PROTOCOL_TCP, 5200, 5203);
  RANGE (PORT_PROTOCOL_TCP, 5222, 5222);
  RANGE (PORT_PROTOCOL_TCP, 5225, 5226);
  RANGE (PORT_PROTOCOL_TCP, 5232, 5232);
  RANGE (PORT_PROTOCOL_TCP, 5236, 5236);
  RANGE (PORT_PROTOCOL_TCP, 5250, 5251);
  RANGE (PORT_PROTOCOL_TCP, 5264, 5265);
  RANGE (PORT_PROTOCOL_TCP, 5269, 5269);
  RANGE (PORT_PROTOCOL_TCP, 5272, 5272);
  RANGE (PORT_PROTOCOL_TCP, 5282, 5282);
  RANGE (PORT_PROTOCOL_TCP, 5300, 5311);
  RANGE (PORT_PROTOCOL_TCP, 5314, 5315);
  RANGE (PORT_PROTOCOL_TCP, 5351, 5355);
  RANGE (PORT_PROTOCOL_TCP, 5400, 5432);
  RANGE (PORT_PROTOCOL_TCP, 5435, 5435);
  RANGE (PORT_PROTOCOL_TCP, 5454, 5456);
  RANGE (PORT_PROTOCOL_TCP, 5461, 5463);
  RANGE (PORT_PROTOCOL_TCP, 5465, 5465);
  RANGE (PORT_PROTOCOL_TCP, 5500, 5504);
  RANGE (PORT_PROTOCOL_TCP, 5510, 5510);
  RANGE (PORT_PROTOCOL_TCP, 5520, 5521);
  RANGE (PORT_PROTOCOL_TCP, 5530, 5530);
  RANGE (PORT_PROTOCOL_TCP, 5540, 5540);
  RANGE (PORT_PROTOCOL_TCP, 5550, 5550);
  RANGE (PORT_PROTOCOL_TCP, 5553, 5556);
  RANGE (PORT_PROTOCOL_TCP, 5566, 5566);
  RANGE (PORT_PROTOCOL_TCP, 5569, 5569);
  RANGE (PORT_PROTOCOL_TCP, 5595, 5605);
  RANGE (PORT_PROTOCOL_TCP, 5631, 5632);
  RANGE (PORT_PROTOCOL_TCP, 5666, 5666);
  RANGE (PORT_PROTOCOL_TCP, 5673, 5680);
  RANGE (PORT_PROTOCOL_TCP, 5688, 5688);
  RANGE (PORT_PROTOCOL_TCP, 5690, 5690);
  RANGE (PORT_PROTOCOL_TCP, 5713, 5717);
  RANGE (PORT_PROTOCOL_TCP, 5720, 5720);
  RANGE (PORT_PROTOCOL_TCP, 5729, 5730);
  RANGE (PORT_PROTOCOL_TCP, 5741, 5742);
  RANGE (PORT_PROTOCOL_TCP, 5745, 5746);
  RANGE (PORT_PROTOCOL_TCP, 5755, 5755);
  RANGE (PORT_PROTOCOL_TCP, 5757, 5757);
  RANGE (PORT_PROTOCOL_TCP, 5766, 5768);
  RANGE (PORT_PROTOCOL_TCP, 5771, 5771);
  RANGE (PORT_PROTOCOL_TCP, 5800, 5803);
  RANGE (PORT_PROTOCOL_TCP, 5813, 5813);
  RANGE (PORT_PROTOCOL_TCP, 5858, 5859);
  RANGE (PORT_PROTOCOL_TCP, 5882, 5882);
  RANGE (PORT_PROTOCOL_TCP, 5888, 5889);
  RANGE (PORT_PROTOCOL_TCP, 5900, 5903);
  RANGE (PORT_PROTOCOL_TCP, 5968, 5969);
  RANGE (PORT_PROTOCOL_TCP, 5977, 5979);
  RANGE (PORT_PROTOCOL_TCP, 5987, 5991);
  RANGE (PORT_PROTOCOL_TCP, 5997, 6010);
  RANGE (PORT_PROTOCOL_TCP, 6050, 6051);
  RANGE (PORT_PROTOCOL_TCP, 6064, 6073);
  RANGE (PORT_PROTOCOL_TCP, 6085, 6085);
  RANGE (PORT_PROTOCOL_TCP, 6100, 6112);
  RANGE (PORT_PROTOCOL_TCP, 6123, 6123);
  RANGE (PORT_PROTOCOL_TCP, 6141, 6150);
  RANGE (PORT_PROTOCOL_TCP, 6175, 6177);
  RANGE (PORT_PROTOCOL_TCP, 6200, 6200);
  RANGE (PORT_PROTOCOL_TCP, 6253, 6253);
  RANGE (PORT_PROTOCOL_TCP, 6255, 6255);
  RANGE (PORT_PROTOCOL_TCP, 6270, 6270);
  RANGE (PORT_PROTOCOL_TCP, 6300, 6300);
  RANGE (PORT_PROTOCOL_TCP, 6321, 6322);
  RANGE (PORT_PROTOCOL_TCP, 6343, 6343);
  RANGE (PORT_PROTOCOL_TCP, 6346, 6347);
  RANGE (PORT_PROTOCOL_TCP, 6373, 6373);
  RANGE (PORT_PROTOCOL_TCP, 6382, 6382);
  RANGE (PORT_PROTOCOL_TCP, 6389, 6389);
  RANGE (PORT_PROTOCOL_TCP, 6400, 6400);
  RANGE (PORT_PROTOCOL_TCP, 6455, 6456);
  RANGE (PORT_PROTOCOL_TCP, 6471, 6471);
  RANGE (PORT_PROTOCOL_TCP, 6500, 6503);
  RANGE (PORT_PROTOCOL_TCP, 6505, 6510);
  RANGE (PORT_PROTOCOL_TCP, 6543, 6543);
  RANGE (PORT_PROTOCOL_TCP, 6547, 6550);
  RANGE (PORT_PROTOCOL_TCP, 6558, 6558);
  RANGE (PORT_PROTOCOL_TCP, 6566, 6566);
  RANGE (PORT_PROTOCOL_TCP, 6580, 6582);
  RANGE (PORT_PROTOCOL_TCP, 6588, 6588);
  RANGE (PORT_PROTOCOL_TCP, 6620, 6621);
  RANGE (PORT_PROTOCOL_TCP, 6623, 6623);
  RANGE (PORT_PROTOCOL_TCP, 6628, 6628);
  RANGE (PORT_PROTOCOL_TCP, 6631, 6631);
  RANGE (PORT_PROTOCOL_TCP, 6665, 6670);
  RANGE (PORT_PROTOCOL_TCP, 6672, 6673);
  RANGE (PORT_PROTOCOL_TCP, 6699, 6701);
  RANGE (PORT_PROTOCOL_TCP, 6714, 6714);
  RANGE (PORT_PROTOCOL_TCP, 6767, 6768);
  RANGE (PORT_PROTOCOL_TCP, 6776, 6776);
  RANGE (PORT_PROTOCOL_TCP, 6788, 6790);
  RANGE (PORT_PROTOCOL_TCP, 6831, 6831);
  RANGE (PORT_PROTOCOL_TCP, 6841, 6842);
  RANGE (PORT_PROTOCOL_TCP, 6850, 6850);
  RANGE (PORT_PROTOCOL_TCP, 6881, 6889);
  RANGE (PORT_PROTOCOL_TCP, 6891, 6891);
  RANGE (PORT_PROTOCOL_TCP, 6901, 6901);
  RANGE (PORT_PROTOCOL_TCP, 6939, 6939);
  RANGE (PORT_PROTOCOL_TCP, 6961, 6966);
  RANGE (PORT_PROTOCOL_TCP, 6969, 6970);
  RANGE (PORT_PROTOCOL_TCP, 6998, 7015);
  RANGE (PORT_PROTOCOL_TCP, 7020, 7021);
  RANGE (PORT_PROTOCOL_TCP, 7030, 7030);
  RANGE (PORT_PROTOCOL_TCP, 7070, 7070);
  RANGE (PORT_PROTOCOL_TCP, 7099, 7100);
  RANGE (PORT_PROTOCOL_TCP, 7121, 7121);
  RANGE (PORT_PROTOCOL_TCP, 7161, 7161);
  RANGE (PORT_PROTOCOL_TCP, 7170, 7170);
  RANGE (PORT_PROTOCOL_TCP, 7174, 7174);
  RANGE (PORT_PROTOCOL_TCP, 7200, 7201);
  RANGE (PORT_PROTOCOL_TCP, 7210, 7210);
  RANGE (PORT_PROTOCOL_TCP, 7269, 7269);
  RANGE (PORT_PROTOCOL_TCP, 7273, 7273);
  RANGE (PORT_PROTOCOL_TCP, 7280, 7281);
  RANGE (PORT_PROTOCOL_TCP, 7283, 7283);
  RANGE (PORT_PROTOCOL_TCP, 7300, 7300);
  RANGE (PORT_PROTOCOL_TCP, 7320, 7320);
  RANGE (PORT_PROTOCOL_TCP, 7326, 7326);
  RANGE (PORT_PROTOCOL_TCP, 7391, 7392);
  RANGE (PORT_PROTOCOL_TCP, 7395, 7395);
  RANGE (PORT_PROTOCOL_TCP, 7426, 7431);
  RANGE (PORT_PROTOCOL_TCP, 7437, 7437);
  RANGE (PORT_PROTOCOL_TCP, 7464, 7464);
  RANGE (PORT_PROTOCOL_TCP, 7491, 7491);
  RANGE (PORT_PROTOCOL_TCP, 7501, 7501);
  RANGE (PORT_PROTOCOL_TCP, 7510, 7511);
  RANGE (PORT_PROTOCOL_TCP, 7544, 7545);
  RANGE (PORT_PROTOCOL_TCP, 7560, 7560);
  RANGE (PORT_PROTOCOL_TCP, 7566, 7566);
  RANGE (PORT_PROTOCOL_TCP, 7570, 7570);
  RANGE (PORT_PROTOCOL_TCP, 7575, 7575);
  RANGE (PORT_PROTOCOL_TCP, 7588, 7588);
  RANGE (PORT_PROTOCOL_TCP, 7597, 7597);
  RANGE (PORT_PROTOCOL_TCP, 7624, 7624);
  RANGE (PORT_PROTOCOL_TCP, 7626, 7627);
  RANGE (PORT_PROTOCOL_TCP, 7633, 7634);
  RANGE (PORT_PROTOCOL_TCP, 7648, 7649);
  RANGE (PORT_PROTOCOL_TCP, 7666, 7666);
  RANGE (PORT_PROTOCOL_TCP, 7674, 7676);
  RANGE (PORT_PROTOCOL_TCP, 7743, 7743);
  RANGE (PORT_PROTOCOL_TCP, 7775, 7779);
  RANGE (PORT_PROTOCOL_TCP, 7781, 7781);
  RANGE (PORT_PROTOCOL_TCP, 7786, 7786);
  RANGE (PORT_PROTOCOL_TCP, 7797, 7798);
  RANGE (PORT_PROTOCOL_TCP, 7800, 7801);
  RANGE (PORT_PROTOCOL_TCP, 7845, 7846);
  RANGE (PORT_PROTOCOL_TCP, 7875, 7875);
  RANGE (PORT_PROTOCOL_TCP, 7902, 7902);
  RANGE (PORT_PROTOCOL_TCP, 7913, 7913);
  RANGE (PORT_PROTOCOL_TCP, 7932, 7933);
  RANGE (PORT_PROTOCOL_TCP, 7967, 7967);
  RANGE (PORT_PROTOCOL_TCP, 7979, 7980);
  RANGE (PORT_PROTOCOL_TCP, 7999, 8005);
  RANGE (PORT_PROTOCOL_TCP, 8007, 8010);
  RANGE (PORT_PROTOCOL_TCP, 8022, 8022);
  RANGE (PORT_PROTOCOL_TCP, 8032, 8033);
  RANGE (PORT_PROTOCOL_TCP, 8044, 8044);
  RANGE (PORT_PROTOCOL_TCP, 8074, 8074);
  RANGE (PORT_PROTOCOL_TCP, 8080, 8082);
  RANGE (PORT_PROTOCOL_TCP, 8088, 8089);
  RANGE (PORT_PROTOCOL_TCP, 8098, 8098);
  RANGE (PORT_PROTOCOL_TCP, 8100, 8100);
  RANGE (PORT_PROTOCOL_TCP, 8115, 8116);
  RANGE (PORT_PROTOCOL_TCP, 8118, 8118);
  RANGE (PORT_PROTOCOL_TCP, 8121, 8122);
  RANGE (PORT_PROTOCOL_TCP, 8130, 8132);
  RANGE (PORT_PROTOCOL_TCP, 8160, 8161);
  RANGE (PORT_PROTOCOL_TCP, 8181, 8194);
  RANGE (PORT_PROTOCOL_TCP, 8199, 8201);
  RANGE (PORT_PROTOCOL_TCP, 8204, 8208);
  RANGE (PORT_PROTOCOL_TCP, 8224, 8225);
  RANGE (PORT_PROTOCOL_TCP, 8245, 8245);
  RANGE (PORT_PROTOCOL_TCP, 8311, 8311);
  RANGE (PORT_PROTOCOL_TCP, 8351, 8351);
  RANGE (PORT_PROTOCOL_TCP, 8376, 8380);
  RANGE (PORT_PROTOCOL_TCP, 8400, 8403);
  RANGE (PORT_PROTOCOL_TCP, 8416, 8417);
  RANGE (PORT_PROTOCOL_TCP, 8431, 8431);
  RANGE (PORT_PROTOCOL_TCP, 8443, 8444);
  RANGE (PORT_PROTOCOL_TCP, 8450, 8450);
  RANGE (PORT_PROTOCOL_TCP, 8473, 8473);
  RANGE (PORT_PROTOCOL_TCP, 8554, 8555);
  RANGE (PORT_PROTOCOL_TCP, 8649, 8649);
  RANGE (PORT_PROTOCOL_TCP, 8733, 8733);
  RANGE (PORT_PROTOCOL_TCP, 8763, 8765);
  RANGE (PORT_PROTOCOL_TCP, 8786, 8787);
  RANGE (PORT_PROTOCOL_TCP, 8804, 8804);
  RANGE (PORT_PROTOCOL_TCP, 8863, 8864);
  RANGE (PORT_PROTOCOL_TCP, 8875, 8875);
  RANGE (PORT_PROTOCOL_TCP, 8880, 8880);
  RANGE (PORT_PROTOCOL_TCP, 8888, 8894);
  RANGE (PORT_PROTOCOL_TCP, 8900, 8901);
  RANGE (PORT_PROTOCOL_TCP, 8910, 8911);
  RANGE (PORT_PROTOCOL_TCP, 8954, 8954);
  RANGE (PORT_PROTOCOL_TCP, 8989, 8989);
  RANGE (PORT_PROTOCOL_TCP, 8999, 9002);
  RANGE (PORT_PROTOCOL_TCP, 9006, 9006);
  RANGE (PORT_PROTOCOL_TCP, 9009, 9009);
  RANGE (PORT_PROTOCOL_TCP, 9020, 9026);
  RANGE (PORT_PROTOCOL_TCP, 9080, 9080);
  RANGE (PORT_PROTOCOL_TCP, 9090, 9091);
  RANGE (PORT_PROTOCOL_TCP, 9100, 9103);
  RANGE (PORT_PROTOCOL_TCP, 9110, 9111);
  RANGE (PORT_PROTOCOL_TCP, 9131, 9131);
  RANGE (PORT_PROTOCOL_TCP, 9152, 9152);
  RANGE (PORT_PROTOCOL_TCP, 9160, 9164);
  RANGE (PORT_PROTOCOL_TCP, 9200, 9207);
  RANGE (PORT_PROTOCOL_TCP, 9210, 9211);
  RANGE (PORT_PROTOCOL_TCP, 9217, 9217);
  RANGE (PORT_PROTOCOL_TCP, 9281, 9285);
  RANGE (PORT_PROTOCOL_TCP, 9287, 9287);
  RANGE (PORT_PROTOCOL_TCP, 9292, 9292);
  RANGE (PORT_PROTOCOL_TCP, 9321, 9321);
  RANGE (PORT_PROTOCOL_TCP, 9343, 9344);
  RANGE (PORT_PROTOCOL_TCP, 9346, 9346);
  RANGE (PORT_PROTOCOL_TCP, 9374, 9374);
  RANGE (PORT_PROTOCOL_TCP, 9390, 9390);
  RANGE (PORT_PROTOCOL_TCP, 9396, 9397);
  RANGE (PORT_PROTOCOL_TCP, 9400, 9400);
  RANGE (PORT_PROTOCOL_TCP, 9418, 9418);
  RANGE (PORT_PROTOCOL_TCP, 9495, 9495);
  RANGE (PORT_PROTOCOL_TCP, 9500, 9500);
  RANGE (PORT_PROTOCOL_TCP, 9535, 9537);
  RANGE (PORT_PROTOCOL_TCP, 9593, 9595);
  RANGE (PORT_PROTOCOL_TCP, 9600, 9600);
  RANGE (PORT_PROTOCOL_TCP, 9612, 9612);
  RANGE (PORT_PROTOCOL_TCP, 9704, 9704);
  RANGE (PORT_PROTOCOL_TCP, 9747, 9747);
  RANGE (PORT_PROTOCOL_TCP, 9753, 9753);
  RANGE (PORT_PROTOCOL_TCP, 9797, 9797);
  RANGE (PORT_PROTOCOL_TCP, 9800, 9802);
  RANGE (PORT_PROTOCOL_TCP, 9872, 9872);
  RANGE (PORT_PROTOCOL_TCP, 9875, 9876);
  RANGE (PORT_PROTOCOL_TCP, 9888, 9889);
  RANGE (PORT_PROTOCOL_TCP, 9898, 9901);
  RANGE (PORT_PROTOCOL_TCP, 9909, 9909);
  RANGE (PORT_PROTOCOL_TCP, 9911, 9911);
  RANGE (PORT_PROTOCOL_TCP, 9950, 9952);
  RANGE (PORT_PROTOCOL_TCP, 9990, 10005);
  RANGE (PORT_PROTOCOL_TCP, 10007, 10008);
  RANGE (PORT_PROTOCOL_TCP, 10012, 10012);
  RANGE (PORT_PROTOCOL_TCP, 10080, 10083);
  RANGE (PORT_PROTOCOL_TCP, 10101, 10103);
  RANGE (PORT_PROTOCOL_TCP, 10113, 10116);
  RANGE (PORT_PROTOCOL_TCP, 10128, 10128);
  RANGE (PORT_PROTOCOL_TCP, 10252, 10252);
  RANGE (PORT_PROTOCOL_TCP, 10260, 10260);
  RANGE (PORT_PROTOCOL_TCP, 10288, 10288);
  RANGE (PORT_PROTOCOL_TCP, 10607, 10607);
  RANGE (PORT_PROTOCOL_TCP, 10666, 10666);
  RANGE (PORT_PROTOCOL_TCP, 10752, 10752);
  RANGE (PORT_PROTOCOL_TCP, 10990, 10990);
  RANGE (PORT_PROTOCOL_TCP, 11000, 11001);
  RANGE (PORT_PROTOCOL_TCP, 11111, 11111);
  RANGE (PORT_PROTOCOL_TCP, 11201, 11201);
  RANGE (PORT_PROTOCOL_TCP, 11223, 11223);
  RANGE (PORT_PROTOCOL_TCP, 11319, 11321);
  RANGE (PORT_PROTOCOL_TCP, 11367, 11367);
  RANGE (PORT_PROTOCOL_TCP, 11371, 11371);
  RANGE (PORT_PROTOCOL_TCP, 11600, 11600);
  RANGE (PORT_PROTOCOL_TCP, 11720, 11720);
  RANGE (PORT_PROTOCOL_TCP, 11751, 11751);
  RANGE (PORT_PROTOCOL_TCP, 11965, 11965);
  RANGE (PORT_PROTOCOL_TCP, 11967, 11967);
  RANGE (PORT_PROTOCOL_TCP, 11999, 12006);
  RANGE (PORT_PROTOCOL_TCP, 12076, 12076);
  RANGE (PORT_PROTOCOL_TCP, 12109, 12109);
  RANGE (PORT_PROTOCOL_TCP, 12168, 12168);
  RANGE (PORT_PROTOCOL_TCP, 12172, 12172);
  RANGE (PORT_PROTOCOL_TCP, 12223, 12223);
  RANGE (PORT_PROTOCOL_TCP, 12321, 12321);
  RANGE (PORT_PROTOCOL_TCP, 12345, 12346);
  RANGE (PORT_PROTOCOL_TCP, 12361, 12362);
  RANGE (PORT_PROTOCOL_TCP, 12468, 12468);
  RANGE (PORT_PROTOCOL_TCP, 12701, 12701);
  RANGE (PORT_PROTOCOL_TCP, 12753, 12753);
  RANGE (PORT_PROTOCOL_TCP, 13160, 13160);
  RANGE (PORT_PROTOCOL_TCP, 13223, 13224);
  RANGE (PORT_PROTOCOL_TCP, 13701, 13702);
  RANGE (PORT_PROTOCOL_TCP, 13705, 13706);
  RANGE (PORT_PROTOCOL_TCP, 13708, 13718);
  RANGE (PORT_PROTOCOL_TCP, 13720, 13722);
  RANGE (PORT_PROTOCOL_TCP, 13724, 13724);
  RANGE (PORT_PROTOCOL_TCP, 13782, 13783);
  RANGE (PORT_PROTOCOL_TCP, 13818, 13822);
  RANGE (PORT_PROTOCOL_TCP, 14001, 14001);
  RANGE (PORT_PROTOCOL_TCP, 14033, 14034);
  RANGE (PORT_PROTOCOL_TCP, 14141, 14141);
  RANGE (PORT_PROTOCOL_TCP, 14145, 14145);
  RANGE (PORT_PROTOCOL_TCP, 14149, 14149);
  RANGE (PORT_PROTOCOL_TCP, 14194, 14194);
  RANGE (PORT_PROTOCOL_TCP, 14237, 14237);
  RANGE (PORT_PROTOCOL_TCP, 14936, 14937);
  RANGE (PORT_PROTOCOL_TCP, 15000, 15000);
  RANGE (PORT_PROTOCOL_TCP, 15126, 15126);
  RANGE (PORT_PROTOCOL_TCP, 15345, 15345);
  RANGE (PORT_PROTOCOL_TCP, 15363, 15363);
  RANGE (PORT_PROTOCOL_TCP, 16360, 16361);
  RANGE (PORT_PROTOCOL_TCP, 16367, 16368);
  RANGE (PORT_PROTOCOL_TCP, 16384, 16384);
  RANGE (PORT_PROTOCOL_TCP, 16660, 16661);
  RANGE (PORT_PROTOCOL_TCP, 16959, 16959);
  RANGE (PORT_PROTOCOL_TCP, 16969, 16969);
  RANGE (PORT_PROTOCOL_TCP, 16991, 16991);
  RANGE (PORT_PROTOCOL_TCP, 17007, 17007);
  RANGE (PORT_PROTOCOL_TCP, 17185, 17185);
  RANGE (PORT_PROTOCOL_TCP, 17219, 17219);
  RANGE (PORT_PROTOCOL_TCP, 17300, 17300);
  RANGE (PORT_PROTOCOL_TCP, 17770, 17772);
  RANGE (PORT_PROTOCOL_TCP, 18000, 18000);
  RANGE (PORT_PROTOCOL_TCP, 18181, 18187);
  RANGE (PORT_PROTOCOL_TCP, 18190, 18190);
  RANGE (PORT_PROTOCOL_TCP, 18241, 18241);
  RANGE (PORT_PROTOCOL_TCP, 18463, 18463);
  RANGE (PORT_PROTOCOL_TCP, 18769, 18769);
  RANGE (PORT_PROTOCOL_TCP, 18888, 18888);
  RANGE (PORT_PROTOCOL_TCP, 19191, 19191);
  RANGE (PORT_PROTOCOL_TCP, 19194, 19194);
  RANGE (PORT_PROTOCOL_TCP, 19283, 19283);
  RANGE (PORT_PROTOCOL_TCP, 19315, 19315);
  RANGE (PORT_PROTOCOL_TCP, 19398, 19398);
  RANGE (PORT_PROTOCOL_TCP, 19410, 19412);
  RANGE (PORT_PROTOCOL_TCP, 19540, 19541);
  RANGE (PORT_PROTOCOL_TCP, 19638, 19638);
  RANGE (PORT_PROTOCOL_TCP, 19726, 19726);
  RANGE (PORT_PROTOCOL_TCP, 20000, 20001);
  RANGE (PORT_PROTOCOL_TCP, 20005, 20005);
  RANGE (PORT_PROTOCOL_TCP, 20011, 20012);
  RANGE (PORT_PROTOCOL_TCP, 20034, 20034);
  RANGE (PORT_PROTOCOL_TCP, 20200, 20200);
  RANGE (PORT_PROTOCOL_TCP, 20202, 20203);
  RANGE (PORT_PROTOCOL_TCP, 20222, 20222);
  RANGE (PORT_PROTOCOL_TCP, 20670, 20670);
  RANGE (PORT_PROTOCOL_TCP, 20999, 21000);
  RANGE (PORT_PROTOCOL_TCP, 21490, 21490);
  RANGE (PORT_PROTOCOL_TCP, 21544, 21544);
  RANGE (PORT_PROTOCOL_TCP, 21590, 21590);
  RANGE (PORT_PROTOCOL_TCP, 21800, 21800);
  RANGE (PORT_PROTOCOL_TCP, 21845, 21849);
  RANGE (PORT_PROTOCOL_TCP, 22000, 22001);
  RANGE (PORT_PROTOCOL_TCP, 22222, 22222);
  RANGE (PORT_PROTOCOL_TCP, 22273, 22273);
  RANGE (PORT_PROTOCOL_TCP, 22289, 22289);
  RANGE (PORT_PROTOCOL_TCP, 22305, 22305);
  RANGE (PORT_PROTOCOL_TCP, 22321, 22321);
  RANGE (PORT_PROTOCOL_TCP, 22370, 22370);
  RANGE (PORT_PROTOCOL_TCP, 22555, 22555);
  RANGE (PORT_PROTOCOL_TCP, 22800, 22800);
  RANGE (PORT_PROTOCOL_TCP, 22951, 22951);
  RANGE (PORT_PROTOCOL_TCP, 23456, 23456);
  RANGE (PORT_PROTOCOL_TCP, 24000, 24006);
  RANGE (PORT_PROTOCOL_TCP, 24242, 24242);
  RANGE (PORT_PROTOCOL_TCP, 24249, 24249);
  RANGE (PORT_PROTOCOL_TCP, 24345, 24347);
  RANGE (PORT_PROTOCOL_TCP, 24386, 24386);
  RANGE (PORT_PROTOCOL_TCP, 24554, 24554);
  RANGE (PORT_PROTOCOL_TCP, 24677, 24678);
  RANGE (PORT_PROTOCOL_TCP, 24922, 24922);
  RANGE (PORT_PROTOCOL_TCP, 25000, 25009);
  RANGE (PORT_PROTOCOL_TCP, 25378, 25378);
  RANGE (PORT_PROTOCOL_TCP, 25544, 25544);
  RANGE (PORT_PROTOCOL_TCP, 25793, 25793);
  RANGE (PORT_PROTOCOL_TCP, 25867, 25867);
  RANGE (PORT_PROTOCOL_TCP, 25901, 25901);
  RANGE (PORT_PROTOCOL_TCP, 25903, 25903);
  RANGE (PORT_PROTOCOL_TCP, 26000, 26000);
  RANGE (PORT_PROTOCOL_TCP, 26208, 26208);
  RANGE (PORT_PROTOCOL_TCP, 26260, 26264);
  RANGE (PORT_PROTOCOL_TCP, 27000, 27010);
  RANGE (PORT_PROTOCOL_TCP, 27345, 27345);
  RANGE (PORT_PROTOCOL_TCP, 27374, 27374);
  RANGE (PORT_PROTOCOL_TCP, 27504, 27504);
  RANGE (PORT_PROTOCOL_TCP, 27665, 27665);
  RANGE (PORT_PROTOCOL_TCP, 27999, 27999);
  RANGE (PORT_PROTOCOL_TCP, 28001, 28001);
  RANGE (PORT_PROTOCOL_TCP, 29559, 29559);
  RANGE (PORT_PROTOCOL_TCP, 29891, 29891);
  RANGE (PORT_PROTOCOL_TCP, 30001, 30002);
  RANGE (PORT_PROTOCOL_TCP, 30100, 30102);
  RANGE (PORT_PROTOCOL_TCP, 30303, 30303);
  RANGE (PORT_PROTOCOL_TCP, 30999, 30999);
  RANGE (PORT_PROTOCOL_TCP, 31337, 31337);
  RANGE (PORT_PROTOCOL_TCP, 31339, 31339);
  RANGE (PORT_PROTOCOL_TCP, 31416, 31416);
  RANGE (PORT_PROTOCOL_TCP, 31457, 31457);
  RANGE (PORT_PROTOCOL_TCP, 31554, 31554);
  RANGE (PORT_PROTOCOL_TCP, 31556, 31556);
  RANGE (PORT_PROTOCOL_TCP, 31620, 31620);
  RANGE (PORT_PROTOCOL_TCP, 31765, 31765);
  RANGE (PORT_PROTOCOL_TCP, 31785, 31787);
  RANGE (PORT_PROTOCOL_TCP, 32261, 32261);
  RANGE (PORT_PROTOCOL_TCP, 32666, 32666);
  RANGE (PORT_PROTOCOL_TCP, 32768, 32780);
  RANGE (PORT_PROTOCOL_TCP, 32786, 32787);
  RANGE (PORT_PROTOCOL_TCP, 32896, 32896);
  RANGE (PORT_PROTOCOL_TCP, 33270, 33270);
  RANGE (PORT_PROTOCOL_TCP, 33331, 33331);
  RANGE (PORT_PROTOCOL_TCP, 33434, 33434);
  RANGE (PORT_PROTOCOL_TCP, 33911, 33911);
  RANGE (PORT_PROTOCOL_TCP, 34249, 34249);
  RANGE (PORT_PROTOCOL_TCP, 34324, 34324);
  RANGE (PORT_PROTOCOL_TCP, 34952, 34952);
  RANGE (PORT_PROTOCOL_TCP, 36865, 36865);
  RANGE (PORT_PROTOCOL_TCP, 37475, 37475);
  RANGE (PORT_PROTOCOL_TCP, 37651, 37651);
  RANGE (PORT_PROTOCOL_TCP, 38037, 38037);
  RANGE (PORT_PROTOCOL_TCP, 38201, 38201);
  RANGE (PORT_PROTOCOL_TCP, 38292, 38293);
  RANGE (PORT_PROTOCOL_TCP, 39681, 39681);
  RANGE (PORT_PROTOCOL_TCP, 40412, 40412);
  RANGE (PORT_PROTOCOL_TCP, 40841, 40843);
  RANGE (PORT_PROTOCOL_TCP, 41111, 41111);
  RANGE (PORT_PROTOCOL_TCP, 41508, 41508);
  RANGE (PORT_PROTOCOL_TCP, 41794, 41795);
  RANGE (PORT_PROTOCOL_TCP, 42508, 42510);
  RANGE (PORT_PROTOCOL_TCP, 43118, 43118);
  RANGE (PORT_PROTOCOL_TCP, 43188, 43190);
  RANGE (PORT_PROTOCOL_TCP, 44321, 44322);
  RANGE (PORT_PROTOCOL_TCP, 44333, 44334);
  RANGE (PORT_PROTOCOL_TCP, 44442, 44443);
  RANGE (PORT_PROTOCOL_TCP, 44818, 44818);
  RANGE (PORT_PROTOCOL_TCP, 45000, 45000);
  RANGE (PORT_PROTOCOL_TCP, 45054, 45054);
  RANGE (PORT_PROTOCOL_TCP, 45678, 45678);
  RANGE (PORT_PROTOCOL_TCP, 45966, 45966);
  RANGE (PORT_PROTOCOL_TCP, 47000, 47000);
  RANGE (PORT_PROTOCOL_TCP, 47557, 47557);
  RANGE (PORT_PROTOCOL_TCP, 47624, 47624);
  RANGE (PORT_PROTOCOL_TCP, 47806, 47806);
  RANGE (PORT_PROTOCOL_TCP, 47808, 47808);
  RANGE (PORT_PROTOCOL_TCP, 47891, 47891);
  RANGE (PORT_PROTOCOL_TCP, 48000, 48003);
  RANGE (PORT_PROTOCOL_TCP, 48556, 48556);
  RANGE (PORT_PROTOCOL_TCP, 49400, 49400);
  RANGE (PORT_PROTOCOL_TCP, 50000, 50004);
  RANGE (PORT_PROTOCOL_TCP, 50505, 50505);
  RANGE (PORT_PROTOCOL_TCP, 50776, 50776);
  RANGE (PORT_PROTOCOL_TCP, 51210, 51210);
  RANGE (PORT_PROTOCOL_TCP, 53001, 53001);
  RANGE (PORT_PROTOCOL_TCP, 54320, 54321);
  RANGE (PORT_PROTOCOL_TCP, 57341, 57341);
  RANGE (PORT_PROTOCOL_TCP, 59595, 59595);
  RANGE (PORT_PROTOCOL_TCP, 60177, 60177);
  RANGE (PORT_PROTOCOL_TCP, 60179, 60179);
  RANGE (PORT_PROTOCOL_TCP, 61439, 61441);
  RANGE (PORT_PROTOCOL_TCP, 61446, 61446);
  RANGE (PORT_PROTOCOL_TCP, 65000, 65000);
  RANGE (PORT_PROTOCOL_TCP, 65301, 65301);
}

/**
 * @brief Ensure the predefined example task and report exists.
 */
static void
check_db_tasks ()
{
  if (sql_int ("SELECT count(*) FROM tasks"
               " WHERE uuid = '" MANAGE_EXAMPLE_TASK_UUID "';")
      == 0)
    {
      sql ("INSERT into tasks (uuid, owner, name, hidden, comment,"
           " run_status, start_time, end_time, config, target, scanner, slave,"
           " alterable, creation_time, modification_time)"
           " VALUES ('" MANAGE_EXAMPLE_TASK_UUID "', NULL, 'Example task',"
           " 1, 'This is an example task for the help pages.', %u,"
           " 1251236905, 1251237136,"
           " (SELECT id FROM configs WHERE name = 'Full and fast'),"
           " (SELECT id FROM targets WHERE name = 'Localhost'),"
           " (SELECT id FROM scanners WHERE uuid = '" SCANNER_UUID_DEFAULT "'),"
           " 0, 0, m_now (), m_now ());",
           TASK_STATUS_DONE);
    }

  if (sql_int ("SELECT count(*) FROM reports"
               " WHERE uuid = '343435d6-91b0-11de-9478-ffd71f4c6f30';")
      == 0)
    {
      task_t task;
      result_t result;
      report_t report;

      /* Setup a dummy user, so that find_task will work. */
      current_credentials.uuid = "";

      if (find_task (MANAGE_EXAMPLE_TASK_UUID, &task))
        g_warning ("%s: error while finding example task", __FUNCTION__);
      else if (task == 0)
        g_warning ("%s: failed to find example task", __FUNCTION__);
      else
        {
          sql ("INSERT into reports (uuid, owner, hidden, task, comment,"
               " start_time, end_time, scan_run_status, slave_progress,"
               " slave_task_uuid)"
               " VALUES ('343435d6-91b0-11de-9478-ffd71f4c6f30', NULL, 1, %llu,"
               " 'This is an example report for the help pages.',"
               " 1251236905, 1251237136,"
               " %u, 0, '');",
               task, TASK_STATUS_DONE);
          report = sql_last_insert_id ();
          sql ("INSERT into results (owner, date, uuid, task, host, port, nvt,"
               " type, severity, description, qod)"
               " VALUES (NULL, m_now(), 'cb291ec0-1b0d-11df-8aa1-002264764cea',"
               " %llu, '127.0.0.1', 'telnet (23/tcp)',"
               " '1.3.6.1.4.1.25623.1.0.10330', 'Security Note', 2.0,"
               " 'A telnet server seems to be running on this port',"
               " '" G_STRINGIFY (QOD_DEFAULT) "');",
               task);
          result = sql_last_insert_id ();
          report_add_result (report, result);
          manage_report_host_add (report, "127.0.0.1", 1251236906, 1251237135);
        }
      current_credentials.uuid = NULL;
    }
}

/**
 * @brief Ensure the predefined target exists.
 */
static void
check_db_targets ()
{
  if (sql_int ("SELECT count(*) FROM targets WHERE name = 'Localhost';")
      == 0)
    sql ("INSERT INTO targets"
         " (uuid, owner, name, hosts, creation_time, modification_time,"
         "  port_range)"
         " VALUES ('" TARGET_UUID_LOCALHOST "', NULL, 'Localhost',"
         " 'localhost', m_now (), m_now (),"
         " (SELECT id FROM port_lists WHERE uuid = '" PORT_LIST_UUID_DEFAULT "'));");
  else
    /* The port list was wrong for a while, so make sure it's correct. */
    sql ("UPDATE targets SET port_range = "
         " (SELECT id FROM port_lists"
         "  WHERE uuid = '" PORT_LIST_UUID_DEFAULT "')"
         " WHERE uuid = '" TARGET_UUID_LOCALHOST "';");

}

/**
 * @brief Ensure the predefined scanner exists.
 *
 * @return 0 if success, -1 if error.
 */
static int
check_db_scanners ()
{
  if (sql_int ("SELECT count(*) FROM scanners WHERE uuid = '%s';",
               SCANNER_UUID_DEFAULT) == 0)
    {
      char *ca_pub, *key_pub, *key_priv;
      char *quoted_ca_pub, *quoted_key_pub, *quoted_key_priv;
      GError *error = NULL;

      if (!g_file_get_contents (CACERT, &ca_pub, NULL, &error))
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          return -1;
        }
      if (!g_file_get_contents (CLIENTCERT, &key_pub, NULL, &error))
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          g_free (ca_pub);
          return -1;
        }
      if (!g_file_get_contents (CLIENTKEY, &key_priv, NULL, &error))
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          g_free (ca_pub);
          g_free (key_pub);
          return -1;
        }
      quoted_ca_pub = sql_quote (ca_pub);
      quoted_key_pub = sql_quote (key_pub);
      quoted_key_priv = sql_quote (key_priv);
      g_free (ca_pub);
      g_free (key_pub);
      g_free (key_priv);

      sql ("INSERT INTO scanners"
           " (uuid, owner, name, host, port, type, ca_pub, key_pub, key_priv,"
           "  creation_time, modification_time)"
           " VALUES ('" SCANNER_UUID_DEFAULT "', NULL, 'OpenVAS Default',"
           " 'localhost', 9391, %d, '%s', '%s', '%s', m_now (), m_now ());",
           SCANNER_TYPE_OPENVAS, quoted_ca_pub, quoted_key_pub,
           quoted_key_priv);
      g_free (quoted_ca_pub);
      g_free (quoted_key_pub);
      g_free (quoted_key_priv);
    }
  return 0;
}

/**
 * @brief Ensure that the predefined port lists exist.
 */
static void
check_db_port_lists ()
{
  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_DEFAULT "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_DEFAULT "', NULL, 'OpenVAS Default',"
           " '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_openvas_default (list);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_TCP "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_TCP "', NULL, 'All TCP',"
           " '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      RANGE (PORT_PROTOCOL_TCP, 1, 65535);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_100 "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_100 "', NULL,"
           " 'All TCP and Nmap 5.51 top 100 UDP', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_all_tcp_nmap_5_51_top_100 (list);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_1000 "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_1000 "', NULL,"
           " 'All TCP and Nmap 5.51 top 1000 UDP', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_all_tcp_nmap_5_51_top_1000 (list);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_PRIV_TCP "', NULL,"
           " 'All privileged TCP', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      RANGE (PORT_PROTOCOL_TCP, 1, 1023);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP_UDP "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_PRIV_TCP_UDP "', NULL,"
           " 'All privileged TCP and UDP', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      RANGE (PORT_PROTOCOL_TCP, 1, 1023);
      RANGE (PORT_PROTOCOL_UDP, 1, 1023);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_2012 "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_IANA_TCP_2012 "', NULL,"
           " 'All IANA assigned TCP 2012-02-10', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_iana_tcp_2012 (list);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_UDP_2012 "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_ALL_IANA_TCP_UDP_2012 "', NULL,"
           " 'All IANA assigned TCP and UDP 2012-02-10', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_iana_tcp_udp_2012 (list);
    }

  if (sql_int ("SELECT count(*) FROM port_lists"
               " WHERE uuid = '" PORT_LIST_UUID_NMAP_5_51_TOP_2000_TOP_100 "';")
      == 0)
    {
      port_list_t list;
      sql ("INSERT INTO port_lists (uuid, owner, name, comment, creation_time,"
           "                        modification_time)"
           " VALUES ('" PORT_LIST_UUID_NMAP_5_51_TOP_2000_TOP_100 "', NULL,"
           " 'Nmap 5.51 top 2000 TCP and top 100 UDP', '', m_now (), m_now ())");
      list = sql_last_insert_id ();
      make_port_ranges_nmap_5_51_top_2000_top_100 (list);
    }
  /*
   * Ensure that the highest number in a port range is 65535.  At some point
   * ranges were initialised to 65536.
   *
   * This should be a migrator, but this way is easier to backport.  */
  sql ("UPDATE port_ranges SET \"end\" = 65535 WHERE \"end\" = 65536;");
  sql ("UPDATE port_ranges SET start = 65535 WHERE start = 65536;");
}

/**
 * @brief Bring UUIDs for single report format up to date.
 *
 * @param[in]  old  Old UUID.
 * @param[in]  new  New UUID.
 */
static void
update_report_format_uuid (const char *old, const char *new)
{
  gchar *dir;

  dir = g_build_filename (OPENVAS_DATA_DIR,
                          "openvasmd",
                          "global_report_formats",
                          old,
                          NULL);

  if (g_file_test (dir, G_FILE_TEST_EXISTS))
    openvas_file_remove_recurse (dir);
  g_free (dir);

  sql ("UPDATE report_formats"
       " SET uuid = '%s', modification_time = m_now ()"
       " WHERE uuid = '%s';",
       new,
       old);

  sql ("UPDATE alert_method_data"
       " SET data = '%s'"
       " WHERE data = '%s';",
       new,
       old);
}

/**
 * @brief Bring report format UUIDs in database up to date.
 */
static void
update_report_format_uuids ()
{
  /* Same as migrate_58_to_59, to enable backporting r13519 to OpenVAS-5
   * without backporting the 58 to 59 migrator.  In future these should be
   * done here instead of in a migrator. */

  update_report_format_uuid ("a0704abb-2120-489f-959f-251c9f4ffebd",
                             "5ceff8ba-1f62-11e1-ab9f-406186ea4fc5");

  update_report_format_uuid ("b993b6f5-f9fb-4e6e-9c94-dd46c00e058d",
                             "6c248850-1f62-11e1-b082-406186ea4fc5");

  update_report_format_uuid ("929884c6-c2c4-41e7-befb-2f6aa163b458",
                             "77bd6c4a-1f62-11e1-abf0-406186ea4fc5");

  update_report_format_uuid ("9f1ab17b-aaaa-411a-8c57-12df446f5588",
                             "7fcc3a1a-1f62-11e1-86bf-406186ea4fc5");

  update_report_format_uuid ("f5c2a364-47d2-4700-b21d-0a7693daddab",
                             "9ca6fe72-1f62-11e1-9e7c-406186ea4fc5");

  update_report_format_uuid ("1a60a67e-97d0-4cbf-bc77-f71b08e7043d",
                             "a0b5bfb2-1f62-11e1-85db-406186ea4fc5");

  update_report_format_uuid ("19f6f1b3-7128-4433-888c-ccc764fe6ed5",
                             "a3810a62-1f62-11e1-9219-406186ea4fc5");

  update_report_format_uuid ("d5da9f67-8551-4e51-807b-b6a873d70e34",
                             "a994b278-1f62-11e1-96ac-406186ea4fc5");

  /* New updates go here.  Oldest must come first, so add at the end. */

  update_report_format_uuid ("7fcc3a1a-1f62-11e1-86bf-406186ea4fc5",
                             "a684c02c-b531-11e1-bdc2-406186ea4fc5");

  update_report_format_uuid ("a0b5bfb2-1f62-11e1-85db-406186ea4fc5",
                             "c402cc3e-b531-11e1-9163-406186ea4fc5");
}

/**
 * @brief Initialize the default settings.
 *
 * Ensure all the default manager settings exist.
 */
static void
check_db_settings ()
{
  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '6765549a-934e-11e3-b358-406186ea4fc5'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('6765549a-934e-11e3-b358-406186ea4fc5', NULL,"
         "  'User Interface Language',"
         "  'Preferred language to be used in client user interfaces.',"
         "  'Browser Language');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '5f5a8712-8017-11e1-8556-406186ea4fc5'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('5f5a8712-8017-11e1-8556-406186ea4fc5', NULL, 'Rows Per Page',"
         "  'The default number of rows displayed in any listing.',"
         "  10);");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '20f3034c-e709-11e1-87e7-406186ea4fc5'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('20f3034c-e709-11e1-87e7-406186ea4fc5', NULL, 'Wizard Rows',"
         "  'If the number of rows in a listing is above this any wizard"
         " be hidden.',"
         "  3);");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = 'f16bb236-a32d-4cd5-a880-e0fcf2599f59'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('f16bb236-a32d-4cd5-a880-e0fcf2599f59', NULL, 'Severity Class',"
         "  'Severity class used for severity bars.',"
         "  'nist');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '77ec2444-e7f2-4a80-a59b-f4237782d93f'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('77ec2444-e7f2-4a80-a59b-f4237782d93f', NULL, 'Dynamic Severity',"
         "  'Whether to use dynamic severity scores by default.',"
         "  '0');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '578a1c14-e2dc-45ef-a591-89d31391d007'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('578a1c14-e2dc-45ef-a591-89d31391d007', NULL, 'Auto-Refresh',"
         "  'The delay between automatic page refreshs in seconds.',"
         "  '0');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = 'a6ac88c5-729c-41ba-ac0a-deea4a3441f2'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('a6ac88c5-729c-41ba-ac0a-deea4a3441f2', NULL,"
         "  'Details Export File Name',"
         "  'File name format string for the export of resource details.',"
         "  '%T-%U');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '0872a6ed-4f85-48c5-ac3f-a5ef5e006745'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('0872a6ed-4f85-48c5-ac3f-a5ef5e006745', NULL,"
         "  'List Export File Name',"
         "  'File name format string for the export of resource lists.',"
         "  '%T-%D');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = 'e1a2ae0b-736e-4484-b029-330c9e15b900'"
               " AND owner IS NULL;")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('e1a2ae0b-736e-4484-b029-330c9e15b900', NULL,"
         "  'Report Export File Name',"
         "  'File name format string for the export of reports.',"
         "  '%T-%U');");

}

/**
 * @brief Add permission to role.
 *
 * Caller must ensure args are SQL escaped.
 *
 * @param[in]  role        Role.
 * @param[in]  permission  Permission.
 */
void
add_role_permission (const gchar *role, const gchar *permission)
{
  sql ("INSERT INTO permissions"
       " (uuid, owner, name, comment, resource_type, resource, resource_uuid,"
       "  resource_location, subject_type, subject, subject_location,"
       "  creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), NULL, lower ('%s'), '', '',"
       "  0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role',"
       "  (SELECT id FROM roles WHERE uuid = '%s'),"
       "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());",
       permission,
       role);
}

/**
 * @brief Refresh nvt_cves table.
 *
 * Caller must organise transaction.
 */
void
refresh_nvt_cves ()
{
  iterator_t nvts;

  sql ("DELETE FROM nvt_cves;");

  init_iterator (&nvts, "SELECT id, oid, cve FROM nvts;");
  while (next (&nvts))
    {
      gchar **split, **point;

      split = g_strsplit_set (iterator_string (&nvts, 2), " ,", 0);

      point = split;
      while (*point)
        {
          g_strstrip (*point);
          if (strlen (*point))
            {
              gchar *quoted_cve, *quoted_oid;

              quoted_cve = sql_insert (*point);
              quoted_oid = sql_insert (iterator_string (&nvts, 1));
              sql ("INSERT INTO nvt_cves (nvt, oid, cve_name)"
                   " VALUES (%llu, %s, %s);",
                   iterator_int64 (&nvts, 0),
                   quoted_oid,
                   quoted_cve);
              g_free (quoted_cve);
              g_free (quoted_oid);
            }
          point++;
        }
      g_strfreev (split);
    }
  cleanup_iterator (&nvts);

  if (sql_is_sqlite3 ())
    sql ("REINDEX nvt_cves_by_oid;");
}

/**
 * @brief Ensure that the databases are the right versions.
 *
 * @param[in]  nvt_cache_mode  True when running in NVT caching mode.
 *
 * @return 0 success, -1 error, -2 database is wrong version, -3 database
 *         needs to be initialised from server.
 */
int
check_db_versions (int nvt_cache_mode)
{
  char *database_version;
  int scap_db_version, cert_db_version;

  database_version = sql_string ("SELECT value FROM %s.meta"
                                 " WHERE name = 'database_version';",
                                 sql_schema ());
  if (nvt_cache_mode)
    {
      if (database_version
          && strcmp (database_version,
                     G_STRINGIFY (OPENVASMD_DATABASE_VERSION)))
        {
          g_message ("%s: database version of database: %s\n",
                     __FUNCTION__,
                     database_version);
          g_message ("%s: database version supported by manager: %s\n",
                     __FUNCTION__,
                     G_STRINGIFY (OPENVASMD_DATABASE_VERSION));
          g_free (database_version);
          return -2;
        }
      g_free (database_version);

      /* If database_version was NULL then meta was missing, so assume
       * that the database is missing, which is OK. */
    }
  else
    {
      long long int count;

      if (database_version)
        {
          if (strcmp (database_version,
                      G_STRINGIFY (OPENVASMD_DATABASE_VERSION)))
            {
              g_message ("%s: database version of database: %s\n",
                         __FUNCTION__,
                         database_version);
              g_message ("%s: database version supported by manager: %s\n",
                         __FUNCTION__,
                         G_STRINGIFY (OPENVASMD_DATABASE_VERSION));
              g_free (database_version);
              return -2;
            }
          g_free (database_version);

          /* Check that the database was initialised from the scanner.
           *
           * This can also fail after a migration, for example if the database
           * was created before NVT preferences were cached in the database.
           */

          if (sql_int64 (&count,
                         "SELECT count(*) FROM %s.meta"
                         " WHERE name = 'nvts_feed_version'"
                         " OR name = 'nvt_preferences_enabled';",
                         sql_schema ())
              || count < 2)
            g_warning ("database must be initialised from scanner"
                       " (with --update or --rebuild)");
        }
      else
        /* Assume database is missing. */
        g_warning ("database must be initialised from scanner"
                   " (with --update or --rebuild)");
    }

  /* Check SCAP database version. */

  scap_db_version = manage_scap_db_version ();
  if (scap_db_version == -1)
    g_message ("No SCAP database found");
  else if (scap_db_version != manage_scap_db_supported_version ())
    {
      g_message ("%s: database version of SCAP database: %i\n",
                 __FUNCTION__,
                 scap_db_version);
      g_message ("%s: SCAP database version supported by manager: %s\n",
                 __FUNCTION__,
                 G_STRINGIFY (OPENVASMD_SCAP_DATABASE_VERSION));
      return -2;
    }

  /* Check CERT database version. */

  cert_db_version = manage_cert_db_version ();
  if (cert_db_version == -1)
    g_message ("No CERT database found");
  else if (cert_db_version != manage_cert_db_supported_version ())
    {
      g_message ("%s: database version of CERT database: %i\n",
                 __FUNCTION__,
                 cert_db_version);
      g_message ("%s: CERT database version supported by manager: %s\n",
                 __FUNCTION__,
                 G_STRINGIFY (OPENVASMD_CERT_DATABASE_VERSION));
      return -2;
    }
  return 0;
}

/**
 * @brief Ensures the sanity of nvts cache in DB.
 */
static void
check_db_nvts ()
{
  /* Ensure the nvti cache update flag exists and is clear. */
  if (sql_int ("SELECT count(*) FROM %s.meta"
               " WHERE name = 'update_nvti_cache';",
               sql_schema ()))
    sql ("UPDATE %s.meta SET value = 0 WHERE name = 'update_nvti_cache';",
         sql_schema ());
  else
    sql ("INSERT INTO %s.meta (name, value)"
         " VALUES ('update_nvti_cache', 0);",
         sql_schema ());

  /*
   * Ensure every part of the predefined selector exists.
   * This restores entries lost due to the error solved 2010-08-13 by r8805.
   */
  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL) ";")
      == 0)
    {
      sql ("INSERT into nvt_selectors (name, exclude, type, family_or_nvt)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_ALL) ", NULL);");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.810002';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) ","
           /* OID of the "CPE Inventory" NVT. */
           " '1.3.6.1.4.1.25623.1.0.810002', 'Service detection');");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.810003';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) ","
           /* OID of the "Host Summary" NVT. */
           " '1.3.6.1.4.1.25623.1.0.810003', 'General');");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
               " AND family_or_nvt = 'Port scanners';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY) ","
           " 'Port scanners', 'Port scanners');");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.14259';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) ","
           /* OID of the "Nmap (NASL wrapper)" NVT. */
           " '1.3.6.1.4.1.25623.1.0.14259', 'Port scanners');");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.100315';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) ","
           /* OID of the "Ping Host" NVT. */
           " '1.3.6.1.4.1.25623.1.0.100315', 'Port scanners');");
    }

  if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name ="
               " '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
               " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.80109';")
      == 0)
    {
      sql ("INSERT into nvt_selectors"
           " (name, exclude, type, family_or_nvt, family)"
           " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, "
           G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) ","
           /* OID of the "w3af (NASL wrapper)" NVT. */
           " '1.3.6.1.4.1.25623.1.0.80109', 'Web application abuses');");
    }
  /* Ensure the NVT CVE table is filled. */
  if (sql_int ("SELECT count (*) FROM nvt_cves;") == 0)
    {
      sql_begin_immediate ();
      refresh_nvt_cves ();
      sql ("COMMIT;");
    }
}

/**
 * @brief Ensure the predefined configs exist.
 */
static void
check_db_configs ()
{
  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE name = 'Full and fast';")
      == 0)
    {
      config_t config;

      sqli (&config,
            "INSERT into configs (uuid, owner, name, nvt_selector, comment,"
            " family_count, nvt_count, nvts_growing, families_growing,"
            " type, creation_time, modification_time)"
            " VALUES ('" CONFIG_UUID_FULL_AND_FAST "', NULL, 'Full and fast',"
            " '" MANAGE_NVT_SELECTOR_UUID_ALL "',"
            " 'Most NVT''s; optimized by using previously collected information.',"
            " %i, %i, 1, 1, 0, m_now (), m_now ())",
            family_nvt_count (NULL) - family_nvt_count ("Port scanners") + 1,
            family_count ());

      /* Setup preferences for the config. */
      setup_full_config_prefs (config, 1, 1, 0);
    }

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE name = 'Full and fast ultimate';")
      == 0)
    {
      config_t config;

      sqli (&config,
            "INSERT into configs (uuid, owner, name, nvt_selector, comment,"
            " family_count, nvt_count, nvts_growing, families_growing,"
            " type, creation_time, modification_time)"
            " VALUES ('" CONFIG_UUID_FULL_AND_FAST_ULTIMATE "', NULL,"
            " 'Full and fast ultimate', '" MANAGE_NVT_SELECTOR_UUID_ALL "',"
            " 'Most NVT''s including those that can stop services/hosts;"
            " optimized by using previously collected information.',"
            " %i, %i, 1, 1, 0, m_now (), m_now ())",
            family_nvt_count (NULL) - family_nvt_count ("Port scanners") + 1,
            family_count ());

      /* Setup preferences for the config. */
      setup_full_config_prefs (config, 0, 1, 0);
    }

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE name = 'Full and very deep';")
      == 0)
    {
      config_t config;

      sqli (&config,
            "INSERT into configs (uuid, owner, name, nvt_selector, comment,"
            " family_count, nvt_count, nvts_growing, families_growing,"
            " type, creation_time, modification_time)"
            " VALUES ('" CONFIG_UUID_FULL_AND_VERY_DEEP "', NULL,"
            " 'Full and very deep', '" MANAGE_NVT_SELECTOR_UUID_ALL "',"
            " 'Most NVT''s; don''t trust previously collected information; slow.',"
            " %i, %i, 1, 1, 0, m_now (), m_now ())",
            family_nvt_count (NULL) - family_nvt_count ("Port scanners") + 1,
            family_count ());

      /* Setup preferences for the config. */
      setup_full_config_prefs (config, 1, 0, 1);
    }

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE name = 'Full and very deep ultimate';")
      == 0)
    {
      config_t config;

      sqli (&config,
            "INSERT into configs (uuid, owner, name, nvt_selector, comment,"
            " family_count, nvt_count, nvts_growing, families_growing,"
            " type, creation_time, modification_time)"
            " VALUES ('" CONFIG_UUID_FULL_AND_VERY_DEEP_ULTIMATE "',"
            " NULL, 'Full and very deep ultimate',"
            " '" MANAGE_NVT_SELECTOR_UUID_ALL "',"
            " 'Most NVT''s including those that can stop services/hosts;"
            " don''t trust previously collected information; slow.',"
            " %i, %i, 1, 1, 0, m_now (), m_now ())",
            family_nvt_count (NULL) - family_nvt_count ("Port scanners") + 1,
            family_count ());

      /* Setup preferences for the config. */
      setup_full_config_prefs (config, 0, 0, 1);
    }

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE uuid = '" CONFIG_UUID_EMPTY "';")
      == 0)
    {
      config_t config;

      sqli (&config,
            "INSERT into configs (uuid, name, owner, nvt_selector, comment,"
            " family_count, nvt_count, nvts_growing, families_growing,"
            " type, creation_time, modification_time)"
            " VALUES ('" CONFIG_UUID_EMPTY "', 'empty', NULL, 'empty',"
            " 'Empty and static configuration template.',"
            " 0, 0, 0, 0, 0, m_now (), m_now ())");

      /* Setup preferences for the config. */
      setup_full_config_prefs (config, 1, 1, 0);
    }

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE uuid = '%s';",
               CONFIG_UUID_DISCOVERY)
      == 0)
    make_config_discovery (CONFIG_UUID_DISCOVERY,
                           MANAGE_NVT_SELECTOR_UUID_DISCOVERY);

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE uuid = '%s';",
               CONFIG_UUID_HOST_DISCOVERY)
      == 0)
    make_config_host_discovery (CONFIG_UUID_HOST_DISCOVERY,
                                MANAGE_NVT_SELECTOR_UUID_HOST_DISCOVERY);

  check_config_host_discovery (CONFIG_UUID_HOST_DISCOVERY);

  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE uuid = '%s';",
               CONFIG_UUID_SYSTEM_DISCOVERY)
      == 0)
    make_config_system_discovery (CONFIG_UUID_SYSTEM_DISCOVERY,
                                  MANAGE_NVT_SELECTOR_UUID_SYSTEM_DISCOVERY);
}

/**
 * @brief Ensure every report format has a unique UUID.
 *
 * @return 0 success, -1 error.
 */
static int
make_report_format_uuids_unique ()
{
  iterator_t rows;

  sql_begin_immediate ();

  sql ("CREATE TEMPORARY TABLE duplicates"
       " AS SELECT id, uuid, make_uuid () AS new_uuid, owner,"
       "           (SELECT uuid FROM users"
       "            WHERE users.id = outer_report_formats.owner)"
       "           AS owner_uuid,"
       "           (SELECT owner from report_formats"
       "                              WHERE uuid = outer_report_formats.uuid"
       "                              ORDER BY id ASC LIMIT 1)"
       "           AS original_owner,"
       "           (SELECT uuid FROM users"
       "            WHERE users.id = (SELECT owner from report_formats"
       "                              WHERE uuid = outer_report_formats.uuid"
       "                              ORDER BY id ASC LIMIT 1))"
       "           AS original_owner_uuid"
       "    FROM report_formats AS outer_report_formats"
       "    WHERE id > (SELECT id from report_formats"
       "                WHERE uuid = outer_report_formats.uuid"
       "                ORDER BY id ASC LIMIT 1);");

  sql ("UPDATE alert_method_data"
       " SET data = (SELECT new_uuid FROM duplicates"
       "             WHERE duplicates.id = alert_method_data.alert)"
       " WHERE alert IN (SELECT id FROM duplicates);");

  /* Update UUIDs on disk. */
  init_iterator (&rows,
                 "SELECT id, uuid, new_uuid, owner, owner_uuid, original_owner,"
                 "       original_owner_uuid"
                 " FROM duplicates;");
  while (next (&rows))
    {
      gchar *dir, *new_dir;
      const char *old_uuid, *new_uuid;
      int copy;

      old_uuid = iterator_string (&rows, 1);
      new_uuid = iterator_string (&rows, 2);

      if (iterator_int64 (&rows, 3) == 0)
        {
          /* Global report format. */

          if (iterator_int64 (&rows, 5) == 0)
            {
              /* Shared subdir in the global dir, so copy. */
              copy = 1;
              dir = g_build_filename (OPENVAS_DATA_DIR,
                                      "openvasmd",
                                      "global_report_formats",
                                      old_uuid,
                                      NULL);
              new_dir = g_build_filename (OPENVAS_DATA_DIR,
                                          "openvasmd",
                                          "global_report_formats",
                                          new_uuid,
                                          NULL);
            }
          else
            {
              const char *owner_uuid;
              /* Dedicated subdir in global dir, but must be renamed. */
              copy = 0;
              owner_uuid = iterator_string (&rows, 6);
              dir = g_build_filename (OPENVAS_STATE_DIR,
                                      "openvasmd",
                                      "report_formats",
                                      owner_uuid,
                                      old_uuid,
                                      NULL);
              new_dir = g_build_filename (OPENVAS_STATE_DIR,
                                          "openvasmd",
                                          "report_formats",
                                          owner_uuid,
                                          new_uuid,
                                          NULL);
            }
        }
      else if (iterator_int64 (&rows, 5) == 0)
        {
          const char *owner_uuid;
          /* Dedicated subdir in user dir, but must be renamed. */
          copy = 0;
          owner_uuid = iterator_string (&rows, 4);
          dir = g_build_filename (OPENVAS_STATE_DIR,
                                  "openvasmd",
                                  "report_formats",
                                  owner_uuid,
                                  old_uuid,
                                  NULL);
          new_dir = g_build_filename (OPENVAS_STATE_DIR,
                                      "openvasmd",
                                      "report_formats",
                                      owner_uuid,
                                      new_uuid,
                                      NULL);
        }
      else
        {
          const char *owner_uuid, *original_owner_uuid;

          /* Two user-owned report formats, may be the same user. */

          owner_uuid = iterator_string (&rows, 4);
          original_owner_uuid = iterator_string (&rows, 6);

          /* Copy the subdir if both report formats owned by one user. */
          copy = owner_uuid
                 && original_owner_uuid
                 && (strcmp (owner_uuid, original_owner_uuid) == 0);

          dir = g_build_filename (OPENVAS_STATE_DIR,
                                  "openvasmd",
                                  "report_formats",
                                  owner_uuid,
                                  old_uuid,
                                  NULL);
          new_dir = g_build_filename (OPENVAS_STATE_DIR,
                                      "openvasmd",
                                      "report_formats",
                                      owner_uuid,
                                      new_uuid,
                                      NULL);
        }

      if (copy)
        {
          gchar *command;
          int ret;

          command = g_strdup_printf ("cp -a %s %s > /dev/null 2>&1",
                                     dir,
                                     new_dir);
          tracef ("   command: %s\n", command);
          ret = system (command);
          g_free (command);

          if (ret == -1 || WEXITSTATUS (ret))
            {
              /* Presume dir missing, just log a warning. */
              g_warning ("%s: cp %s to %s failed",
                         __FUNCTION__, dir, new_dir);
            }
          else
            g_debug ("%s: copied %s to %s", __FUNCTION__, dir, new_dir);
        }
      else
        {
          if (rename (dir, new_dir))
            {
              g_warning ("%s: rename %s to %s: %s",
                         __FUNCTION__, dir, new_dir, strerror (errno));
              if (errno != ENOENT)
                {
                  g_free (dir);
                  g_free (new_dir);
                  sql ("ROLLBACK;");
                  return -1;
                }
            }
          else
            g_debug ("%s: moved %s to %s", __FUNCTION__, dir, new_dir);
        }
      g_free (dir);
      g_free (new_dir);
    }
  cleanup_iterator (&rows);

  sql ("UPDATE report_formats"
       " SET uuid = (SELECT new_uuid FROM duplicates"
       "             WHERE duplicates.id = report_formats.id)"
       " WHERE id IN (SELECT id FROM duplicates);");

  if (sql_changes () > 0)
    g_debug ("%s: gave %d report format(s) new UUID(s) to keep UUIDs unique.",
             __FUNCTION__, sql_changes ());

  sql ("DROP TABLE duplicates;");

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Ensure the predefined report formats exist.
 *
 * @return 0 success, -1 error.
 */
static int
check_db_report_formats ()
{
  /* Bring report format UUIDs in database up to date. */
  update_report_format_uuids ();
  if (make_report_format_uuids_unique ())
    return -1;

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '5057e5cc-b825-11e4-9d0e-28d24461215b';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('5057e5cc-b825-11e4-9d0e-28d24461215b', NULL, 'Anonymous XML',"
           " 'Anonymous version of the raw XML report',"
           " 'Complete scan report in OpenVAS Manager XML format.\n',"
           " 'xml', 'text/xml', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES,
           time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '910200ca-dc05-11e1-954f-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('910200ca-dc05-11e1-954f-406186ea4fc5', NULL, 'ARF',"
           " 'Asset Reporting Format v1.0.0.',"
           " 'NIST Asset Reporting Format 1.1 compliant document.\n',"
           " 'xml', 'text/xml', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES,
           time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
                     " WHERE uuid = '5ceff8ba-1f62-11e1-ab9f-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('5ceff8ba-1f62-11e1-ab9f-406186ea4fc5', NULL, 'CPE',"
           " 'Common Product Enumeration CSV table.',"
           " 'CPE stands for Common Product Enumeration.  It is a structured naming scheme for\n"
           "information technology systems, platforms, and packages.  In other words: CPE\n"
           "provides a unique identifier for virtually any software product that is known for\n"
           "a vulnerability.\n"
           "\n"
           "The CPE dictionary is maintained by MITRE and NIST.  MITRE also maintains CVE\n"
           "(Common Vulnerability Enumeration) and other relevant security standards.\n"
           "\n"
           "The report selects all CPE tables from the results and forms a single table\n"
           "as a comma separated values file.\n',"
           " 'csv', 'text/csv', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'c1645568-627a-11e3-a660-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('c1645568-627a-11e3-a660-406186ea4fc5', NULL, 'CSV Results',"
           " 'CSV result list.',"
           " 'List of results.',"
           " 'csv', 'text/csv', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '9087b18c-626c-11e3-8892-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('9087b18c-626c-11e3-8892-406186ea4fc5', NULL, 'CSV Hosts',"
           " 'CSV host summary.',"
           " 'Base host information and result counts',"
           " 'csv', 'text/csv', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '6c248850-1f62-11e1-b082-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('6c248850-1f62-11e1-b082-406186ea4fc5', NULL, 'HTML',"
           " 'Single page HTML report.',"
           " 'A single HTML page listing results of a scan.  Style information is embedded in\n"
           "the HTML, so the page is suitable for viewing in a browser as is.\n',"
           " 'html', 'text/html', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '77bd6c4a-1f62-11e1-abf0-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('77bd6c4a-1f62-11e1-abf0-406186ea4fc5', NULL, 'ITG',"
           " 'German \"IT-Grundschutz-Kataloge\" report.',"
           " 'Tabular report on the German \"IT-Grundschutz-Kataloge\",\n"
           "as published and maintained by the German Federal Agency for IT-Security.\n',"
           " 'csv', 'text/csv', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'a684c02c-b531-11e1-bdc2-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('a684c02c-b531-11e1-bdc2-406186ea4fc5', NULL, 'LaTeX',"
           " 'LaTeX source file.',"
           " 'Report as LaTeX source file for further processing.\n',"
           " 'tex', 'text/plain', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '9ca6fe72-1f62-11e1-9e7c-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('9ca6fe72-1f62-11e1-9e7c-406186ea4fc5', NULL, 'NBE',"
           " 'Legacy OpenVAS report.',"
           " 'The traditional OpenVAS Scanner text based format.',"
           " 'nbe', 'text/plain', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'c402cc3e-b531-11e1-9163-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('c402cc3e-b531-11e1-9163-406186ea4fc5', NULL, 'PDF',"
           " 'Portable Document Format report.',"
           " 'Scan results in Portable Document Format (PDF).',"
           "'pdf', 'application/pdf', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'a3810a62-1f62-11e1-9219-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('a3810a62-1f62-11e1-9219-406186ea4fc5', NULL, 'TXT',"
           " 'Plain text report.',"
           " 'Plain text report, best viewed with fixed font size.',"
           " 'txt', 'text/plain', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'a994b278-1f62-11e1-96ac-406186ea4fc5';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('a994b278-1f62-11e1-96ac-406186ea4fc5', NULL, 'XML',"
           " 'Raw XML report.',"
           " 'Complete scan report in OpenVAS Manager XML format.',"
           " 'xml', 'text/xml', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '9e5e5deb-879e-4ecc-8be6-a71cd0875cdd';")
      == 0)
    {
      report_format_t report_format;
      report_format_param_t report_format_param;

      /* Create Topology report format */

      sql ("INSERT INTO report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('9e5e5deb-879e-4ecc-8be6-a71cd0875cdd', NULL, 'Topology SVG',"
           " 'Network topology SVG image.',"
           " 'Scan results in topologic structure as scalable vector graphics.\n',"
           " 'svg', 'image/svg+xml', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();

      /* Create report "Graph Type" format parameter and parameter options */
      sql ("INSERT INTO report_format_params (report_format, name, type, value,"
           " type_min, type_max, type_regex, fallback)"
           " VALUES (%lli, 'Graph Type', 2, 'twopi', -9223372036854775808,"
           " 9223372036854775807,'','twopi');",
           report_format);
      report_format_param = sql_last_insert_id ();
      sql ("INSERT INTO report_format_param_options (report_format_param, value)"
           "VALUES (%lli, 'circo');",
           report_format_param);
      sql ("INSERT INTO report_format_param_options (report_format_param, value)"
           "VALUES (%lli, 'dot');",
           report_format_param);
      sql ("INSERT INTO report_format_param_options (report_format_param, value)"
           "VALUES (%lli, 'twopi');",
           report_format_param);

      /* Create report "Node Distance" format parameter */
      sql ("INSERT INTO report_format_params (report_format, name, type, value,"
           " type_min, type_max, type_regex, fallback)"
           " VALUES (%lli, 'Node Distance', 1, 8, 1, 20, '', 8);",
           report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = 'c15ad349-bd8d-457a-880a-c7056532ee15';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('c15ad349-bd8d-457a-880a-c7056532ee15', NULL, 'Verinice ISM',"
           " 'Greenbone Verinice ISM Report, v1.1.10.',"
           " 'Information Security Management Report for Verinice import, version 1.1.10.\n',"
           " 'vna', 'application/zip', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  if (sql_int ("SELECT count(*) FROM report_formats"
               " WHERE uuid = '50c9950a-f326-11e4-800c-28d24461215b';")
      == 0)
    {
      report_format_t report_format;
      sql ("INSERT into report_formats (uuid, owner, name, summary, description,"
           " extension, content_type, signature, trust, trust_time, flags,"
           " creation_time, modification_time)"
           " VALUES ('50c9950a-f326-11e4-800c-28d24461215b', NULL, 'Verinice ITG',"
           " 'Greenbone Verinice ITG Report, v1.0.1.',"
           " 'IT-Grundschutz Report for Verinice import, version 1.0.1.\n',"
           " 'vna', 'application/zip', '', %i, %i, 1, m_now (), m_now ());",
           TRUST_YES, time (NULL));
      report_format = sql_last_insert_id ();
      report_format_verify (report_format);
    }

  return 0;
}

/**
 * @brief Ensure that the report formats trash directory matches the database.
 *
 * @return -1 if error, 0 if success.
 */
static int
check_db_report_formats_trash ()
{
  gchar *dir;
  GError *error;
  GDir *directory;
  const gchar *entry;

  dir = g_build_filename (OPENVAS_DATA_DIR, "openvasmd", "report_formats_trash",
                          NULL);
  error = NULL;
  directory = g_dir_open (dir, 0, &error);

  if (directory == NULL)
    {
      assert (error);
      if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
        {
          g_warning ("g_dir_open (%s) failed - %s\n", dir, error->message);
          g_error_free (error);
          g_free (dir);
          return -1;
        }
    }
  else
    {
      entry = NULL;
      while ((entry = g_dir_read_name (directory)) != NULL)
        {
          gchar *end;
          if (strtol (entry, &end, 10) < 0)
            /* Only interested in positive numbers. */
            continue;
          if (*end != '\0')
            /* Only interested in numbers. */
            continue;

          /* Check whether the db has a report format with this ID. */
          if (sql_int ("SELECT count(*) FROM report_formats_trash"
                       " WHERE id = %s;",
                       entry)
              == 0)
            {
              int ret;
              gchar *entry_path;

              /* Remove the directory. */

              entry_path = g_build_filename (dir, entry, NULL);
              ret = openvas_file_remove_recurse (entry_path);
              g_free (entry_path);
              if (ret)
                {
                  g_warning ("%s: failed to remove %s from %s",
                             __FUNCTION__, entry, dir);
                  g_dir_close (directory);
                  g_free (dir);
                  return -1;
                }
            }
        }
      g_dir_close (directory);
    }
  g_free (dir);
  return 0;
}

/**
 * @brief Ensure the predefined permissions exists.
 */
static void
check_db_permissions ()
{
  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE uuid = '" PERMISSION_UUID_ADMIN_EVERYTHING "';")
      == 0)
    sql ("INSERT INTO permissions"
         " (uuid, owner, name, comment, resource_type, resource, resource_uuid,"
         "  resource_location, subject_type, subject, subject_location,"
         "  creation_time, modification_time)"
         " VALUES"
         " ('" PERMISSION_UUID_ADMIN_EVERYTHING "', NULL, 'Everything', '', '',"
         "  0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role',"
         "  (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_ADMIN "'),"
         "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE uuid = '" PERMISSION_UUID_SUPER_ADMIN_EVERYTHING "';")
      == 0)
    {
      sql ("INSERT INTO permissions"
           " (uuid, owner, name, comment, resource_type, resource, resource_uuid,"
           "  resource_location, subject_type, subject, subject_location,"
           "  creation_time, modification_time)"
           " VALUES"
           " ('" PERMISSION_UUID_SUPER_ADMIN_EVERYTHING "', NULL, 'Everything',"
           "  '', '', 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role',"
           "  (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "'),"
           "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());");
      sql ("INSERT INTO permissions"
           " (uuid, owner, name, comment, resource_type, resource, resource_uuid,"
           "  resource_location, subject_type, subject, subject_location,"
           "  creation_time, modification_time)"
           " VALUES"
           " (make_uuid (), NULL, 'Super',"
           "  '', '', 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role',"
           "  (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "'),"
           "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());");
    }

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE subject_type = 'role'"
               " AND subject = (SELECT id FROM roles"
               "                WHERE uuid = '" ROLE_UUID_GUEST "')"
               " AND resource = 0;")
      <= 1)
    {
      sql_begin_exclusive ();
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_GUEST "');");
      add_role_permission (ROLE_UUID_GUEST, "AUTHENTICATE");
      add_role_permission (ROLE_UUID_GUEST, "COMMANDS");
      add_role_permission (ROLE_UUID_GUEST, "HELP");
      add_role_permission (ROLE_UUID_GUEST, "GET_AGGREGATES");
      add_role_permission (ROLE_UUID_GUEST, "GET_FILTERS");
      add_role_permission (ROLE_UUID_GUEST, "GET_INFO");
      add_role_permission (ROLE_UUID_GUEST, "GET_NVTS");
      add_role_permission (ROLE_UUID_GUEST, "GET_SETTINGS");
      sql ("COMMIT;");
    }

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE subject_type = 'role'"
               " AND subject = (SELECT id FROM roles"
               "                WHERE uuid = '" ROLE_UUID_INFO "')"
               " AND resource = 0;")
      <= 1)
    {
      sql_begin_exclusive ();
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_INFO "');");
      add_role_permission (ROLE_UUID_INFO, "AUTHENTICATE");
      add_role_permission (ROLE_UUID_INFO, "COMMANDS");
      add_role_permission (ROLE_UUID_INFO, "HELP");
      add_role_permission (ROLE_UUID_INFO, "GET_AGGREGATES");
      add_role_permission (ROLE_UUID_INFO, "GET_INFO");
      add_role_permission (ROLE_UUID_INFO, "GET_NVTS");
      add_role_permission (ROLE_UUID_INFO, "GET_SETTINGS");
      add_role_permission (ROLE_UUID_INFO, "MODIFY_SETTING");
      sql ("COMMIT;");
    }

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE subject_type = 'role'"
               " AND subject = (SELECT id FROM roles"
               "                WHERE uuid = '" ROLE_UUID_MONITOR "')"
               " AND resource = 0;")
      <= 1)
    {
      sql_begin_exclusive ();
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_MONITOR "');");
      add_role_permission (ROLE_UUID_MONITOR, "AUTHENTICATE");
      add_role_permission (ROLE_UUID_MONITOR, "COMMANDS");
      add_role_permission (ROLE_UUID_MONITOR, "GET_SETTINGS");
      add_role_permission (ROLE_UUID_MONITOR, "GET_SYSTEM_REPORTS");
      add_role_permission (ROLE_UUID_MONITOR, "HELP");
      sql ("COMMIT;");
    }

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE subject_type = 'role'"
               " AND subject = (SELECT id FROM roles"
               "                WHERE uuid = '" ROLE_UUID_USER "')"
               " AND resource = 0;")
      <= 1)
    {
      command_t *command;
      command = omp_commands;
      sql_begin_exclusive ();

      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_USER "');");
      while (command[0].name)
        {
          if (strstr (command[0].name, "DESCRIBE") == NULL
              && strcmp (command[0].name, "GET_VERSION")
              && strstr (command[0].name, "GROUP") == NULL
              && strstr (command[0].name, "ROLE") == NULL
              && strstr (command[0].name, "SYNC") == NULL
              && strstr (command[0].name, "USER") == NULL)
            add_role_permission (ROLE_UUID_USER, command[0].name);
          command++;
        }
      sql ("COMMIT;");
    }

  if (sql_int ("SELECT count(*) FROM permissions"
               " WHERE subject_type = 'role'"
               " AND subject = (SELECT id FROM roles"
               "                WHERE uuid = '" ROLE_UUID_OBSERVER "')"
               " AND resource = 0;")
      <= 1)
    {
      command_t *command;
      command = omp_commands;
      sql_begin_exclusive ();
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_OBSERVER "');");
      while (command[0].name)
        {
          if ((strstr (command[0].name, "GET") == command[0].name)
              && strcmp (command[0].name, "GET_GROUPS")
              && strcmp (command[0].name, "GET_ROLES")
              && strcmp (command[0].name, "GET_USERS")
              && strcmp (command[0].name, "GET_VERSION"))
            add_role_permission (ROLE_UUID_OBSERVER, command[0].name);
          command++;
        }
      add_role_permission (ROLE_UUID_OBSERVER, "AUTHENTICATE");
      add_role_permission (ROLE_UUID_OBSERVER, "HELP");
      add_role_permission (ROLE_UUID_OBSERVER, "GET_SETTINGS");
      add_role_permission (ROLE_UUID_OBSERVER, "MODIFY_SETTING");
      sql ("COMMIT;");
    }
}

/**
 * @brief Ensure the predefined roles exists.
 */
static void
check_db_roles ()
{
  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_ADMIN "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_ADMIN "', NULL, 'Admin',"
         "  'Administrator.  Full privileges.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_GUEST "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_GUEST "', NULL, 'Guest',"
         "  'Guest.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_INFO "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_INFO "', NULL, 'Info',"
         "  'Information browser.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_MONITOR "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_MONITOR "', NULL, 'Monitor',"
         "  'Performance monitor.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_USER "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_USER "', NULL, 'User',"
         "  'Standard user.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_SUPER_ADMIN "', NULL, 'Super Admin',"
         "  'Super administrator.  Full privileges with access to all users.',"
         " m_now (), m_now ());");

  if (sql_int ("SELECT count(*) FROM roles"
               " WHERE uuid = '" ROLE_UUID_OBSERVER "';")
      == 0)
    sql ("INSERT INTO roles"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('" ROLE_UUID_OBSERVER "', NULL, 'Observer',"
         "  'Observer.',"
         " m_now (), m_now ());");
}

/**
 * @brief Ensure that the database is in order.
 *
 * @return 0 success, -1 error.
 */
static int
check_db ()
{
  create_tables ();
  if (progress)
    progress ();
  set_db_version (OPENVASMD_DATABASE_VERSION);
  check_db_nvts ();
  check_db_configs ();
  check_db_port_lists ();
  check_db_targets ();
  if (check_db_scanners ())
    return -1;
  check_db_tasks ();
  if (check_db_report_formats ())
    return -1;
  if (check_db_report_formats_trash ())
    return -1;
  check_db_roles ();
  check_db_permissions ();
  check_db_settings ();
  check_generate_scripts ();
  if (progress)
    progress ();

  return 0;
}

/**
 * @brief Ensure the generate scripts are all executable.
 *
 * Used by a migrator.
 */
void
check_generate_scripts ()
{
  iterator_t rows;

  init_iterator (&rows, "SELECT owner,"
                        "       uuid,"
                        "       (SELECT uuid FROM users"
                        "        WHERE users.id = report_formats.owner)"
                        " FROM report_formats;");
  while (next (&rows))
    {
      resource_t owner;

      owner = iterator_int64 (&rows, 0);
      if (owner)
        {
          const gchar *report_format_uuid, *user_uuid;
          gchar *path;

          report_format_uuid = iterator_string (&rows, 1);
          if (report_format_uuid == NULL)
            continue;

          user_uuid = iterator_string (&rows, 2);
          if (user_uuid == NULL)
            continue;

          path = g_build_filename (OPENVAS_STATE_DIR,
                                   "openvasmd",
                                   "report_formats",
                                   user_uuid,
                                   report_format_uuid,
                                   "generate",
                                   NULL);

          if (chmod (path, 0755 /* rwxr-xr-x */))
            g_warning ("%s: chmod %s failed: %s\n",
                       __FUNCTION__,
                       path,
                       strerror (errno));

          g_free (path);
        }
    }
  cleanup_iterator (&rows);
}

/**
 * @brief Stop any active tasks.
 */
void
stop_active_tasks ()
{
  iterator_t tasks;
  get_data_t get;

  /* Set requested and running tasks to stopped. */

  assert (current_credentials.uuid == NULL);
  memset (&get, '\0', sizeof (get));
  init_task_iterator (&tasks, &get);
  while (next (&tasks))
    {
      switch (task_iterator_run_status (&tasks))
        {
          case TASK_STATUS_DELETE_REQUESTED:
          case TASK_STATUS_DELETE_ULTIMATE_REQUESTED:
          case TASK_STATUS_DELETE_ULTIMATE_WAITING:
          case TASK_STATUS_DELETE_WAITING:
          case TASK_STATUS_REQUESTED:
          case TASK_STATUS_RUNNING:
          case TASK_STATUS_STOP_REQUESTED_GIVEUP:
          case TASK_STATUS_STOP_REQUESTED:
          case TASK_STATUS_STOP_WAITING:
            {
              task_t index = get_iterator_resource (&tasks);
              /* Set the current user, for event checks. */
              current_credentials.uuid = task_owner_uuid (index);
              set_task_run_status (index, TASK_STATUS_STOPPED);
              free (current_credentials.uuid);
              break;
            }
          default:
            break;
        }
    }
  cleanup_iterator (&tasks);
  current_credentials.uuid = NULL;

  /* Set requested and running reports to stopped. */

  sql ("UPDATE reports SET scan_run_status = %u"
       " WHERE scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u"
       " OR scan_run_status = %u;",
       TASK_STATUS_STOPPED,
       TASK_STATUS_DELETE_REQUESTED,
       TASK_STATUS_DELETE_ULTIMATE_REQUESTED,
       TASK_STATUS_DELETE_ULTIMATE_WAITING,
       TASK_STATUS_DELETE_WAITING,
       TASK_STATUS_REQUESTED,
       TASK_STATUS_RUNNING,
       TASK_STATUS_STOP_REQUESTED,
       TASK_STATUS_STOP_REQUESTED_GIVEUP,
       TASK_STATUS_STOP_WAITING);
}

/**
 * @brief Clean up database tables.
 *
 * Remove superfluous entries from tables.
 */
void
cleanup_tables ()
{
  /* Remove group and role assignments of deleted users.
   *
   * This should be a migrator, but this way is easier to backport.  */

  sql ("DELETE FROM group_users"
       " WHERE \"user\" NOT IN (SELECT id FROM users);");
  sql ("DELETE FROM group_users_trash"
       " WHERE \"user\" NOT IN (SELECT id FROM users);");
  sql ("DELETE FROM role_users"
       " WHERE \"user\" NOT IN (SELECT id FROM users);");
  sql ("DELETE FROM role_users_trash"
       " WHERE \"user\" NOT IN (SELECT id FROM users);");
}

/**
 * @brief Initialize the manage library.
 *
 * Check DB version, do startup database checks, load the NVT cache.
 * Optionally also stop active tasks.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  nvt_cache_mode  True when running in NVT caching mode.
 * @param[in]  database        Location of database.
 * @param[in]  max_ips_per_target  Max number of IPs per target.
 * @param[in]  max_email_attachment_size  Max size of email attachments.
 * @param[in]  max_email_include_size     Max size of email inclusions.
 * @param[in]  update_progress     Function to update progress, or NULL. *
 * @param[in]  stop_tasks          Stop any active tasks.
 *
 * @return 0 success, -1 error, -2 database is wrong version, -3 database needs
 *         to be initialised from server, -4 max_ips_per_target out of range.
 */
static int
init_manage_internal (GSList *log_config,
                      int nvt_cache_mode,
                      const gchar *database,
                      int max_ips_per_target,
                      int max_email_attachment_size,
                      int max_email_include_size,
                      void (*update_progress) (),
                      int stop_tasks)
{
  int ret;

  /* Summary of init cases:
   *
   *     daemon [--foreground]
   *         init_ompd  cache 0
   *             init_manage
   *         serve_and_schedule
   *             forks child (serve_omp)
   *                 init_ompd_process
   *                     init_manage_process
   *             manage_schedule
   *                 fork_connection_for_schedular
   *                     fork one
   *                         init_ompd_process
   *                             init_manage_process
   *                         serve_client
   *                     fork two
   *                         omp_auth, omp_start_task_report, omp_resume_task_report.
   *     --rebuild --update
   *         rebuild_nvt_cache_retry
   *             forks update_or_rebuild_nvt_cache
   *                 init_ompd  cache -1 or -2
   *                     init_manage
   *                 serve_omp
   *                     init_ompd_process
   *     --create-user --delete-user --get-users
   *         manage_create, ...
   *             init_manage_helper
   *     --encrypt/decrypt-all-credentials
   *         manage_encrypt_...
   *             init_manage_helper
   *     --backup-database
   *         (no init because no db access required)
   *     --migrate
   *         manage_migrate
   *             init_manage_process (sorts out db state itself) */

  /* The number of 70000 is choosen to cover "192.168.0.0-192.168.255.255" */
  if ((max_ips_per_target <= 0)
      || (max_ips_per_target > 70000))
    return -4;

  max_hosts = max_ips_per_target;
  if (max_email_attachment_size)
    max_attach_length = max_email_attachment_size;
  if (max_email_include_size)
    max_content_length = max_email_include_size;
  progress = update_progress;

  g_log_set_handler (G_LOG_DOMAIN,
                     ALL_LOG_LEVELS,
                     (GLogFunc) openvas_log_func,
                     log_config);

  memset (&current_credentials, '\0', sizeof (current_credentials));

  init_manage_process (0, database);

  /* Check that the versions of the databases are correct. */

  ret = check_db_versions (nvt_cache_mode);
  if (ret)
    return ret;

  /* Ensure the database is complete, removing superfluous rows.
   *
   * Assume that all other running processes are from the same Manager version,
   * because some of these checks will modify the database if it is out of
   * date.  This is relevant because the caller may be a command option process
   * like a --create-user process.  */

  ret = check_db ();
  if (ret)
    return ret;

  cleanup_tables ();

  if (stop_tasks && (nvt_cache_mode == 0))
    /* Stop any active tasks. */
    stop_active_tasks ();

  /* Load the NVT cache into memory. */

  if (nvti_cache == NULL)
    update_nvti_cache ();

  sql_close ();
  task_db_name = g_strdup (database);
  return 0;
}

/**
 * @brief Initialize the manage library.
 *
 * Check DB version, do startup database checks, load the NVT cache.
 *
 * Ensure all tasks are in a clean initial state.
 *
 * Beware that calling this function while tasks are running may lead to
 * problems.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  nvt_cache_mode  True when running in NVT caching mode.
 * @param[in]  database        Location of database.
 * @param[in]  max_ips_per_target  Max number of IPs per target.
 * @param[in]  max_email_attachment_size  Max size of email attachments.
 * @param[in]  max_email_include_size     Max size of email inclusions.
 * @param[in]  update_progress     Function to update progress, or NULL. *
 *
 * @return 0 success, -1 error, -2 database is wrong version, -3 database needs
 *         to be initialised from server, -4 max_ips_per_target out of range.
 */
int
init_manage (GSList *log_config, int nvt_cache_mode, const gchar *database,
             int max_ips_per_target, int max_email_attachment_size,
             int max_email_include_size, void (*update_progress) ())
{
  return init_manage_internal (log_config,
                               nvt_cache_mode,
                               database,
                               max_ips_per_target,
                               max_email_attachment_size,
                               max_email_include_size,
                               update_progress,
                               1);  /* Stop active tasks. */
}

/**
 * @brief Initialize the manage library for a helper program.
 *
 * This should be called at the beginning of any program that accesses the
 * database.  Forked processes should call init_manage_process.  The daemon
 * itself calls init_manage, including in NVT cache mode (--rebuild/update).
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  nvt_cache_mode  True when running in NVT caching mode.
 * @param[in]  database        Location of database.
 * @param[in]  max_ips_per_target  Max number of IPs per target.
 * @param[in]  update_progress     Function to update progress, or NULL. *
 *
 * @return 0 success, -1 error, -2 database is wrong version, -3 database needs
 *         to be initialised from server, -4 max_ips_per_target out of range.
 */
int
init_manage_helper (GSList *log_config, const gchar *database,
                    int max_ips_per_target, void (*update_progress) ())
{
  return init_manage_internal (log_config,
                               0,   /* Run daemon in NVT cache mode. */
                               database,
                               max_ips_per_target,
                               0,   /* Default max_email_attachment_size. */
                               0,   /* Default max_email_include_size. */
                               update_progress,
                               0);  /* Stop active tasks. */
}

/**
 * @brief Cleanup the manage library.
 *
 * Optionally put any running task in the stopped state and close the database.
 *
 * @param[in]  cleanup  If TRUE perform all cleanup operations, else only
 *                      those required at the start of a forked process.
 */
void
cleanup_manage_process (gboolean cleanup)
{
  if (sql_is_open ())
    {
      if (cleanup)
        {
          if (current_scanner_task)
            set_task_run_status (current_scanner_task, TASK_STATUS_STOPPED);
          cleanup_prognosis_iterator ();
          sql_close ();
        }
      else
        sql_close_fork ();
    }
}

/**
 * @brief Cleanup as immediately as possible.
 *
 * Put any running task in the error state and close the database.
 *
 * Intended for handlers for signals like SIGSEGV and SIGABRT.
 *
 * @param[in]  signal  Dummy argument for use as signal handler.
 */
void
manage_cleanup_process_error (int signal)
{
  g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "Received %s signal.\n",
         sys_siglist[signal]);
  if (sql_is_open ())
    {
      if (current_scanner_task)
        set_task_run_status (current_scanner_task, TASK_STATUS_INTERNAL_ERROR);
      sql_close ();
    }
}

/**
 * @brief Get user hash.
 *
 * This is for "file" users, now entirely stored in db.
 *
 * @param[in]  username  User name.
 *
 * @return Hash.
 */
gchar *
manage_user_hash (const gchar *username)
{
  gchar *hash, *quoted_username;
  quoted_username = sql_quote (username);
  hash = sql_string ("SELECT password FROM users WHERE name = '%s';",
                     quoted_username);
  g_free (quoted_username);
  return hash;
}

/**
 * @brief Get user uuid.
 *
 * @param[in]  username  User name.
 * @param[in]  method    Authentication method.
 *
 * @return UUID.
 */
gchar *
manage_user_uuid (const gchar *username, auth_method_t method)
{
  gchar *uuid, *quoted_username, *quoted_method;
  quoted_username = sql_quote (username);
  quoted_method = sql_quote (auth_method_name (method));
  uuid = sql_string ("SELECT uuid FROM users"
                     " WHERE name = '%s' AND method = '%s';",
                     quoted_username,
                     quoted_method);
  g_free (quoted_username);
  g_free (quoted_method);
  return uuid;
}

/**
 * @brief Ensure the user exists in the database.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 *
 * @return 0 success.
 */
int
user_ensure_in_db (const gchar *name, const gchar *method)
{
  gchar *quoted_name, *quoted_method;

  if ((method == NULL) || (strcasecmp (method, "file") == 0))
    /* A "file" user, now entirely stored in db. */
    return 0;

  /* SELECT then INSERT instead of using "INSERT OR REPLACE", so that the
   * id stays the same. */

  quoted_name = sql_quote (name);
  quoted_method = sql_quote (method);

  if (sql_int ("SELECT count(*)"
               " FROM users WHERE name = '%s' and method = '%s';",
               quoted_name,
               quoted_method))
    {
      g_free (quoted_method);
      g_free (quoted_name);
      return 0;
    }

  sql ("INSERT INTO users"
       " (uuid, owner, name, comment, password, timezone, method, hosts,"
       "  hosts_allow, ifaces, ifaces_allow, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (),"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '', NULL, NULL, '%s', '', 2, '', 2, m_now (), m_now ());",
       current_credentials.uuid,
       quoted_name,
       quoted_method);

  g_free (quoted_method);
  g_free (quoted_name);

  return 0;
}

/**
 * @brief Set user role.
 *
 * This works for any auth method.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 * @param[in]  role    User role.
 *
 * @return 0 success.
 */
int
manage_user_set_role (const gchar *name, const gchar *method, const gchar *role)
{
  gchar *quoted_role, *quoted_name, *quoted_method;

  assert ((strcmp (role, "Admin") == 0)
          || (strcmp (role, "User") == 0)
          || (strcmp (role, "Observer") == 0));

  user_ensure_in_db (name, method);

  quoted_role = sql_quote (role);
  quoted_name = sql_quote (name);
  quoted_method = sql_quote (method);
  sql ("DELETE FROM role_users"
       " WHERE \"user\" = (SELECT id FROM users"
       "                   WHERE name = '%s' AND method = '%s');",
       quoted_name,
       quoted_method);
  sql ("INSERT INTO role_users (role, \"user\")"
       " VALUE ((SELECT id FROM users"
       "         WHERE name = '%s' AND method = '%s'),"
       "        (SELECT id FROM roles"
       "         WHERE name = '%s'));",
       quoted_name,
       quoted_method,
       quoted_role);
  g_free (quoted_role);
  g_free (quoted_name);
  g_free (quoted_method);

  return 0;
}

/**
 * @brief Check if user exists.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 *
 * @return 0 yes, 1 no.
 */
int
manage_user_exists (const gchar *name, auth_method_t method)
{
  gchar *quoted_name, *quoted_method;
  int ret;

  quoted_name = sql_quote (name);
  quoted_method = sql_quote (auth_method_name (method));
  ret = sql_int ("SELECT count (*) FROM users"
                 " WHERE name = '%s' AND method = '%s';",
                 quoted_name,
                 quoted_method);
  g_free (quoted_name);
  g_free (quoted_method);

  return ret;
}


/**
 * @brief Set the address of scanner to connect to.
 *
 * @param[in]  uuid     Scanner UUID.
 *
 * @return 0 if success, -1 if error.
 */
int
manage_scanner_set (const char *uuid)
{
  scanner_t scanner = 0;
  char *host, *ca_pub, *key_pub, *key_priv;
  int port, type;

  if (uuid == NULL)
    return -1;

  if (!current_credentials.uuid)
    current_credentials.uuid = "";
  if (find_scanner (uuid, &scanner) || scanner == 0)
    {
      g_warning ("Failed to find scanner %s\n", uuid);
      return -1;
    }
  if (!strcmp (current_credentials.uuid, ""))
    current_credentials.uuid = NULL;

  type = scanner_type (scanner);
  if (type != SCANNER_TYPE_OPENVAS)
    {
      g_warning ("Scanner %s not an OpenVAS Scanner\n", uuid);
      return -1;
    }
  host = scanner_host (scanner);
  port = scanner_port (scanner);
  if (openvas_scanner_set_address (host, port))
    {
      g_warning ("Failed to set %s:%d as scanner\n", host, port);
      g_free (host);
      return -1;
    }
  ca_pub = scanner_ca_pub (scanner);
  key_pub = scanner_key_pub (scanner);
  key_priv = scanner_key_priv (scanner);
  openvas_scanner_set_certs (ca_pub, key_pub, key_priv);
  g_free (host);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  return 0;
}

/**
 * @brief Set the default scanner as the scanner to connect to.
 *
 * @return 0 if success, -1 if error.
 */
int
manage_scanner_set_default ()
{
  return manage_scanner_set (SCANNER_UUID_DEFAULT);
}

/**
 * @brief Set credentials for authenticate.
 *
 * @param[in]  credentials  Credentials.
 *
 * @return 0 success, 99 permission denied.
 */
static int
credentials_setup (credentials_t *credentials)
{
  assert (credentials->uuid);

  credentials->role
    = g_strdup (user_is_super_admin (credentials->uuid)
                 ? "Super Admin"
                 : (user_is_admin (credentials->uuid)
                     ? "Admin"
                     : (user_is_observer (credentials->uuid)
                         ? "Observer"
                         : (user_is_user (credentials->uuid)
                             ? "User"
                             : ""))));

  if (user_may ("authenticate") == 0)
    {
      free (credentials->uuid);
      credentials->uuid = NULL;
      g_free (credentials->role);
      credentials->role = NULL;
      return 99;
    }

  credentials->timezone = sql_string ("SELECT timezone FROM users"
                                      " WHERE uuid = '%s';",
                                      credentials->uuid);

  credentials->severity_class
    = sql_string ("SELECT value FROM settings"
                  " WHERE name = 'Severity Class'"
                  " AND ((owner IS NULL)"
                  "      OR (owner ="
                  "          (SELECT id FROM users"
                  "           WHERE users.uuid = '%s')))"
                  " ORDER BY owner DESC LIMIT 1;",
                  credentials->uuid);

  credentials->dynamic_severity
    = sql_int ("SELECT value FROM settings"
                " WHERE name = 'Dynamic Severity'"
                " AND ((owner IS NULL)"
                "      OR (owner ="
                "          (SELECT id FROM users"
                "           WHERE users.uuid = '%s')))"
                " ORDER BY owner DESC LIMIT 1;",
                credentials->uuid);

  return 0;
}

/**
 * @brief Authenticate credentials.
 *
 * @param[in]  credentials  Credentials.
 *
 * @return 0 authentication success, 1 authentication failure, 99 permission
 *         denied, -1 error.
 */
int
authenticate (credentials_t* credentials)
{
  if (credentials->username && credentials->password)
    {
      int fail;
      auth_method_t auth_method;

      if (authenticate_allow_all)
        {
          /* This flag is set for scheduled tasks only. Take the stored uuid
           * to be able to tell apart locally authenticated vs remotely
           * authenticated users (in order to fetch the correct rules). */
          credentials->uuid = get_scheduled_user_uuid ();
          if (*credentials->uuid)
            {
              if (credentials_setup (credentials))
                return 99;

              manage_session_init (credentials->uuid);
              return 0;
            }
          return -1;
        }

      fail = openvas_authenticate_method (credentials->username,
                                          credentials->password,
                                          &auth_method);
      if (fail == 0)
        {
          gchar *quoted_name, *quoted_method;

          /* Authentication succeeded. */

          user_ensure_in_db (credentials->username,
                             auth_method_name (auth_method));

          quoted_name = sql_quote (credentials->username);
          quoted_method = sql_quote (auth_method_name (auth_method));
          credentials->uuid = sql_string ("SELECT uuid FROM users"
                                          " WHERE name = '%s'"
                                          " AND method = '%s';",
                                          quoted_name,
                                          quoted_method);
          g_free (quoted_name);
          g_free (quoted_method);

          if (credentials_setup (credentials))
            {
              free (credentials->uuid);
              credentials->uuid = NULL;
              credentials->role = NULL;
              return 99;
            }

          manage_session_init (credentials->uuid);

          return 0;
        }
      return fail;
    }
  return 1;
}

/**
 * @brief Return number of resources of a certain type for current user.
 *
 * @param[in]  type  Type.
 * @param[in]  get   GET params.
 *
 * @return The number of resources associated with the current user.
 */
int
resource_count (const char *type, const get_data_t *get)
{
  static const char *filter_columns[] = { "owner", NULL };
  static column_t select_columns[] = {{ "owner", NULL }, { NULL, NULL }};
  get_data_t count_get;

  memset (&count_get, '\0', sizeof (count_get));
  count_get.trash = get->trash;
  if (type_owned (type))
    count_get.filter = "rows=-1 first=1 permission=any owner=any";
  else
    count_get.filter = "rows=-1 first=1 permission=any";

  return count (get->subtype ? get->subtype : type,
                &count_get,
                type_owned (type) ? select_columns : NULL,
                type_owned (type) ? select_columns : NULL,
                type_owned (type) ? filter_columns : NULL,
                0, NULL,
                strcmp (type, "task")
                 ? ((strcmp (type, "report") && strcmp (type, "result"))
                     ? NULL
                     : " AND (SELECT hidden FROM tasks"
                       "      WHERE tasks.id = task)"
                       "     = 0")
                 : (get->id
                    && (strcmp (get->id, MANAGE_EXAMPLE_TASK_UUID)
                        == 0))
                    ? " AND hidden = 1"
                    : (get->trash
                        ? " AND hidden = 2"
                        : " AND hidden = 0"),
                type_owned (type));
}

/**
 * @brief Test whether a resource of the given type and unique ID exists.
 *
 * @param[in]  type  Type.
 * @param[in]  id    Unique ID.
 *
 * @return 1 if the resource exists, 0 otherwise.
 */
int
resource_id_exists (const char *type, const char * id)
{
  return !!sql_int ("SELECT count(*)"
                    " FROM %ss"
                    " WHERE uuid='%s'"
                    " %s;",
                    type,
                    id,
                    (strcmp (type, "task") == 0) ? "AND hidden=0" : "");
}

/**
 * @brief Test Whether a resource of the given type and ID exists in the trash.
 *
 * @param[in]  type  Type.
 * @param[in]  id    Unique ID.
 *
 * @return 1 if the resource exists, 0 otherwise.
 */
int
trash_id_exists (const char *type, const char * id)
{
  if ((strcmp (type, "nvt") == 0)
      || (strcmp (type, "cpe") == 0)
      || (strcmp (type, "cve") == 0)
      || (strcmp (type, "ovaldef") == 0)
      || (strcmp (type, "cert_bund_adv") == 0)
      || (strcmp (type, "dfn_cert_adv") == 0)
      || (strcmp (type, "report") == 0)
      || (strcmp (type, "result") == 0)
      || (strcmp (type, "user") == 0))
    return 0;
  else if (strcmp (type, "task"))
    return !!sql_int ("SELECT count(*)"
                      " FROM %ss_trash"
                      " WHERE uuid='%s';",
                      type,
                      id);
  else
    return !!sql_int ("SELECT count(*)"
                      " FROM tasks"
                      " WHERE uuid='%s'"
                      " AND hidden=2;",
                      id);
}

/**
 * @brief Return the number of tasks associated with the current user.
 *
 * @param[in]  get  GET params.
 *
 * @return The number of tasks associated with the current user.
 */
unsigned int
task_count (const get_data_t *get)
{
  static const char *extra_columns[] = TASK_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TASK_ITERATOR_COLUMNS;
  char *filter;
  gchar *value;
  int overrides, min_qod;
  gchar *extra_tables;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;
  value = filter_term_value (filter ? filter : get->filter, "apply_overrides");
  overrides = value && strcmp (value, "0");
  g_free (value);

  value = filter_term_value (filter ? filter : get->filter, "min_qod");
  if (value == NULL || sscanf (value, "%d", &min_qod) != 1)
    min_qod = MIN_QOD_DEFAULT;
  g_free (value);
  free (filter);

  extra_tables = task_iterator_opts_table (overrides, min_qod);

  ret = count ("task", get,
                columns,
                columns,
                extra_columns, 0,
                extra_tables,
                (get->id
                 && (strcmp (get->id, MANAGE_EXAMPLE_TASK_UUID)
                     == 0))
                 ? " AND hidden = 1"
                 : (get->trash
                     ? " AND hidden = 2"
                     : " AND hidden = 0"),
                TRUE);

  g_free (extra_tables);
  return ret;
}

/**
 * @brief Return the identifier of a task.
 *
 * @param[in]  task  Task.
 *
 * @return ID of task.
 */
unsigned int
task_id (task_t task)
{
  /** @todo The cast is a hack for compatibility with the old, alternate,
   *        FS based storage mechanism. */
  return (unsigned int) task;
}

/**
 * @brief Return the UUID of a task.
 *
 * @param[in]   task  Task.
 * @param[out]  id    Pointer to a newly allocated string.
 *
 * @return 0.
 */
int
task_uuid (task_t task, char ** id)
{
  *id = sql_string ("SELECT uuid FROM tasks WHERE id = %llu;",
                    task);
  return 0;
}

/**
 * @brief Return whether a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trashcan, else 0.
 */
int
task_in_trash (task_t task)
{
  return sql_int ("SELECT hidden = 2"
                  " FROM tasks WHERE id = %llu;",
                  task);
}

/**
 * @brief Return the name of the owner of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Newly allocated user name.
 */
char*
task_owner_name (task_t task)
{
  return sql_string ("SELECT name FROM users WHERE id ="
                     " (SELECT owner FROM tasks WHERE id = %llu);",
                     task);
}

/**
 * @brief Return the name of the owner of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Newly allocated user name.
 */
static char*
task_owner_uuid (task_t task)
{
  return sql_string ("SELECT uuid FROM users WHERE id ="
                     " (SELECT owner FROM tasks WHERE id = %llu);",
                     task);
}

/**
 * @brief Return the name of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Task name.
 */
char*
task_name (task_t task)
{
  return sql_string ("SELECT name FROM tasks WHERE id = %llu;",
                     task);
}

/**
 * @brief Return the comment of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Comment of task.
 */
char*
task_comment (task_t task)
{
  return sql_string ("SELECT comment FROM tasks WHERE id = %llu;",
                     task);
}

/**
 * @brief Return the hosts ordering of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Hosts ordering of task.
 */
char*
task_hosts_ordering (task_t task)
{
  return sql_string ("SELECT hosts_ordering FROM tasks WHERE id = %llu;",
                     task);
}

/**
 * @brief Return the observers of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Observers of task.
 */
char*
task_observers (task_t task)
{
  iterator_t users;
  GString *observers;

  observers = g_string_new ("");

  init_task_user_iterator (&users, task);
  if (next (&users))
    {
      g_string_append (observers, task_user_iterator_name (&users));
      while (next (&users))
        g_string_append_printf (observers,
                                " %s",
                                task_user_iterator_name (&users));
    }
  cleanup_iterator (&users);

  return observers->str;
}

/**
 * @brief Return the config of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Config of task.
 */
config_t
task_config (task_t task)
{
  config_t config;
  switch (sql_int64 (&config,
                     "SELECT config FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return config;
      default:       /* Programming error. */
      case 1:        /* Too few rows in result of query. */
      case -1:       /* Error. */
        /* Every task should have a config. */
        assert (0);
        return 0;
        break;
    }
}

/**
 * @brief Return the UUID of the config of a task.
 *
 * @param[in]  task  Task.
 *
 * @return UUID of config of task.
 */
char*
task_config_uuid (task_t task)
{
  if (task_config_in_trash (task))
    return sql_string ("SELECT uuid FROM configs_trash WHERE id ="
                       " (SELECT config FROM tasks WHERE id = %llu);",
                       task);
  return sql_string ("SELECT uuid FROM configs WHERE id ="
                     " (SELECT config FROM tasks WHERE id = %llu);",
                     task);
}

/**
 * @brief Return the name of the config of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Name of config of task.
 */
char*
task_config_name (task_t task)
{
  if (task_config_in_trash (task))
    return sql_string ("SELECT name FROM configs_trash WHERE id ="
                       " (SELECT config FROM tasks WHERE id = %llu);",
                       task);
  return sql_string ("SELECT name FROM configs WHERE id ="
                     " (SELECT config FROM tasks WHERE id = %llu);",
                     task);
}

/**
 * @brief Return whether the config of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trashcan, else 0.
 */
int
task_config_in_trash (task_t task)
{
  return sql_int ("SELECT config_location = " G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks WHERE id = %llu;",
                  task);
}

/**
 * @brief Set the config of a task.
 *
 * @param[in]  task    Task.
 * @param[in]  config  Config.
 */
void
set_task_config (task_t task, config_t config)
{
  sql ("UPDATE tasks SET config = %llu, modification_time = m_now ()"
       " WHERE id = %llu;",
       config,
       task);
}

/**
 * @brief Return the target of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Target of task.
 */
target_t
task_target (task_t task)
{
  target_t target = 0;
  switch (sql_int64 (&target,
                     "SELECT target FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return target;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        return 0;
        break;
    }
}

/**
 * @brief Set the target of a task.
 *
 * @param[in]  task    Task.
 * @param[in]  target  Target.
 */
void
set_task_target (task_t task, target_t target)
{
  sql ("UPDATE tasks SET target = %llu, modification_time = m_now ()"
       " WHERE id = %llu;",
       target,
       task);
}

/**
 * @brief Set the hosts ordering of a task.
 *
 * @param[in]  task         Task.
 * @param[in]  ordering     Hosts ordering.
 */
void
set_task_hosts_ordering (task_t task, const char *ordering)
{
  char *quoted_ordering = sql_quote (ordering ?: "");
  sql ("UPDATE tasks SET hosts_ordering = '%s', modification_time = m_now ()"
       " WHERE id = %llu;", quoted_ordering, task);
  g_free (quoted_ordering);
}

/**
 * @brief Return whether the target of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_target_in_trash (task_t task)
{
  return sql_int ("SELECT target_location = " G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks WHERE id = %llu;",
                  task);
}

/**
 * @brief Return the scanner of a task.
 *
 * @param[in]  task  Task.
 *
 * @return scanner of task.
 */
scanner_t
task_scanner (task_t task)
{
  scanner_t scanner = 0;
  switch (sql_int64 (&scanner, "SELECT scanner FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return scanner;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        return 0;
        break;
    }
}

/**
 * @brief Set the scanner of a task.
 *
 * @param[in]  task     Task.
 * @param[in]  scanner  Scanner.
 */
void
set_task_scanner (task_t task, scanner_t scanner)
{
  sql ("UPDATE tasks SET scanner = %llu, modification_time = m_now ()"
       " WHERE id = %llu;", scanner, task);
}

/**
 * @brief Return the slave of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Slave of task.
 */
slave_t
task_slave (task_t task)
{
  slave_t slave = 0;
  switch (sql_int64 (&slave,
                     "SELECT slave FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return slave;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        return 0;
        break;
    }
}

/**
 * @brief Set the slave of a task.
 *
 * @param[in]  task   Task.
 * @param[in]  slave  Slave.
 */
void
set_task_slave (task_t task, slave_t slave)
{
  sql ("UPDATE tasks SET slave = %llu, modification_time = m_now ()"
       " WHERE id = %llu;",
       slave,
       task);
}

/**
 * @brief Return whether the slave of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_slave_in_trash (task_t task)
{
  return sql_int ("SELECT slave_location = " G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks WHERE id = %llu;",
                  task);
}

/**
 * @brief Return the run state of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Task run status.
 */
task_status_t
task_run_status (task_t task)
{
  return (unsigned int) sql_int ("SELECT run_status FROM tasks WHERE id = %llu;",
                                 task);
}

/**
 * @brief Set the run state of a task.
 *
 * @param[in]  task    Task.
 * @param[in]  status  New run status.
 */
void
set_task_run_status (task_t task, task_status_t status)
{
  char *uuid;
  char *name;

  if ((task == current_scanner_task) && current_report)
    sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;",
         status,
         current_report);
  sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;",
       status,
       task);

  task_uuid (task, &uuid);
  name = task_name (task);
  g_log ("event task", G_LOG_LEVEL_MESSAGE,
         "Status of task %s (%s) has changed to %s",
         name, uuid, run_status_name (status));
  free (uuid);
  free (name);

  event (task, EVENT_TASK_RUN_STATUS_CHANGED, (void*) status);
}

/**
 * @brief Atomically set the run state of a task to requested.
 *
 * @param[in]  task    Task.
 * @param[out] status  Old run status of task.
 *
 * @return 0 success, 1 task is active already.
 */
int
set_task_requested (task_t task, task_status_t *status)
{
  task_status_t run_status;

  sql_begin_exclusive ();

  run_status = task_run_status (task);
  if (run_status == TASK_STATUS_REQUESTED
      || run_status == TASK_STATUS_RUNNING
      || run_status == TASK_STATUS_STOP_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED_GIVEUP
      || run_status == TASK_STATUS_STOP_WAITING
      || run_status == TASK_STATUS_DELETE_REQUESTED
      || run_status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED
      || run_status == TASK_STATUS_DELETE_ULTIMATE_WAITING
      || run_status == TASK_STATUS_DELETE_WAITING)
    {
      sql ("END;");
      *status = run_status;
      return 1;
    }

  set_task_run_status (task, TASK_STATUS_REQUESTED);

  sql ("COMMIT;");

  *status = run_status;
  return 0;
}

/**
 * @brief Return the running report of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Current report of task if task is active, else (report_t) 0.
 */
report_t
task_running_report (task_t task)
{
  task_status_t run_status = task_run_status (task);
  if (run_status == TASK_STATUS_REQUESTED
      || run_status == TASK_STATUS_RUNNING)
    {
      return (unsigned int) sql_int ("SELECT max(id) FROM reports"
                                     " WHERE task = %llu AND end_time IS NULL"
                                     " AND scan_run_status = %u;",
                                     task,
                                     TASK_STATUS_RUNNING);
    }
  return (report_t) 0;
}

/**
 * @brief Return the current report of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Current report of task if task is active, else (report_t) 0.
 */
report_t
task_iterator_current_report (iterator_t *iterator)
{
  task_t task = get_iterator_resource (iterator);
  task_status_t run_status = task_iterator_run_status (iterator);
  if (run_status == TASK_STATUS_REQUESTED
      || run_status == TASK_STATUS_RUNNING
      || run_status == TASK_STATUS_DELETE_REQUESTED
      || run_status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED_GIVEUP
      || run_status == TASK_STATUS_STOPPED)
    {
      return (unsigned int) sql_int ("SELECT max(id) FROM reports"
                                     " WHERE task = %llu"
                                     " AND (scan_run_status = %u"
                                     " OR scan_run_status = %u"
                                     " OR scan_run_status = %u"
                                     " OR scan_run_status = %u"
                                     " OR scan_run_status = %u"
                                     " OR scan_run_status = %u"
                                     " OR scan_run_status = %u);",
                                     task,
                                     TASK_STATUS_REQUESTED,
                                     TASK_STATUS_RUNNING,
                                     TASK_STATUS_DELETE_REQUESTED,
                                     TASK_STATUS_DELETE_ULTIMATE_REQUESTED,
                                     TASK_STATUS_STOP_REQUESTED,
                                     TASK_STATUS_STOP_REQUESTED_GIVEUP,
                                     TASK_STATUS_STOPPED);
    }
  return (report_t) 0;
}

/**
 * @brief Return the upload progress of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Task upload progress, as a percentage, or -1 on error.
 */
int
task_upload_progress (task_t task)
{
  report_t report;
  report = task_running_report (task);
  if (report)
    {
      int count;
      if (report_scan_result_count (report, NULL, NULL, 0, NULL, NULL,
                                    0, 0, &count))
        return -1;
      return sql_int ("SELECT"
                      " max (min (((%i * 100) / upload_result_count), 100), -1)"
                      " FROM tasks"
                      " WHERE id = %llu;",
                      count,
                      task);
    }
  return -1;
}

/**
 * @brief Set the start time of a task.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New time.  ISO format.  Freed before return.
 */
void
set_task_start_time (task_t task, char* time)
{
  sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()"
       " WHERE id = %llu;",
       parse_iso_time (time),
       task);
  free (time);
}

/**
 * @brief Set the start time of a task.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New time.  Seconds since epoch.
 */
void
set_task_start_time_epoch (task_t task, int time)
{
  sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()"
       " WHERE id = %llu;",
       time,
       task);
}

/**
 * @brief Set the start time of a task.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New time.  OTP format (ctime).  Freed before return.
 */
void
set_task_start_time_otp (task_t task, char* time)
{
  sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()"
       " WHERE id = %llu;",
       parse_otp_time (time),
       task);
  free (time);
}

/**
 * @brief Get the report from the most recently completed invocation of task.
 *
 * @param[in]  task    The task.
 * @param[out] report  Report return, 0 if succesfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
int
task_last_report (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " ORDER BY date DESC LIMIT 1;",
                     task,
                     TASK_STATUS_DONE))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *report = 0;
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }
  return 0;
}

/**
 * @brief Get the report from second most recently completed invocation of task.
 *
 * @param[in]  task    The task.
 * @param[out] report  Report return, 0 if succesfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
static int
task_second_last_report (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " ORDER BY date DESC LIMIT 1 OFFSET 1;",
                     task,
                     TASK_STATUS_DONE))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *report = 0;
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }
  return 0;
}

/**
 * @brief Get the report from the most recently stopped invocation of task.
 *
 * @param[in]  task    The task.
 * @param[out] report  Report return, 0 if succesfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
int
task_last_stopped_report (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " ORDER BY date DESC LIMIT 1;",
                     task,
                     TASK_STATUS_STOPPED))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *report = 0;
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }
  return 0;
}

/**
 * @brief Get report ID from second most recently completed invocation of task.
 *
 * @param[in]  task  The task.
 *
 * @return The UUID of the report as a newly allocated string.
 */
gchar*
task_second_last_report_id (task_t task)
{
  return sql_string ("SELECT uuid FROM reports WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " ORDER BY date DESC LIMIT 1 OFFSET 1;",
                     task,
                     TASK_STATUS_DONE);
}

/**
 * @brief Add an alert to a task.
 *
 * @param[in]  task       Task.
 * @param[in]  alert  Alert.
 */
void
add_task_alert (task_t task, alert_t alert)
{
  sql ("INSERT INTO task_alerts (task, alert, alert_location)"
       " VALUES (%llu, %llu, " G_STRINGIFY (LOCATION_TABLE) ");",
       task,
       alert);
}

/**
 * @brief Set the alerts on a task, removing any previous alerts.
 *
 * @param[in]  task    Task.
 * @param[in]  alerts  Alerts.
 * @param[out] alert_id_return  ID of alert on "failed to find" error.
 *
 * @return 0 success, -1 error, 1 failed to find alert.
 */
int
set_task_alerts (task_t task, array_t *alerts, gchar **alert_id_return)
{
  alert_t alert = 0;
  guint index;

  sql_begin_immediate ();

  sql ("DELETE FROM task_alerts where task = %llu;", task);

  index = alerts->len;
  while (index--)
    {
      gchar *alert_id;

      alert_id = (gchar*) g_ptr_array_index (alerts, index);
      if (strcmp (alert_id, "0") == 0)
        continue;

      if (find_alert_with_permission (alert_id, &alert, "get_alerts"))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (alert == 0)
        {
          sql ("ROLLBACK;");
          if (alert_id_return) *alert_id_return = alert_id;
          return 1;
        }

      sql ("INSERT INTO task_alerts (task, alert, alert_location)"
           " VALUES (%llu, %llu, " G_STRINGIFY (LOCATION_TABLE) ");",
           task,
           alert);
    }

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Set the alterable state of a task.
 *
 * @param[in]  task       Task.
 * @param[in]  alterable  Whether task is alterable.
 */
void
set_task_alterable (task_t task, int alterable)
{
  sql ("UPDATE tasks SET alterable = %i WHERE id = %llu;",
       alterable,
       task);
}

/**
 * @brief Set observer groups on a task, removing any previous groups.
 *
 * @param[in]  task    Task.
 * @param[in]  groups  Groups.
 * @param[out] group_id_return  ID of group on "failed to find" error.
 *
 * @return 0 success, -1 error, 1 failed to find group.
 */
int
set_task_groups (task_t task, array_t *groups, gchar **group_id_return)
{
  group_t group = 0;
  guint index;

  sql_begin_immediate ();

  sql ("DELETE FROM permissions"
       " WHERE resource_type = 'task'"
       " AND resource = %llu"
       " AND subject_type = 'group'"
       " AND name = 'get';",
       task);

  index = 0;
  while (index < groups->len)
    {
      gchar *group_id;

      group_id = (gchar*) g_ptr_array_index (groups, index);
      if (strcmp (group_id, "0") == 0)
        {
          index++;
          continue;
        }

      if (find_group (group_id, &group))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (group == 0)
        {
          sql ("ROLLBACK;");
          if (group_id_return) *group_id_return = group_id;
          return 1;
        }

      sql ("INSERT INTO permissions"
           " (uuid, owner, name, comment, resource_type, resource,"
           "  resource_uuid, resource_location, subject_type, subject,"
           "  subject_location, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (),"
           "  (SELECT id FROM users WHERE users.uuid = '%s'),"
           "  'get_tasks', '', 'task', %llu,"
           "  (SELECT uuid FROM tasks WHERE tasks.id = %llu),"
           "  " G_STRINGIFY (LOCATION_TABLE) ", 'group', %llu,"
           "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());",
           current_credentials.uuid, task, task, group);

      index++;
    }

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Set the schedule of a task.
 *
 * @param[in]  task      Task.
 * @param[in]  schedule  Schedule.
 * @param[in]  periods   Number of schedule periods.
 *
 * @return 0 success, -1 error.
 */
int
set_task_schedule (task_t task, schedule_t schedule, int periods)
{
  sql ("UPDATE tasks"
       " SET schedule = %llu,"
       " schedule_periods = %i,"
       " schedule_next_time = (SELECT next_time (first_time,"
       "                                         period,"
       "                                         period_months)"
       "                       FROM schedules"
       "                       WHERE id = %llu),"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       schedule, periods, schedule, task);

  return 0;
}

/**
 * @brief Set the schedule of a task.
 *
 * @param[in]  task_id   Task UUID.
 * @param[in]  schedule  Schedule.
 * @param[in]  periods   Number of schedule periods.
 *
 * @return 0 success, -1 error.
 */
int
set_task_schedule_uuid (const gchar *task_id, schedule_t schedule, int periods)
{
  gchar *quoted_task_id;

  quoted_task_id = sql_quote (task_id);
  sql ("UPDATE tasks"
       " SET schedule = %llu,"
       " schedule_periods = %i,"
       " schedule_next_time = (SELECT next_time (first_time,"
       "                                         period,"
       "                                         period_months)"
       "                       FROM schedules"
       "                       WHERE id = %llu),"
       " modification_time = m_now ()"
       " WHERE uuid = '%s';",
       schedule, periods, schedule, quoted_task_id);
  g_free (quoted_task_id);

  return 0;
}

/**
 * @brief Set the schedule periods of a task.
 *
 * The task modification time stays the same.
 *
 * @param[in]  task_id   Task UUID.
 * @param[in]  periods   Schedule periods.
 *
 * @return 0 success, -1 error.
 */
int
set_task_schedule_periods (const gchar *task_id, int periods)
{
  gchar *quoted_task_id;

  quoted_task_id = sql_quote (task_id);
  sql ("UPDATE tasks"
       " SET schedule_periods = %i"
       " WHERE uuid = '%s';",
       periods, quoted_task_id);
  g_free (quoted_task_id);

  return 0;
}

/**
 * @brief Return the schedule of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Schedule.
 */
schedule_t
task_schedule (task_t task)
{
  schedule_t schedule = 0;
  switch (sql_int64 (&schedule,
                     "SELECT schedule FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return schedule;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        return 0;
        break;
    }
}

/**
 * @brief Return the schedule of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Schedule.
 */
schedule_t
task_schedule_uuid (const gchar *task_id)
{
  schedule_t schedule;
  gchar *quoted_task_id;

  quoted_task_id = sql_quote (task_id);
  schedule = 0;
  switch (sql_int64 (&schedule,
                     "SELECT schedule FROM tasks WHERE uuid = '%s';",
                     quoted_task_id))
    {
      case 0:
        g_free (quoted_task_id);
        return schedule;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_task_id);
        return 0;
        break;
    }
}

/**
 * @brief Get whether the task schedule is in the trash.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_schedule_in_trash (task_t task)
{
  return sql_int ("SELECT schedule_location = " G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks"
                  " WHERE id = %llu;",
                  task);
}

/**
 * @brief Get the number of times the period schedule should run on the task.
 *
 * @param[in]  task  Task.
 *
 * @return Number of times.
 */
int
task_schedule_periods (task_t task)
{
  return sql_int ("SELECT schedule_periods FROM tasks WHERE id = %llu;", task);
}

/**
 * @brief Set the next time a scheduled task will be due.
 *
 * @param[in]  task_id  Task UUID.
 *
 * @return Task schedule periods.
 */
int
task_schedule_periods_uuid (const gchar *task_id)
{
  gchar *quoted_task_id;
  int ret;

  quoted_task_id = sql_quote (task_id);
  ret = sql_int ("SELECT schedule_periods FROM tasks WHERE uuid = '%s';",
                 quoted_task_id);
  g_free (quoted_task_id);
  return ret;
}

/**
 * @brief Get next time a scheduled task will run, following schedule timezone.
 *
 * @param[in]  task  Task.
 *
 * @return If the task has a schedule, the next time the task will run (0 if it
 *         has already run), otherwise 0.
 */
int
task_schedule_next_time_tz (task_t task)
{
  int next_time;
  int ret;
  iterator_t schedules;
  get_data_t get;
  schedule_t schedule;

  schedule = task_schedule (task);
  memset (&get, '\0', sizeof (get));
  get.id = schedule_uuid (schedule);

  next_time = sql_int ("SELECT schedule_next_time FROM tasks"
                       " WHERE id = %llu;",
                       task);
  if (next_time == 0)
    return 0;

  ret = init_schedule_iterator (&schedules, &get);

  if (ret)
    return next_time;

  if (next (&schedules))
    next_time += schedule_iterator_initial_offset (&schedules)
                  - time_offset (schedule_iterator_timezone (&schedules),
                                 next_time);
  cleanup_iterator (&schedules);
  return next_time;
}

/**
 * @brief Set the next time a scheduled task will be due.
 *
 * @param[in]  task_id  Task UUID.
 */
time_t
task_schedule_next_time (const gchar *task_id)
{
  gchar *quoted_task_id;
  time_t ret;

  quoted_task_id = sql_quote (task_id);
  ret = (time_t) sql_int ("SELECT schedule_next_time FROM tasks"
                          " WHERE uuid = '%s';",
                          quoted_task_id);
  g_free (quoted_task_id);
  return ret;
}

/**
 * @brief Set the next time a scheduled task will be due.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New next time.
 */
void
set_task_schedule_next_time (task_t task, time_t time)
{
  sql ("UPDATE tasks SET schedule_next_time = %i WHERE id = %llu;",
       time, task);
}

/**
 * @brief Set the next time a scheduled task will be due.
 *
 * @param[in]  task_id  Task UUID.
 * @param[in]  time     New next time.
 */
void
set_task_schedule_next_time_uuid (const gchar *task_id, time_t time)
{
  gchar *quoted_task_id;

  quoted_task_id = sql_quote (task_id);
  sql ("UPDATE tasks SET schedule_next_time = %i WHERE uuid = '%s';",
       time, quoted_task_id);
  g_free (quoted_task_id);
}

/**
 * @brief Return the severity score of a task, taking overrides into account.
 *
 * @param[in]  task       Task.
 * @param[in]  overrides  Whether to apply overrides.
 * @param[in]  min_qod    Minimum QoD of results to count.
 * @param[in]  offset     Offset of report to get severity from:
 *                        0 = use last report, 1 = use next to last report
 *
 * @return Severity score of last report on task if there is one, as a freshly
 *  allocated string, else NULL.
 */
char *
task_severity (task_t task, int overrides, int min_qod, int offset)
{
  char* severity;
  gchar *severity_sql, *ov, *new_severity_sql;

  if (current_credentials.uuid == NULL)
    return NULL;

  if (task_target (task) == 0)
    /* Container task. */
    return NULL;

  if (setting_dynamic_severity_int ())
    severity_sql = g_strdup("CASE WHEN results.severity"
                            "          > " G_STRINGIFY (SEVERITY_LOG)
                            " THEN coalesce ((SELECT CAST (cvss_base"
                            "                              AS REAL)"
                            "                 FROM nvts"
                            "                 WHERE nvts.oid"
                            "                         = results.nvt),"
                            "                results.severity)"
                            "       FROM nvts"
                            "       WHERE nvts.oid = results.nvt)"
                            " ELSE results.severity END");
  else
    severity_sql = g_strdup ("results.severity");

  if (overrides)
    {
      gchar *owned_clause;

      owned_clause = where_owned_for_get ("override", NULL);

      ov = g_strdup_printf
            ("SELECT overrides.new_severity"
             " FROM overrides"
             " WHERE overrides.nvt = results.nvt"
             " AND %s"
             " AND ((overrides.end_time = 0)"
             "      OR (overrides.end_time >= m_now ()))"
             " AND (overrides.task = results.task"
             "      OR overrides.task = 0)"
             " AND (overrides.result = results.id"
             "      OR overrides.result = 0)"
             " AND (overrides.hosts is NULL"
             "      OR overrides.hosts = ''"
             "      OR hosts_contains (overrides.hosts, results.host))"
             " AND (overrides.port is NULL"
             "      OR overrides.port = ''"
             "      OR overrides.port = results.port)"
             " AND severity_matches_ov (%s, overrides.severity)"
             " ORDER BY overrides.result DESC, overrides.task DESC,"
             " overrides.port DESC, overrides.severity ASC,"
             " overrides.creation_time DESC"
             " LIMIT 1",
             owned_clause,
             severity_sql);

      new_severity_sql = g_strdup_printf ("coalesce ((%s), %s)",
                                          ov, severity_sql);

      g_free (owned_clause);
      g_free (ov);
    }
  else
    new_severity_sql = g_strdup (severity_sql);

  g_free (severity_sql);

  severity = sql_string ("SELECT max (%s)"
                         " FROM results"
                         " WHERE results.report"
                         "       = +(SELECT id FROM reports"
                         "           WHERE reports.task = %llu"
                         "           AND reports.scan_run_status = %u"
                         "           ORDER BY reports.date DESC"
                         "           LIMIT 1 OFFSET %d)"
                         "       AND results.qod"
                         "            >= %d;",
                         new_severity_sql,
                         task,
                         TASK_STATUS_DONE,
                         offset,
                         min_qod);

  g_free (new_severity_sql);

  return severity;
}

/**
 * @brief Return the severity score of a task, taking overrides into account.
 *
 * @param[in]  task       Task.
 * @param[in]  overrides  Whether to apply overrides.
 * @param[in]  min_qod    Minimum QoD of results to count.
 * @param[in]  offset     Offset of report to get severity from:
 *                        0 = use last report, 1 = use next to last report
 *
 * @return Severity score of last report on task as a double if there is one,
 *         else SEVERITY_MISSING.
 */
double
task_severity_double (task_t task, int overrides, int min_qod, int offset)
{
  char* severity;
  double severity_dbl;
  severity = task_severity (task, overrides, min_qod, offset);

  if (severity == NULL || sscanf (severity, "%lf", &severity_dbl) != 1)
    {
      free (severity);
      return SEVERITY_MISSING;
    }
  else
    {
      free (severity);
      return severity_dbl;
    }
}

/**
 * @brief Set the observers of a task.
 *
 * @param[in]  task       Task.
 * @param[in]  observers  Observers.
 *
 * @return 0 success, -1 error, 1 user name validation failed, 2 failed to find
 *         user.
 */
int
set_task_observers (task_t task, const gchar *observers)
{
  gchar **split, **point;
  GList *added;

  // TODO the tricky bit here is if you have to own the task to set observers.

  assert (current_credentials.username);

  added = NULL;
  split = g_strsplit_set (observers, " ,", 0);

  sql_begin_immediate ();

  sql ("DELETE FROM permissions"
       " WHERE resource_type = 'task' AND resource = %llu"
       " AND subject_type = 'user';",
       task);

  point = split;
  while (*point)
    {
      user_t user;
      gchar *name;

      name = *point;

      g_strstrip (name);

      if (strcmp (name, "") == 0)
        {
          point++;
          continue;
        }

      if ((strcmp (name, current_credentials.username) == 0)
          || g_list_find_custom (added, name, (GCompareFunc) strcmp))
        {
          point++;
          continue;
        }

      added = g_list_prepend (added, name);

      if (openvas_user_exists (name) == 0)
        {
          g_list_free (added);
          g_strfreev (split);
          sql ("ROLLBACK;");
          return 2;
        }

      if (find_user_by_name (name, &user))
        {
          g_list_free (added);
          g_strfreev (split);
          sql ("ROLLBACK;");
          return -1;
        }

      if (user == 0)
        {
          gchar *uuid;

          /** @todo Similar to validate_user in openvas-administrator. */
          if (g_regex_match_simple ("^[[:alnum:]-_]+$", name, 0, 0) == 0)
            {
              g_list_free (added);
              g_strfreev (split);
              sql ("ROLLBACK;");
              return 1;
            }

          uuid = openvas_user_uuid (name);

          if (uuid == NULL)
            {
              g_list_free (added);
              g_strfreev (split);
              sql ("ROLLBACK;");
              return -1;
            }

          if (sql_int ("SELECT count(*) FROM users WHERE uuid = '%s';",
                       uuid)
              == 0)
            {
              gchar *quoted_name;
              quoted_name = sql_quote (name);
              sql ("INSERT INTO users"
                   " (uuid, name, creation_time, modification_time)"
                   " VALUES"
                   " ('%s', '%s', m_now (), m_now ());",
                   uuid,
                   quoted_name);
              g_free (quoted_name);

              user = sql_last_insert_id ();
            }
          else
            {
              /* user_find should have found it. */
              assert (0);
              g_free (uuid);
              g_list_free (added);
              g_strfreev (split);
              sql ("ROLLBACK;");
              return -1;
            }

          g_free (uuid);
        }

      sql ("INSERT INTO permissions"
           " (uuid, owner, name, comment, resource_type, resource,"
           "  resource_uuid, resource_location, subject_type, subject,"
           "  subject_location, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (),"
           "  (SELECT id FROM users WHERE users.uuid = '%s'),"
           "  'get_tasks', '', 'task', %llu,"
           "  (SELECT uuid FROM tasks WHERE tasks.id = %llu),"
           "  " G_STRINGIFY (LOCATION_TABLE) ", 'user', %llu,"
           "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());",
           current_credentials.uuid, task, task, user);

      point++;
    }

  g_list_free (added);
  g_strfreev (split);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Clear once-off schedules from tasks where the duration has passed.
 *
 * @param[in]  task  Task.  0 for all.
 */
void
clear_duration_schedules (task_t task)
{
  gchar *task_element;
  const gchar *duration_expired_element;

  if (task)
    task_element = g_strdup_printf (" AND id = %llu", task);
  else
    task_element = g_strdup ("");

  duration_expired_element
   = " AND (SELECT first_time + duration FROM schedules"
     "      WHERE schedules.id = schedule)"
     "     < m_now ()";

  sql ("UPDATE tasks"
       " SET schedule = 0,"
       " schedule_next_time = 0,"
       " schedule_periods = 0,"
       " modification_time = m_now ()"
       " WHERE schedule > 0"
       "%s"
       " AND (SELECT period FROM schedules WHERE schedules.id = schedule) = 0"
       " AND (SELECT duration FROM schedules WHERE schedules.id = schedule) > 0"
       "%s"
       " AND run_status != %i"
       " AND run_status != %i;",
       task_element,
       task ? "" : duration_expired_element,
       TASK_STATUS_RUNNING,
       TASK_STATUS_REQUESTED);

  g_free (task_element);
}

/**
 * @brief Update tasks with limited run schedules which have durations.
 *
 * If a task is given, assume that the task has finished.  Otherwise only
 * update the task if more time than the duration has passed the start time.
 *
 * @param[in]  task  Task.  0 for all.
 */
void
update_duration_schedule_periods (task_t task)
{
  gchar *task_element;
  const gchar *duration_expired_element;

  if (task)
    task_element = g_strdup_printf (" AND id = %llu", task);
  else
    task_element = g_strdup ("");

  duration_expired_element
   = /* The task has started, so assume that the start time was the last
      * most recent start of the period. */
     " AND (SELECT first_time"
     "             + (((m_now () - first_time) / period) * period)"
     "             + duration"
     "      FROM schedules"
     "      WHERE schedules.id = schedule)"
     "     < m_now ()";

  sql ("UPDATE tasks"
       " SET schedule = 0,"
       " schedule_next_time = 0,"
       " schedule_periods = 0,"
       " modification_time = m_now ()"
       " WHERE schedule > 0"
       "%s"
       " AND schedule_periods = 1"
       " AND (SELECT period FROM schedules WHERE schedules.id = schedule) > 0"
       " AND (SELECT duration FROM schedules WHERE schedules.id = schedule) > 0"
       " AND schedule_next_time = 0"  /* Set as flag when starting task. */
       "%s"
       " AND run_status != %i"
       " AND run_status != %i;",
       task_element,
       task ? "" : duration_expired_element,
       TASK_STATUS_RUNNING,
       TASK_STATUS_REQUESTED);
  g_free (task_element);
}


/* Results. */

/**
 * @brief Find a result for a set of permissions, given a UUID.
 *
 * @param[in]   uuid        UUID of result.
 * @param[out]  result      Result return, 0 if succesfully failed to find
 *                          result.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find result), TRUE on error.
 */
gboolean
find_result_with_permission (const char* uuid, result_t* result,
                             const char *permission)
{
  gchar *quoted_uuid = sql_quote (uuid);
  if (user_has_access_uuid ("result", quoted_uuid, permission, 0) == 0)
    {
      g_free (quoted_uuid);
      *result = 0;
      return FALSE;
    }
  switch (sql_int64 (result,
                     "SELECT id FROM results WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *result = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Make an OSP result.
 *
 * @param[in]  task         The task associated with the result.
 * @param[in]  host         Target host of result.
 * @param[in]  nvt          The uuid of oval definition that produced the
 *                          result, a title for the result otherwise.
 * @param[in]  type         Type of result.  "Alarm", etc.
 * @param[in]  description  Description of the result.
 * @param[in]  port         Result port.
 * @param[in]  severity     Result severity.
 *
 * @return A result descriptor for the new result, 0 if error.
 */
result_t
make_osp_result (task_t task, const char *host, const char *nvt,
                 const char *type, const char *description,
                 const char *port, const char *severity)
{
  char *nvt_revision = NULL, *quoted_desc, *quoted_nvt, *result_severity;
  char *quoted_port;

  assert (task);
  assert (type);

  if (nvt && g_str_has_prefix (nvt, "oval:"))
    nvt_revision = ovaldef_version (nvt);
  quoted_desc = sql_quote (description ?: "");
  quoted_nvt = sql_quote (nvt ?: "");
  quoted_port = sql_quote (port ?: "");
  if (!severity || !strcmp (severity, ""))
    {
      if (!strcmp (type, severity_to_type (SEVERITY_ERROR)))
        result_severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR));
      else
        {
          if (nvt && g_str_has_prefix (nvt, "CVE-"))
            result_severity = cve_cvss_base (nvt);
          else
            result_severity = g_strdup (G_STRINGIFY (SEVERITY_LOG));
        }
    }
  else
    result_severity = g_strdup (severity);
    result_severity = sql_quote (severity);
  sql ("INSERT into results"
       " (owner, date, task, host, port, nvt, nvt_version, severity, type,"
       "  qod, qod_type, description, uuid)"
       " VALUES (NULL, m_now(), %llu, '%s', '%s', '%s', '%s', '%s', '%s',"
       "         '%s', '', '%s', make_uuid ());",
       task, host ?: "", quoted_port, quoted_nvt, nvt_revision ?: "",
       result_severity ?: "0", type, G_STRINGIFY (QOD_DEFAULT), quoted_desc);
  g_free (result_severity);
  g_free (nvt_revision);
  g_free (quoted_desc);
  g_free (quoted_nvt);
  g_free (quoted_port);

  return sql_last_insert_id ();
}

/**
 * @brief Get QoD percentage for a qod_type string.
 *
 * @param[in]  qod_type   The QoD type string.
 *
 * @return A QoD percentage value, QOD_DEFAULT if string is NULL or unknown.
 */
int
qod_from_type (const char *qod_type)
{
  if (qod_type == NULL)
    return QOD_DEFAULT;
  else if (strcmp (qod_type, "exploit") == 0)
    return 100;
  else if  (strcmp (qod_type, "remote_vul") == 0)
    return 99;
  else if (strcmp (qod_type, "remote_app") == 0)
    return 98;
  else if (strcmp (qod_type, "package") == 0)
    return 97;
  else if (strcmp (qod_type, "registry") == 0)
    return 97;
  else if (strcmp (qod_type, "remote_active") == 0)
    return 95;
  else if (strcmp (qod_type, "remote_banner") == 0)
    return 80;
  else if (strcmp (qod_type, "executable_version") == 0)
    return 80;
  else if (strcmp (qod_type, "remote_analysis") == 0)
    return 70;
  else if (strcmp (qod_type, "remote_probe") == 0)
    return 50;
  else if (strcmp (qod_type, "remote_banner_unreliable") == 0)
    return 30;
  else if (strcmp (qod_type, "executable_version_unreliable") == 0)
    return 30;
  else if (strcmp (qod_type, "general_note") == 0)
    return 1;
  else
    return QOD_DEFAULT;
}

/**
 * @brief Make a result.
 *
 * @param[in]  task         The task associated with the result.
 * @param[in]  host         Host.
 * @param[in]  port         The port the result refers to.
 * @param[in]  nvt          The OID of the NVT that produced the result.
 * @param[in]  type         Type of result.  "Security Hole", etc.
 * @param[in]  description  Description of the result.
 *
 * @return A result descriptor for the new result, 0 if error.
 */
result_t
make_result (task_t task, const char* host, const char* port, const char* nvt,
             const char* type, const char* description)
{
  result_t result;
  gchar *nvt_revision, *severity;
  gchar *quoted_descr, *quoted_qod_type;
  int qod;
  nvt_t nvt_id = 0;

  if (nvt && strcmp (nvt, "") && (find_nvt (nvt, &nvt_id) || nvt_id <= 0))
    {
      g_warning ("NVT '%s' not found. Result not created.\n", nvt);
      return 0;
    }
  else if (nvt && strcmp (nvt, ""))
    {
      nvti_t *nvti;

      nvti = nvtis_lookup (nvti_cache, nvt);
      if (nvti)
        {
          gchar *qod_str, *qod_type;
          qod_str = tag_value (nvti_tag (nvti), "qod");
          qod_type = tag_value (nvti_tag (nvti), "qod_type");

          if (qod_str == NULL || sscanf (qod_str, "%d", &qod) != 1)
            qod = qod_from_type (qod_type);

          quoted_qod_type = sql_quote (qod_type);

          g_free (qod_str);
          g_free (qod_type);
        }
      else
        {
          qod = QOD_DEFAULT;
          quoted_qod_type = g_strdup ("");
        }

      if (strcasecmp (type, "Alarm") == 0)
        {
          severity = sql_string ("SELECT coalesce(cvss_base, '0.0')"
                                 " FROM nvts WHERE uuid = '%s';",
                                 nvt);

        }
      else if (strcasecmp (type, "Log Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_LOG));
      else if (strcasecmp (type, "Debug Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_DEBUG));
      else if (strcasecmp (type, "Error Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR));
      else
        {
          g_warning ("%s: Invalid message type: '%s'", __FUNCTION__, type);
          return 0;
        }
      nvt_revision = sql_string ("SELECT version FROM nvts WHERE uuid = '%s';",
                                 nvt);
    }
  else
    {
      qod = QOD_DEFAULT;
      quoted_qod_type = g_strdup ("");
      if (strcasecmp (type, "Log Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_LOG));
      else if (strcasecmp (type, "Debug Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_DEBUG));
      else if (strcasecmp (type, "Error Message") == 0)
        severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR));
      else
        {
          if (strcasecmp (type, "Alarm") == 0)
            g_warning ("%s: Message type requires an NVT: '%s'",
                       __FUNCTION__, type);
          else
            g_warning ("%s: Invalid message type: '%s'", __FUNCTION__, type);
          return 0;
        }
      nvt_revision = g_strdup ("");
    }

  if (!strcmp (severity, ""))
    {
      g_free (severity);
      severity = g_strdup ("0.0");
    }
  quoted_descr = sql_quote (description ?: "");
  sql ("INSERT into results"
       " (owner, date, task, host, port, nvt, nvt_version, severity, type,"
       "  description, uuid, qod, qod_type)"
       " VALUES"
       " (NULL, m_now (), %llu, '%s', '%s', '%s', '%s', '%s', '%s',"
       "  '%s', make_uuid (), %i, '%s');",
       task, host ?: "", port ?: "", nvt ?: "", nvt_revision, severity, type,
       quoted_descr, qod, quoted_qod_type);

  g_free (quoted_descr);
  g_free (quoted_qod_type);
  g_free (nvt_revision);
  g_free (severity);
  result = sql_last_insert_id ();
  return result;
}

/**
 * @brief Return the UUID of a result.
 *
 * @param[in]   result  Result.
 * @param[out]  id      Pointer to a newly allocated string.
 *
 * @return 0.
 */
int
result_uuid (result_t result, char ** id)
{
  *id = sql_string ("SELECT uuid FROM results WHERE id = %llu;",
                    result);
  return 0;
}

/**
 * @brief Get product detection results corresponding to a given vulnerability
 *        detection result.
 *
 * @param[in]   result      Vulnerability detection result.
 * @param[out]  ref         Detection result UUID.
 * @param[out]  product     Product name.
 * @param[out]  location    Product location.
 * @param[out]  oid         Detection script OID.
 * @param[out]  name        Detection script name.
 *
 * @return -1 on error, 0 on success.
 */
int
result_detection_reference (result_t result, char **ref, char **product,
                            char **location, char **oid, char **name)
{
  char *report, *host = NULL;

  if ((ref == NULL) || (product == NULL) || (location == NULL) || (oid == NULL)
      || (name == NULL))
    return -1;

  *ref = *product = *location = *oid = *name = NULL;
  report = sql_string ("SELECT report FROM results WHERE id = %llu;",
                       result);
  if (report == NULL)
    goto detect_cleanup;

  host = sql_string ("SELECT host FROM results where id = %llu;",
                     result);
  if (host == NULL)
    goto detect_cleanup;

  *oid = sql_string ("SELECT value"
                     " FROM report_host_details"
                     " WHERE report_host = (SELECT id"
                     "                      FROM report_hosts"
                     "                      WHERE report = %s"
                     "                      AND host = '%s')"
                     " AND name = 'detected_by'"
                     " AND source_name = (SELECT nvt FROM results"
                     "                    WHERE id = %llu);",
                     report, host, result);
  if (*oid == NULL)
    goto detect_cleanup;

  *location = sql_string ("SELECT value"
                          " FROM report_host_details"
                          " WHERE report_host = (SELECT id"
                          "                      FROM report_hosts"
                          "                      WHERE report = %s"
                          "                      AND host = '%s')"
                          " AND name = 'detected_at'"
                          " AND source_name = (SELECT nvt"
                          "                    FROM results"
                          "                    WHERE id = %llu);",
                          report, host, result);
  if (*location == NULL)
    goto detect_cleanup;

  *product = sql_string ("SELECT name"
                         " FROM report_host_details"
                         " WHERE report_host = (SELECT id"
                         "                      FROM report_hosts"
                         "                      WHERE report = %s"
                         "                      AND host = '%s')"
                         " AND source_name = '%s'"
                         " AND value = '%s';",
                         report, host, *oid, *location);
  if (*product == NULL)
    goto detect_cleanup;

  *name = sql_string ("SELECT name FROM nvts WHERE oid = '%s';", *oid);
  if (*name == NULL)
    goto detect_cleanup;

  /* Get the result produced by the detection NVT when it detected the
   * product.  The result port or description must include the product
   * location in order for this to work. */
  *ref = sql_string ("SELECT uuid"
                     " FROM results"
                     " WHERE report = %s"
                     " AND host = '%s'"
                     " AND nvt = '%s'"
                     " AND (description LIKE '%%%s%%'"
                     "      OR port LIKE '%%%s%%');",
                     report, host, *oid, *location, *location);
  if (*ref == NULL)
    goto detect_cleanup;

  g_free (report);
  g_free (host);

  return 0;

detect_cleanup:
  g_free (report);
  g_free (host);

  return -1;
}



/* Prognostics. */

/**
 * @brief Return highest CVE for an App.
 *
 * @param[in]  cpe  CPE.
 */
double
cpe_highest_cvss (const char *cpe)
{
  int highest;
  gchar *quoted_cpe;
  quoted_cpe = sql_quote (cpe);
  highest = sql_double ("SELECT"
                        " (CASE WHEN EXISTS (SELECT id FROM cpes"
                        "                    WHERE name = '%s')"
                        "  THEN (SELECT max_cvss FROM cpes WHERE name = '%s')"
                        "  ELSE -1"
                        "  END);",
                        quoted_cpe,
                        quoted_cpe);
  g_free (quoted_cpe);
  return highest;
}

/**
 * @brief Prognosis iterator prepared statement.
 */
static sql_stmt_t *prognosis_stmt = NULL;

/**
 * @brief Cleanup the prognosis iterator prepared statement.
 */
void
cleanup_prognosis_iterator ()
{
  if (prognosis_stmt)
    {
      sql_finalize (prognosis_stmt);
      prognosis_stmt = NULL;
    }
}

/**
 * @brief Initialise a prognosis iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  cpe       CPE.
 */
void
init_prognosis_iterator (iterator_t *iterator, const char *cpe)
{
  if (prognosis_stmt == NULL)
    prognosis_stmt = sql_prepare ("SELECT cves.name, cves.cvss,"
                                  "       cves.description, cpes.name"
                                  " FROM scap.cves, scap.cpes,"
                                  "      scap.affected_products"
                                  " WHERE cpes.name=$1"
                                  " AND cpes.id=affected_products.cpe"
                                  " AND cves.id=affected_products.cve"
                                  " ORDER BY CAST (cves.cvss AS NUMERIC)"
                                  " DESC;");
  else
    {
      if (sql_reset (prognosis_stmt))
        {
          g_warning ("%s: sql_reset failed\n", __FUNCTION__);
          abort ();
        }
    }

  if (prognosis_stmt == NULL)
    abort ();

  init_prepared_iterator (iterator, prognosis_stmt);

  /* Bind iterator. */
  if (sql_bind_text (prognosis_stmt, 1, cpe, -1))
    {
      g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
      abort ();
    }
}

DEF_ACCESS (prognosis_iterator_cve, 0);
DEF_ACCESS (prognosis_iterator_cvss, 1);
DEF_ACCESS (prognosis_iterator_description, 2);
DEF_ACCESS (prognosis_iterator_cpe, 3);

/**
 * @brief Get the CVSS from a result iterator as a double.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return CVSS.
 */
double
prognosis_iterator_cvss_double (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_double (iterator, 1);
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a search phrase.
 *
 * @param[in]  search_phrase  Phrase that results must include.  All results if
 *                            NULL or "".
 *
 * @return WHERE clause for search phrase if one is required, else NULL.
 */
static GString *
prognosis_where_search_phrase (const char* search_phrase)
{
  if (search_phrase)
    {
      GString *phrase_sql;
      gchar *quoted_search_phrase;

      if (strlen (search_phrase) == 0)
        return NULL;

      quoted_search_phrase = sql_quote (search_phrase);
      phrase_sql = g_string_new ("");
      g_string_append_printf (phrase_sql,
                              " AND (cves.description LIKE '%%%%%s%%%%'"
                              " OR cves.name LIKE '%%%%%s%%%%'"
                              " OR cpes.name LIKE '%%%%%s%%%%')",
                              quoted_search_phrase,
                              quoted_search_phrase,
                              quoted_search_phrase);
      g_free (quoted_search_phrase);

      return phrase_sql;
    }
  return NULL;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to levels.
 *
 * @param[in]  levels  String describing threat levels (message types)
 *                     to include in report (for example, "hmlgd" for
 *                     High, Medium, Low, loG and Debug).  All levels if
 *                     NULL.
 *
 * @return WHERE clause for levels if one is required, else NULL.
 */
static const char *
prognosis_where_levels (const char* levels)
{
  char *high, *medium, *low;

  if (levels == NULL || strlen (levels) == 0)
    return "";

  high = strchr (levels, 'h');
  medium = strchr (levels, 'm');
  low = strchr (levels, 'l');

  if (high && medium && low)
    return "";

  if (high && medium)
    return " AND cves.cvss > 2";

  if (high && low)
    return " AND (cves.cvss > 5 OR cves.cvss <= 2)";

  if (medium && low)
    return " AND cves.cvss <= 5";

  if (high)
    return " AND cves.cvss > 5";

  if (medium)
    return " AND cves.cvss <= 5 AND cves.cvss > 2";

  if (low)
    return " AND cves.cvss <= 2";

  return "";
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a minimum CVSS base.
 *
 * @param[in]  min_cvss_base  Minimum value for CVSS.
 *
 * @return WHERE clause if one is required, else NULL.
 */
static GString *
prognosis_where_cvss_base (const char* min_cvss_base)
{
  if (min_cvss_base)
    {
      GString *cvss_sql;
      gchar *quoted_min_cvss_base;

      if (strlen (min_cvss_base) == 0)
        return NULL;

      quoted_min_cvss_base = sql_quote (min_cvss_base);
      cvss_sql = g_string_new ("");
      g_string_append_printf (cvss_sql,
                              " AND CAST (cves.cvss AS REAL)"
                              " >= CAST ('%s' AS REAL)",
                              quoted_min_cvss_base);
      g_free (quoted_min_cvss_base);

      return cvss_sql;
    }
  return NULL;
}

/**
 * @brief Return SQL ORDER BY for sorting results in a prognostic report.
 *
 * @param[in]  sort_field  Name of the field to sort by.
 * @param[in]  ascending   Sort in ascending order if true, descending if false.
 *
 * @return ORDER BY clause.
 */
static GString *
prognosis_order_by (const char* sort_field, int ascending)
{
  GString* order_sql = g_string_new("");

  if (strcmp (sort_field, "ROWID") == 0)
    g_string_append_printf (order_sql,
                            " ORDER BY cves.id %s",
                            ascending ? "ASC" : "DESC");
  else if (strcmp (sort_field, "host") == 0)
    g_string_append_printf (order_sql,
                            " ORDER BY"
                            " inet ((SELECT host FROM report_hosts"
                            "        WHERE id"
                            "              = report_host_details.report_host))"
                            " %s,"
                            " severity DESC",
                            ascending ? "ASC" : "DESC");
  else if (strcmp (sort_field, "vulnerability") == 0)
    g_string_append_printf (order_sql,
                            " ORDER BY"
                            " vulnerability %s",
                            ascending ? "ASC" : "DESC");
  else if (strcmp (sort_field, "location") == 0)
    g_string_append_printf (order_sql,
                            " ORDER BY"
                            " location,"
                            " severity %s",
                            ascending ? "ASC" : "DESC");
  else
    g_string_append_printf (order_sql,
                            " ORDER BY"
                            " severity %s",
                            ascending ? "ASC" : "DESC");

  return order_sql;
}

/**
 * @brief Initialise a report host prognosis iterator.
 *
 * @param[in]  iterator     Iterator.
 * @param[in]  report_host  Report host whose prognosis the iterator loops over.
 *                          All report_hosts if NULL.
 * @param[in]  first_result   The result to start from.  The results are 0
 *                            indexed.
 * @param[in]  max_results    The maximum number of results returned.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in]  search_phrase  Phrase that results must include.  All results
 * @param[in]  min_cvss_base  Minimum value for CVSS.  All results if NULL.
 * @param[in]  sort_order     Whether to sort in ascending order.
 * @param[in]  sort_field     Name of the field to sort by.
 */
static void
init_host_prognosis_iterator (iterator_t* iterator, report_host_t report_host,
                              int first_result, int max_results,
                              const char *levels, const char *search_phrase,
                              const char *min_cvss_base, int sort_order,
                              const char *sort_field)
{
  GString *phrase_sql, *cvss_sql, *order_sql;

  if (levels == NULL) levels = "hmlgdf";

  phrase_sql = prognosis_where_search_phrase (search_phrase);
  cvss_sql = prognosis_where_cvss_base (min_cvss_base);
  order_sql = prognosis_order_by (sort_field, sort_order);

  init_iterator (iterator,
                 "SELECT cves.name AS vulnerability,"
                 "       CAST (cves.cvss AS NUMERIC) AS severity,"
                 "       cves.description,"
                 "       cpes.name AS location,"
                 "       (SELECT host FROM report_hosts"
                 "        WHERE id = %llu) AS host"
                 " FROM scap.cves, scap.cpes, scap.affected_products,"
                 "      report_host_details"
                 " WHERE report_host_details.report_host = %llu"
                 " AND cpes.name = report_host_details.value"
                 " AND report_host_details.name = 'App'"
                 " AND cpes.id=affected_products.cpe"
                 " AND cves.id=affected_products.cve"
                 "%s%s%s%s"
                 " LIMIT %s OFFSET %i;",
                 report_host,
                 report_host,
                 phrase_sql ? phrase_sql->str : "",
                 prognosis_where_levels (levels),
                 cvss_sql ? cvss_sql->str : "",
                 order_sql ? order_sql->str : "",
                 sql_select_limit (max_results),
                 first_result);

  if (phrase_sql) g_string_free (phrase_sql, TRUE);
  if (cvss_sql) g_string_free (cvss_sql, TRUE);
  if (order_sql) g_string_free (order_sql, TRUE);
}

/**
 * @brief Count all filtered results for a prognostic report.
 *
 * @param[in]   report_host    Report host for which to count.
 * @param[in]   search_phrase  Phrase that results must include.  All results
 * @param[in]   min_cvss_base  Minimum value for CVSS.  All results if NULL.
 * @param[out]  all            Number of messages to increment.
 * @param[out]  holes          Number of hole messages to increment.
 * @param[out]  infos          Number of info messages to increment.
 * @param[out]  warnings       Number of warning messages to increment.
 */
static void
prognostic_report_result_count (report_host_t report_host,
                                const char *search_phrase,
                                const char *min_cvss_base, int *all,
                                int *holes, int *infos, int *warnings)
{
  GString *phrase_sql, *cvss_sql;
  int host_holes, host_warnings, host_infos;

  phrase_sql = prognosis_where_search_phrase (search_phrase);
  cvss_sql = prognosis_where_cvss_base (min_cvss_base);

  host_holes = sql_int ("SELECT count (*)"
                        " FROM scap.cves, scap.cpes, scap.affected_products,"
                        "      report_host_details"
                        " WHERE report_host_details.report_host = %llu"
                        " AND cpes.name = report_host_details.value"
                        " AND report_host_details.name = 'App'"
                        " AND cpes.id=affected_products.cpe"
                        " AND cves.id=affected_products.cve"
                        "%s%s%s;",
                        report_host,
                        phrase_sql ? phrase_sql->str : "",
                        prognosis_where_levels ("h"),
                        cvss_sql ? cvss_sql->str : "");

  host_warnings = sql_int ("SELECT count (*)"
                           " FROM scap.cves, scap.cpes, scap.affected_products,"
                           "      report_host_details"
                           " WHERE report_host_details.report_host = %llu"
                           " AND cpes.name = report_host_details.value"
                           " AND report_host_details.name = 'App'"
                           " AND cpes.id=affected_products.cpe"
                           " AND cves.id=affected_products.cve"
                           "%s%s%s;",
                           report_host,
                           phrase_sql ? phrase_sql->str : "",
                           prognosis_where_levels ("m"),
                           cvss_sql ? cvss_sql->str : "");

  host_infos = sql_int ("SELECT count (*)"
                        " FROM scap.cves, scap.cpes, scap.affected_products,"
                        "      report_host_details"
                        " WHERE report_host_details.report_host = %llu"
                        " AND cpes.name = report_host_details.value"
                        " AND report_host_details.name = 'App'"
                        " AND cpes.id=affected_products.cpe"
                        " AND cves.id=affected_products.cve"
                        "%s%s%s;",
                        report_host,
                        phrase_sql ? phrase_sql->str : "",
                        prognosis_where_levels ("l"),
                        cvss_sql ? cvss_sql->str : "");

  *all += host_holes + host_warnings + host_infos;

  *holes += host_holes;
  *warnings += host_warnings;
  *infos += host_infos;

  if (phrase_sql) g_string_free (phrase_sql, TRUE);
  if (cvss_sql) g_string_free (cvss_sql, TRUE);
}

/**
 * @brief Count total number of results for a prognostic report.
 *
 * @param[in]   report_host    Report host for which to count.
 * @param[out]  total          Total count of all results.
 */
static void
prognostic_report_result_total (report_host_t report_host, int *total)
{
  if (total)
    *total = sql_int ("SELECT count (*)"
                      " FROM scap.cves, scap.cpes, scap.affected_products,"
                      "      report_host_details"
                      " WHERE report_host_details.report_host = %llu"
                      " AND cpes.name = report_host_details.value"
                      " AND report_host_details.name = 'App'"
                      " AND cpes.id=affected_products.cpe"
                      " AND cves.id=affected_products.cve;",
                      report_host);
}


/* Reports. */

/**
 * @brief Clear all cached report result counts.
 *
 * @param[in]  override  Flag for override or regular case.
 */
void
reports_clear_count_cache (int override)
{
  sql ("DELETE FROM report_counts WHERE override = %d;",
       override);
}

/**
 * @brief Rebuild the report count cache for all reports and users.
 *
 * @param[in]  clear        Whether to clear the cache before rebuilding.
 * @param[out] changes_out  The number of processed user/report combinations.
 */
void
reports_build_count_cache (int clear, int* changes_out)
{
  int changes;
  int user_changes, user_changes_no_ov, user_changes_ov;
  iterator_t users;
  iterator_t reports;
  gchar *old_uuid, *old_username;
  changes = 0;

  if (clear)
    {
      reports_clear_count_cache (0);
      reports_clear_count_cache (1);
    }

  old_uuid = current_credentials.uuid;
  old_username = current_credentials.username;
  init_iterator (&users, "SELECT id, uuid, name FROM users;");
  while (next (&users))
    {
      user_changes = 0;
      user_changes_no_ov = 0;
      user_changes_ov = 0;
      gchar *user_uuid = g_strdup (iterator_string (&users, 1));
      gchar *user_name = g_strdup (iterator_string (&users, 2));
      gchar *owned_clause;
      g_debug ("%s: Rebuilding report cache for user '%s' (%s)",
               __FUNCTION__, user_name, user_uuid);
      current_credentials.uuid = user_uuid;
      current_credentials.username = user_name;

      owned_clause = where_owned_for_get ("report", NULL);
      init_iterator (&reports,
                      "SELECT id, uuid FROM reports",
                      owned_clause);
      g_free (owned_clause);
      while (next (&reports))
        {
          report_t report = iterator_int64 (&reports, 0);
          const char* report_id = iterator_string (&reports, 1);
          if (user_has_access_uuid ("report", report_id, NULL, 0))
            {
              int updated = 0;
              severity_data_t severity_data;

              // Cache report without overrides
              if (clear || report_counts_cache_exists (report, 0) == 0)
                {
                  init_severity_data (&severity_data);
                  report_severity_data (report, 0, NULL, NULL, NULL,
                                        NULL, 0, 0, &severity_data,
                                        NULL);
                  cache_report_counts (report, 0, &severity_data, 0);
                  cleanup_severity_data (&severity_data);
                  updated = 1;
                  user_changes_no_ov++;
                }

              // Cache report with overrides
              if (clear || report_counts_cache_exists (report, 1) == 0)
                {
                  init_severity_data (&severity_data);
                  report_severity_data (report, 1, NULL, NULL, NULL,
                                        NULL, 0, 0, &severity_data,
                                        NULL);
                  cache_report_counts (report, 1, &severity_data, 0);
                  cleanup_severity_data (&severity_data);
                  updated = 1;
                  user_changes_ov++;
                }

              if (updated)
                {
                  changes++;
                  user_changes++;
                }
            }
        }
      cleanup_iterator (&reports);

      g_debug ("%s: Rebuilt cache for %d reports"
               " (%d with overrides / %d without)",
               __FUNCTION__, user_changes,
               user_changes_ov, user_changes_no_ov);

      g_free (user_uuid);
      g_free (user_name);
    }
  cleanup_iterator (&users);
  current_credentials.uuid = old_uuid;
  current_credentials.username = old_username;

  if (changes_out)
    *changes_out = changes;
}

/**
 * @brief Cached report counts.
 *
 * @param[in]  report  Report.
 */
void
report_cache_counts (report_t report)
{
  int debugs, holes, infos, logs, warnings, false_positives;
  double severity;
  // TODO: Cache for non-default QoD
  report_counts_id (report, &debugs, &holes, &infos, &logs, &warnings,
                    &false_positives, &severity, 0, NULL, 0,
                    G_STRINGIFY (MIN_QOD_DEFAULT));
  report_counts_id (report, &debugs, &holes, &infos, &logs, &warnings,
                    &false_positives, &severity, 1, NULL, 0,
                    G_STRINGIFY (MIN_QOD_DEFAULT));
}

/**
 * @brief Make a report.
 *
 * @param[in]  task    The task associated with the report.
 * @param[in]  uuid    The UUID of the report.
 * @param[in]  status  The run status of the scan associated with the report.
 *
 * @return A report descriptor for the new report.
 */
report_t
make_report (task_t task, const char* uuid, task_status_t status)
{
  sql ("INSERT into reports (uuid, owner, hidden, task, date, nbefile, comment,"
       " scan_run_status, slave_progress, slave_task_uuid)"
       " VALUES ('%s',"
       " (SELECT owner FROM tasks WHERE tasks.id = %llu),"
       " 0, %llu, %i, '', '', %u, 0, '');",
       uuid, task, task, time (NULL), status);
  return sql_last_insert_id ();
}

/**
 * @brief Create the current report for a task.
 *
 * @param[in]   task       The task.
 * @param[out]  report_id  Report ID.
 * @param[in]   status     Run status of scan associated with report.
 *
 * @return 0 success, -1 current_report is already set, -2 failed to generate ID.
 */
int
create_current_report (task_t task, char **report_id, task_status_t status)
{
  char *id;

  assert (current_report == (report_t) 0);

  if (current_report) return -1;

  if (report_id == NULL) report_id = &id;

  /* Generate report UUID. */

  *report_id = openvas_uuid_make ();
  if (*report_id == NULL) return -2;

  /* Create the report. */

  current_report = make_report (task, *report_id, status);

  return 0;
}

/**
 * @brief Free a host detail.
 *
 * @param[in]  detail  Host detail.
 */
void
host_detail_free (host_detail_t *detail)
{
  g_free (detail->ip);
  g_free (detail->name);
  g_free (detail->source_desc);
  g_free (detail->source_name);
  g_free (detail->source_type);
  g_free (detail->value);
}

/**
 * @brief Create the current report for a task.
 *
 * @param[in]   report      The detail's report.
 * @param[in]   host        The detail's host.
 * @param[in]   s_type      The detail's source type.
 * @param[in]   s_name      The detail's source name.
 * @param[in]   s_desc      The detail's source description.
 * @param[in]   name        The detail's name.
 * @param[in]   type        The detail's type.
 */
void
insert_report_host_detail (report_t report, const char *host,
                           const char *s_type, const char *s_name,
                           const char *s_desc, const char *name,
                           const char *value)
{
  char *quoted_host, *quoted_source_name, *quoted_source_type;
  char *quoted_source_desc, *quoted_name, *quoted_value;

  quoted_host = sql_quote (host);
  quoted_source_type = sql_quote (s_type);
  quoted_source_name = sql_quote (s_name);
  quoted_source_desc = sql_quote (s_desc);
  quoted_name = sql_quote (name);
  quoted_value = sql_quote (value);
  sql ("INSERT INTO report_host_details"
       " (report_host, source_type, source_name, source_description,"
       "  name, value)"
       " VALUES"
       " ((SELECT id FROM report_hosts"
       "   WHERE report = %llu AND host = '%s'),"
       "  '%s', '%s', '%s', '%s', '%s');",
       report, quoted_host, quoted_source_type, quoted_source_name,
       quoted_source_desc, quoted_name, quoted_value);

  g_free (quoted_host);
  g_free (quoted_source_type);
  g_free (quoted_source_name);
  g_free (quoted_source_desc);
  g_free (quoted_name);
  g_free (quoted_value);
}

/**
 * @brief Create a report from an array of results.
 *
 * @param[in]   results       Array of create_report_result_t pointers.
 * @param[in]   task_id       UUID of container task, or NULL to create new one.
 * @param[in]   task_name     Name for container task.
 * @param[in]   task_comment  Comment for container task.
 * @param[in]   scan_start    Scan start time text.
 * @param[in]   scan_end      Scan end time text.
 * @param[in]   host_starts   Array of create_report_result_t pointers.  Host
 *                            name in host, time in description.
 * @param[in]   host_ends     Array of create_report_result_t pointers.  Host
 *                            name in host, time in description.
 * @param[in]   details       Array of host_detail_t pointers.
 * @param[out]  report_id     Report ID.
 *
 * @return 0 success, 99 permission denied, -1 error, -2 failed to generate ID,
 *         -3 task_name is NULL, -4 failed to find task, -5 task must be
 *         container.
 */
int
create_report (array_t *results, const char *task_id, const char *task_name,
               const char *task_comment, const char *scan_start,
               const char *scan_end, array_t *host_starts,
               array_t *host_ends, array_t *details, char **report_id)
{
  int index;
  create_report_result_t *result, *end, *start;
  report_t report;
  user_t owner;
  task_t task;
  pid_t pid;
  host_detail_t *detail;

  if (user_may ("create_report") == 0)
    return 99;

  if (task_id == NULL && task_name == NULL)
    return -3;

  /* Find or create the task. */

  sql_begin_immediate ();
  if (task_id)
    {
      int rc = 0;

      if (find_task (task_id, &task))
        rc = -1;
      else if (task == 0)
        rc = -4;
      else if (task_target (task))
        rc = -5;
      if (rc)
        {
          sql ("ROLLBACK;");
          return rc;
        }
    }
  else
    task = make_task (g_strdup (task_name),
                      task_comment ? g_strdup (task_comment) : NULL);

  /* Generate report UUID. */

  *report_id = openvas_uuid_make ();
  if (*report_id == NULL) return -2;

  /* Create the report. */

  report = make_report (task, *report_id, TASK_STATUS_RUNNING);

  if (scan_start)
    {
      sql ("UPDATE reports SET start_time = %i WHERE id = %llu;",
           parse_iso_time (scan_start),
           report);
    }

  if (scan_end)
    {
      sql ("UPDATE reports SET end_time = %i WHERE id = %llu;",
           parse_iso_time (scan_end),
           report);
    }

  /* Show that the upload has started. */

  set_task_run_status (task, TASK_STATUS_RUNNING);
  sql ("UPDATE tasks SET upload_result_count = %llu WHERE id = %llu;",
       results->len,
       task);
  sql ("COMMIT;");

  /* Fork a child to import the results while the parent responds to the
   * client. */

  pid = fork ();
  switch (pid)
    {
      case 0:
        /* Child.  Reopen the database (required after fork) and carry on
         * to import the reports, . */
        reinit_manage_process ();
        break;
      case -1:
        /* Parent when error. */
        g_warning ("%s: fork: %s\n", __FUNCTION__, strerror (errno));
        set_task_run_status (task, TASK_STATUS_INTERNAL_ERROR);
        return -1;
        break;
      default:
        /* Parent.  Return, in order to respond to client. */
        return 0;
        break;
    }

  /* Add the results. */

  if (sql_int64 (&owner,
                 "SELECT owner FROM tasks WHERE tasks.id = %llu",
                 task))
    {
      g_warning ("%s: failed to get owner of task\n", __FUNCTION__);
      return -1;
    }

  sql_begin_immediate ();
  index = 0;
  while ((start = (create_report_result_t*) g_ptr_array_index (host_starts,
                                                               index++)))
    if (start->host && start->description)
      manage_report_host_add (report, start->host,
                              parse_iso_time (start->description),
                              0);

  index = 0;
  while ((result = (create_report_result_t*) g_ptr_array_index (results,
                                                                index++)))
    {
      gchar *quoted_host, *quoted_port, *quoted_nvt_oid;
      gchar *quoted_description, *quoted_scan_nvt_version, *quoted_severity;
      gchar *quoted_qod, *quoted_qod_type;

      quoted_host = sql_quote (result->host ? result->host : "");
      quoted_port = sql_quote (result->port ? result->port : "");
      quoted_nvt_oid = sql_quote (result->nvt_oid ? result->nvt_oid : "");
      quoted_description = sql_quote (result->description
                                       ? result->description
                                       : "");
      quoted_scan_nvt_version = sql_quote (result->scan_nvt_version
                                       ? result->scan_nvt_version
                                       : "");
      quoted_severity =  sql_quote (result->severity ? result->severity : "");
      if (result->qod && strcmp (result->qod, "") && strcmp (result->qod, "0"))
        quoted_qod = sql_quote (result->qod);
      else
        quoted_qod = g_strdup (G_STRINGIFY (QOD_DEFAULT));
      quoted_qod_type = sql_quote (result->qod_type ? result->qod_type : "");
      sql ("INSERT INTO results"
           " (uuid, owner, date, task, host, port, nvt, type, description,"
           "  nvt_version, severity, qod, qod_type)"
           " VALUES"
           " (make_uuid (), %llu, m_now (), %llu, '%s', '%s', '%s', '%s', '%s',"
           "  '%s', '%s', '%s', '%s');",
           owner,
           task,
           quoted_host,
           quoted_port,
           quoted_nvt_oid,
           result->threat
            ? threat_message_type (result->threat)
            : "Log Message",
           quoted_description,
           quoted_scan_nvt_version,
           quoted_severity,
           quoted_qod,
           quoted_qod_type);

      g_free (quoted_host);
      g_free (quoted_port);
      g_free (quoted_nvt_oid);
      g_free (quoted_description);
      g_free (quoted_scan_nvt_version);
      g_free (quoted_severity);
      g_free (quoted_qod);
      g_free (quoted_qod_type);

      report_add_result (report, sql_last_insert_id ());
    }

  index = 0;
  while ((end = (create_report_result_t*) g_ptr_array_index (host_ends,
                                                             index++)))
    if (end->host)
      {
        gchar *quoted_host;

        quoted_host = sql_quote (end->host);

        if (end->description)
          sql ("UPDATE report_hosts SET end_time = %i"
               " WHERE report = %llu AND host = '%s';",
               parse_iso_time (end->description),
               report,
               quoted_host);
        else
          sql ("UPDATE report_hosts SET end_time = NULL"
               " WHERE report = %llu AND host = '%s';",
               report,
               quoted_host);

        g_free (quoted_host);
      }

  index = 0;
  while ((detail = (host_detail_t*) g_ptr_array_index (details, index++)))
    if (detail->ip && detail->name)
      insert_report_host_detail
       (report, detail->ip, detail->source_type ?: "",
        detail->source_name ?: "", detail->source_desc ?: "", detail->name,
        detail->value ?: "");
  sql ("COMMIT;");

  current_scanner_task = task;
  current_report = report;
  set_task_run_status (task, TASK_STATUS_DONE);
  current_scanner_task = 0;
  current_report = 0;

  exit (EXIT_SUCCESS);
  /*@notreached@*/
  return 0;
}

/**
 * @brief Return the UUID of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Report UUID.
 */
char*
report_uuid (report_t report)
{
  return sql_string ("SELECT uuid FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the task of a report.
 *
 * @param[in]   report  A report.
 * @param[out]  task    Task return, 0 if succesfully failed to find task.
 *
 * @return FALSE on success (including if failed to find report), TRUE on error.
 */
gboolean
report_task (report_t report, task_t *task)
{
  switch (sql_int64 (task,
                     "SELECT task FROM reports WHERE id = %llu;",
                     report))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *task = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return TRUE;
        break;
    }
  return FALSE;
}

/**
 * @brief Return the UUID of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave UUID.
 */
char*
report_slave_uuid (report_t report)
{
  return sql_string ("SELECT slave_uuid FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the name of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave name.
 */
char*
report_slave_name (report_t report)
{
  return sql_string ("SELECT slave_name FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the host of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave UUID.
 */
char*
report_slave_host (report_t report)
{
  return sql_string ("SELECT slave_host FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the host of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave UUID.
 */
char*
report_slave_port (report_t report)
{
  return sql_string ("SELECT slave_port FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the source interface of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Source interface.
 */
char*
report_source_iface (report_t report)
{
  return sql_string ("SELECT source_iface FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Set the UUID of the slave on a report.
 *
 * @param[in]  report  Report.
 * @param[in]  uuid    UUID.
 */
void
report_set_slave_uuid (report_t report, const gchar *uuid)
{
  gchar *quoted_uuid;
  quoted_uuid = sql_quote (uuid);
  sql ("UPDATE reports SET slave_uuid = '%s' WHERE id = %llu;",
       quoted_uuid,
       report);
  g_free (quoted_uuid);
}

/**
 * @brief Set the name of the slave on a report.
 *
 * @param[in]  report  Report.
 * @param[in]  name    Name.
 */
void
report_set_slave_name (report_t report, const gchar *name)
{
  gchar *quoted_name;
  quoted_name = sql_quote (name);
  sql ("UPDATE reports SET slave_name = '%s' WHERE id = %llu;",
       quoted_name,
       report);
  g_free (quoted_name);
}

/**
 * @brief Set the host of the slave of a report.
 *
 * @param[in]  report  Report.
 * @param[in]  host    Host.
 */
void
report_set_slave_host (report_t report, const gchar *host)
{
  gchar *quoted_host;
  quoted_host = sql_quote (host);
  sql ("UPDATE reports SET slave_host = '%s' WHERE id = %llu;",
       quoted_host,
       report);
  g_free (quoted_host);
}

/**
 * @brief Set the port of the slave of a report.
 *
 * @param[in]  report  Report.
 * @param[in]  port    Port.
 */
void
report_set_slave_port (report_t report, int port)
{
  sql ("UPDATE reports SET slave_port = %i WHERE id = %llu;",
       port,
       report);
}

/**
 * @brief Set the source interface of a report.
 *
 * @param[in]  report  Report.
 * @param[in]  iface   Source interface.
 */
void
report_set_source_iface (report_t report, const gchar *iface)
{
  gchar *quoted_iface;
  quoted_iface = sql_quote (iface);
  sql ("UPDATE reports SET source_iface = '%s' WHERE id = %llu;",
       quoted_iface,
       report);
  g_free (quoted_iface);
}

/**
 * @brief Add a result to a report.
 *
 * @param[in]  report  The report.
 * @param[in]  result  The result.
 */
void
report_add_result (report_t report, result_t result)
{
  char *ov_severity_str;
  double severity, ov_severity;
  int qod;
  rowid_t rowid;
  gchar *owned_clause;

  if (report == 0 || result == 0)
    return;

  sql ("UPDATE results SET report = %llu,"
       "                   owner = (SELECT reports.owner"
       "                            FROM reports WHERE id = %llu)"
       " WHERE id = %llu;",
       report, report, result);

  qod = sql_int ("SELECT qod FROM results WHERE id = %llu;",
                 result);

  if (qod < MIN_QOD_DEFAULT)
    return;

  severity = sql_double ("SELECT severity FROM results WHERE id = %llu;",
                         result);

  rowid = 0;
  sql_int64 (&rowid,
             "SELECT id FROM report_counts"
             " WHERE report = %llu"
             " AND \"user\" = (SELECT id FROM users WHERE users.uuid = '%s')"
             " AND override = 0"
             " AND severity = %1.1f;",
             report, current_credentials.uuid, severity);
  if (rowid)
    sql ("UPDATE report_counts SET count = count + 1"
         " WHERE id = %llu;", rowid);
  else
    sql ("INSERT INTO report_counts"
         " (report, \"user\", override, severity, count, end_time)"
         " VALUES"
         " (%llu, (SELECT id FROM users WHERE uuid='%s'), 0, %1.1f, 1, 0);",
         report, current_credentials.uuid, severity);

  owned_clause = where_owned_for_get ("override", NULL);

  ov_severity_str
    = sql_string ("SELECT coalesce (overrides.new_severity, %1.1f)"
                  " FROM overrides, results"
                  " WHERE results.id = %llu"
                  " AND overrides.nvt = results.nvt"
                  " AND %s"
                  " AND ((overrides.end_time = 0)"
                  "      OR (overrides.end_time >= m_now ()))"
                  " AND (overrides.task ="
                  "      (SELECT reports.task FROM reports"
                  "       WHERE reports.id = %llu)"
                  "      OR overrides.task = 0)"
                  " AND (overrides.result = results.id"
                  "      OR overrides.result = 0)"
                  " AND (overrides.hosts is NULL"
                  "      OR overrides.hosts = ''"
                  "      OR hosts_contains (overrides.hosts, results.host))"
                  " AND (overrides.port is NULL"
                  "      OR overrides.port = ''"
                  "      OR overrides.port = results.port)"
                  " AND severity_matches_ov (%1.1f, overrides.severity)"
                  " ORDER BY overrides.result DESC, overrides.task DESC,"
                  " overrides.port DESC, overrides.severity ASC,"
                  " overrides.creation_time DESC"
                  " LIMIT 1",
                  severity,
                  result,
                  owned_clause,
                  report,
                  severity);

  g_free (owned_clause);

  if (ov_severity_str == NULL
      || (sscanf (ov_severity_str, "%lf", &ov_severity) != 1))
    ov_severity = severity;

  rowid = 0;
  sql_int64 (&rowid,
             "SELECT id FROM report_counts"
             " WHERE report = %llu"
             " AND \"user\" = (SELECT id FROM users WHERE users.uuid = '%s')"
             " AND override = 1"
             " AND severity = %1.1f;",
             report, current_credentials.uuid, ov_severity);
  if (rowid)
    sql ("UPDATE report_counts"
         " SET count = count + 1"
         " WHERE id = %llu;",
         rowid);
  else
    sql ("INSERT INTO report_counts"
         " (report, \"user\", override, severity, count, end_time)"
         " VALUES"
         " (%llu, (SELECT id FROM users WHERE uuid='%s'), 1, %1.1f, 1, 0);",
         report, current_credentials.uuid, ov_severity);

  sql ("UPDATE report_counts"
       " SET end_time = (SELECT coalesce(min(overrides.end_time), 0)"
       "                 FROM overrides, results"
       "                 WHERE overrides.nvt = results.nvt"
       "                 AND results.report = %llu"
       "                 AND overrides.end_time >= m_now ())"
       " WHERE report = %llu AND override = 1;",
       report, report);
}

/**
 * @brief Filter columns for report iterator.
 */
#define REPORT_ITERATOR_FILTER_COLUMNS                                         \
 { ANON_GET_ITERATOR_FILTER_COLUMNS, "task_id", "name", "date", "status",      \
   "task", "severity", "false_positive", "log", "low", "medium", "high",       \
   NULL }

/**
 * @brief Report iterator columns.
 */
#define REPORT_ITERATOR_COLUMNS                                              \
 {                                                                           \
   { "id", NULL },                                                           \
   { "uuid", NULL },                                                         \
   { "iso_time (start_time)", "name" },                                      \
   { "''", NULL },                                                           \
   { "iso_time (start_time)", NULL },                                        \
   { "iso_time (end_time)", NULL },                                          \
   { "start_time", "created" },                                              \
   { "end_time", "modified" },                                               \
   { "''", NULL },                                                           \
   { "run_status_name (scan_run_status)", "status" },                        \
   { "(SELECT uuid FROM tasks WHERE tasks.id = task)", "task_id" },          \
   { "date", NULL },                                                         \
   { "(SELECT name FROM tasks WHERE tasks.id = task)", "task" },             \
   { "report_severity (id, opts.override, opts.min_qod)", "severity" },      \
   {                                                                         \
     "(CASE WHEN (SELECT target IS NULL FROM tasks WHERE tasks.id = task)"   \
     "  THEN 'Container'"                                                    \
     "  ELSE run_status_name (scan_run_status)"                              \
     "       || (SELECT CAST (temp / 100 AS text)"                           \
     "                  || CAST (temp / 10 AS text)"                         \
     "                  || CAST (temp % 10 as text)"                         \
     "           FROM (SELECT report_progress (id) AS temp) AS temp_sub)"    \
     "  END)",                                                               \
     "status"                                                                \
   },                                                                        \
   {                                                                         \
     " report_severity_count (id, opts.override, opts.min_qod,"              \
     "                        'False Positive')",                            \
     "false_positive" },                                                     \
   { "report_severity_count (id, opts.override, opts.min_qod, 'Log')",       \
     "log" },                                                                \
   { "report_severity_count (id, opts.override, opts.min_qod, 'Low')",       \
     "low" },                                                                \
   { "report_severity_count (id, opts.override, opts.min_qod, 'Medium')",    \
     "medium" },                                                             \
   { "report_severity_count (id, opts.override, opts.min_qod, 'High')",      \
     "high" },                                                               \
   {                                                                         \
     "(SELECT name FROM users WHERE users.id = reports.owner)",              \
     "_owner"                                                                \
   },                                                                        \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Generate the extra_tables string for a report iterator.
 *
 * @param[in]  override  Whether to apply overrides.
 * @param[in]  min_qod   Minimum QoD of results to count.
 *
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
report_iterator_opts_table (int override, int min_qod)
{
  return g_strdup_printf (", (SELECT"
                          "   %d AS override,"
                          "   %d AS min_qod)"
                          "  AS opts",
                          override,
                          min_qod);
}

/**
 * @brief Count number of reports.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of reports in filtered set.
 */
int
report_count (const get_data_t *get)
{
  static const char *filter_columns[] = REPORT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = REPORT_ITERATOR_COLUMNS;
  gchar *extra_tables;
  int ret;

  extra_tables = report_iterator_opts_table (0, MIN_QOD_DEFAULT);

  ret = count ("report", get, columns, NULL,
                filter_columns, 0,
                extra_tables,
                get->trash
                 ? " AND (SELECT hidden FROM tasks"
                   "      WHERE tasks.id = task)"
                   "     = 2"
                 : " AND (SELECT hidden FROM tasks"
                   "      WHERE tasks.id = task)"
                   "     = 0",
                TRUE);

  g_free (extra_tables);
  return ret;
}

/**
 * @brief Initialise a report iterator, including observed reports.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find report, 2 failed to find filter,
 *         -1 error.
 */
int
init_report_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = REPORT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = REPORT_ITERATOR_COLUMNS;
  char *filter;
  gchar *value;
  int overrides, min_qod;
  gchar *extra_tables;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;
  value = filter_term_value (filter ? filter : get->filter, "apply_overrides");
  overrides = value && strcmp (value, "0");
  g_free (value);

  value = filter_term_value (filter ? filter : get->filter, "min_qod");
  if (value == NULL || sscanf (value, "%d", &min_qod) != 1)
    min_qod = MIN_QOD_DEFAULT;
  g_free (value);
  free (filter);

  extra_tables = report_iterator_opts_table (overrides, min_qod);

  ret = init_get_iterator (iterator,
                            "report",
                            get,
                            /* Columns. */
                            columns,
                            /* Columns for trashcan. */
                            NULL,
                            filter_columns,
                            0,
                            extra_tables,
                            get->trash
                             ? " AND (SELECT hidden FROM tasks"
                               "      WHERE tasks.id = task)"
                               "     = 2"
                             : " AND (SELECT hidden FROM tasks"
                               "      WHERE tasks.id = task)"
                               "     = 0",
                            TRUE);
  g_free (extra_tables);
  return ret;
}

/**
 * @brief Initialise a report iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task whose reports the iterator loops over.
 */
void
init_report_iterator_task (iterator_t* iterator, task_t task)
{
  assert (task);
  init_iterator (iterator,
                 "SELECT id, uuid FROM reports WHERE task = %llu;",
                 task);
}

#if 0
/**
 * @brief Get the NAME from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The NAME of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
#endif


/**
 * @brief Get the UUID from a report iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_iterator_uuid, 1);

/**
 * @brief Read the next report from an iterator.
 *
 * @param[in]   iterator  Task iterator.
 * @param[out]  report    Report.
 *
 * @return TRUE if there was a next task, else FALSE.
 */
gboolean
next_report (iterator_t* iterator, report_t* report)
{
  if (next (iterator))
    {
      *report = iterator_int64 (iterator, 0);
      return TRUE;
    }
  return FALSE;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to levels.
 *
 * @param[in]  levels  String describing threat levels (message types)
 *                     to include in report (for example, "hmlgd" for
 *                     High, Medium, Low, loG and Debug).  All levels if
 *                     NULL.
 * @param[in]  new_severity_sql  SQL for new severity.
 *
 * @return WHERE clause for levels if one is required, else NULL.
 */
static GString *
where_levels (const char* levels, const char *new_severity_sql)
{
  int count;
  GString *levels_sql;

  /* Generate SQL for constraints on message type, according to levels. */

  if (levels == NULL || strlen (levels) == 0)
    {
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              " AND %s != " G_STRINGIFY (SEVERITY_ERROR),
                              new_severity_sql);
      return levels_sql;
    }

  levels_sql = NULL;
  count = 0;

  /* High. */
  if (strchr (levels, 'h'))
    {
      count = 1;
      // FIX handles dynamic "severity" in caller?
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              " AND (severity_in_level (%s, 'high')",
                              new_severity_sql);
    }

  /* Medium. */
  if (strchr (levels, 'm'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (severity_in_level (%s, 'medium')",
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR severity_in_level (%s, 'medium')",
                                new_severity_sql);
      count++;
    }

  /* Low. */
  if (strchr (levels, 'l'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (severity_in_level (%s, 'low')",
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR severity_in_level (%s, 'low')",
                                new_severity_sql);
      count++;
    }

  /* loG. */
  if (strchr (levels, 'g'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND ((%s"
                                  "       = " G_STRINGIFY (SEVERITY_LOG) ")",
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR (%s"
                                "     = " G_STRINGIFY (SEVERITY_LOG) ")",
                                new_severity_sql);
      count++;
    }

  /* Debug. */
  if (strchr (levels, 'd'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND ((%s"
                                  "       = " G_STRINGIFY
                                               (SEVERITY_DEBUG) ")",
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR (%s"
                                "     = " G_STRINGIFY (SEVERITY_DEBUG) ")",
                                new_severity_sql);
      count++;
    }

  /* False Positive. */
  if (strchr (levels, 'f'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND ((%s"
                                  "       = " G_STRINGIFY
                                               (SEVERITY_FP) ")",
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR (%s"
                                "     = " G_STRINGIFY (SEVERITY_FP) ")",
                                new_severity_sql);
      count++;
    }
  else if (count)
    levels_sql = g_string_append (levels_sql, ")");

  if (count == 6)
    {
      /* All levels. */
      g_string_free (levels_sql, TRUE);
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              "AND %s != " G_STRINGIFY (SEVERITY_ERROR),
                              new_severity_sql);
    }

  return levels_sql;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to levels.
 *
 * @param[in]  levels  String describing threat levels (message types)
 *                     to include in report (for example, "hmlgd" for
 *                     High, Medium, Low, loG and Debug).  All levels if
 *                     NULL.
 * @param[in]  new_severity_sql  SQL for new severity.
 * @param[in]  auto_type_sql     SQL for auto type.
 *
 * @return WHERE clause for levels if one is required, else NULL.
 */
static GString *
where_levels_auto (const char *levels, const char *new_severity_sql,
                   const char *auto_type_sql)
{
  int count;
  GString *levels_sql;

  /* Generate SQL for constraints on message type, according to levels. */

  if (levels == NULL || strlen (levels) == 0)
    {
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              " AND %s != " G_STRINGIFY (SEVERITY_ERROR),
                              new_severity_sql);
      return levels_sql;
    }

  levels_sql = NULL;
  count = 0;

  /* High. */
  if (strchr (levels, 'h'))
    {
      count = 1;
      // FIX handles dynamic "severity" in caller?
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              " AND (((%s IS NULL) AND (severity_in_level (%s, 'high')",
                              auto_type_sql,
                              new_severity_sql);
    }

  /* Medium. */
  if (strchr (levels, 'm'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (((%s IS NULL) AND (severity_in_level (%s, 'medium')",
                                  auto_type_sql,
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR severity_in_level (%s, 'medium')",
                                new_severity_sql);
      count++;
    }

  /* Low. */
  if (strchr (levels, 'l'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (((%s IS NULL) AND (severity_in_level (%s, 'low')",
                                  auto_type_sql,
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR severity_in_level (%s, 'low')",
                                new_severity_sql);
      count++;
    }

  /* loG. */
  if (strchr (levels, 'g'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (((%s IS NULL) AND (severity_in_level (%s, 'log')",
                                  auto_type_sql,
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR severity_in_level (%s, 'log')",
                                new_severity_sql);
      count++;
    }

  /* Debug. */
  if (strchr (levels, 'd'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (((%s IS NULL)"
                                  "       AND ((%s"
                                  "             = " G_STRINGIFY
                                                     (SEVERITY_DEBUG) ")",
                                  auto_type_sql,
                                  new_severity_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR (%s"
                                "     = " G_STRINGIFY
                                           (SEVERITY_DEBUG) ")",
                                new_severity_sql);
      count++;
    }

  /* False Positive. */
  if (strchr (levels, 'f'))
    {
      if (count == 0)
        {
          levels_sql = g_string_new ("");
          g_string_append_printf (levels_sql,
                                  " AND (((%s IS NULL)"
                                  "       AND %s"
                                  "           = " G_STRINGIFY
                                                   (SEVERITY_FP) ")"
                                  "      OR %s = 1)",
                                  auto_type_sql,
                                  new_severity_sql,
                                  auto_type_sql);
        }
      else
        g_string_append_printf (levels_sql,
                                " OR %s"
                                "    = " G_STRINGIFY
                                          (SEVERITY_FP) "))"
                                " OR %s = 1)",
                                new_severity_sql,
                                auto_type_sql);
      count++;
    }
  else if (count)
    levels_sql = g_string_append (levels_sql, ")))");

  if (count == 6)
    {
      /* All levels. */
      g_string_free (levels_sql, TRUE);
      levels_sql = g_string_new ("");
      g_string_append_printf (levels_sql,
                              " AND %s != " G_STRINGIFY (SEVERITY_ERROR),
                              new_severity_sql);
    }

  return levels_sql;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to levels.
 *
 * @param[in]  levels  String describing threat levels (message types)
 *                     to include in report (for example, "hmlgd" for
 *                     High, Medium, Low, loG and Debug).  All levels if
 *                     NULL.
 *
 * @return WHERE clause for levels if one is required, else NULL.
 */
static GString *
where_levels_type (const char* levels)
{
  int count;
  GString *levels_sql;

  /* Generate SQL for constraints on message type, according to levels. */

  if (levels == NULL || strlen (levels) == 0)
    return g_string_new (" AND severity != " G_STRINGIFY (SEVERITY_ERROR));

  levels_sql = NULL;
  count = 0;

  /* High. */
  if (strchr (levels, 'h'))
    {
      count = 1;
      // FIX handles dynamic "severity" in caller?
      levels_sql = g_string_new (" AND (severity_in_level (severity, 'high')");
    }

  /* Medium. */
  if (strchr (levels, 'm'))
    {
      if (count == 0)
        levels_sql = g_string_new (" AND (severity_in_level (severity, 'medium')");
      else
        levels_sql = g_string_append (levels_sql,
                                      " OR severity_in_level (severity, 'medium')");
      count++;
    }

  /* Low. */
  if (strchr (levels, 'l'))
    {
      if (count == 0)
        levels_sql = g_string_new (" AND (severity_in_level (severity, 'low')");
      else
        levels_sql = g_string_append (levels_sql,
                                      " OR severity_in_level (severity, 'low')");
      count++;
    }

  /* loG. */
  if (strchr (levels, 'g'))
    {
      if (count == 0)
        levels_sql = g_string_new (" AND (severity_in_level (severity, 'log')");
      else
        levels_sql = g_string_append (levels_sql,
                                      " OR severity_in_level (severity, 'log')");
    }

  /* Debug. */
  if (strchr (levels, 'd'))
    {
      if (count == 0)
        levels_sql = g_string_new (" AND ((severity"
                                   "       = " G_STRINGIFY
                                                (SEVERITY_DEBUG) ")");
      else
        levels_sql = g_string_append (levels_sql,
                                      " OR (severity"
                                      "     = " G_STRINGIFY
                                                 (SEVERITY_DEBUG) ")");
      count++;
    }

  /* False Positive. */
  if (strchr (levels, 'f'))
    {
      if (count == 0)
        levels_sql = g_string_new (" AND ((severity"
                                   "       = " G_STRINGIFY
                                                (SEVERITY_FP) "))");
      else
        levels_sql = g_string_append (levels_sql,
                                      " OR (severity"
                                      "     = " G_STRINGIFY
                                                 (SEVERITY_FP) "))");
      count++;
    }
  else if (count)
    levels_sql = g_string_append (levels_sql, ")");

  if (count == 6)
    {
      /* All levels. */
      g_string_free (levels_sql, TRUE);
      levels_sql = g_string_new (" AND severity != "
                                 G_STRINGIFY (SEVERITY_ERROR));
    }

  return levels_sql;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a minimum CVSS base.
 *
 * @param[in]  min_cvss_base  Minimum value for CVSS.
 *
 * @return WHERE clause if one is required, else NULL.
 */
static GString *
where_cvss_base (const char* min_cvss_base)
{
  if (min_cvss_base)
    {
      GString *cvss_sql;
      gchar *quoted_min_cvss_base;

      if (strlen (min_cvss_base) == 0)
        return NULL;

      quoted_min_cvss_base = sql_quote (min_cvss_base);
      cvss_sql = g_string_new ("");
      g_string_append_printf (cvss_sql,
                              " AND CAST ((SELECT cvss_base FROM nvts WHERE nvts.oid = results.nvt) AS REAL)"
                              " >= CAST ('%s' AS REAL)",
                              quoted_min_cvss_base);
      g_free (quoted_min_cvss_base);

      return cvss_sql;
    }
  return NULL;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a minimum QoD.
 *
 * @param[in]  min_cvss_base  Minimum value for QoD.
 *
 * @return WHERE clause if one is required, else NULL.
 */
static gchar*
where_qod (const char* min_qod)
{
  gchar *qod_sql;
  if (min_qod && strlen (min_qod))
    {
      gchar *quoted_qod = sql_quote (min_qod);
      qod_sql = g_strdup_printf (" AND (qod >= CAST ('%s' AS INTEGER))",
                                 quoted_qod);
      g_free (quoted_qod);
    }
  else
    qod_sql = g_strdup_printf (" AND (qod >= CAST ('%d' AS INTEGER))",
                               MIN_QOD_DEFAULT);

  return qod_sql;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a search phrase.
 *
 * @param[in]  search_phrase  Phrase that results must include.  All results if
 *                            NULL or "".
 * @param[in]  exact          Whether match must be exact.
 *
 * @return WHERE clause for search phrase if one is required, else NULL.
 */
static GString *
where_search_phrase (const char* search_phrase, int exact)
{
  if (search_phrase)
    {
      GString *phrase_sql;
      gchar *quoted_search_phrase;

      if (strlen (search_phrase) == 0)
        return NULL;

      /* Changing these SQL terms may require a change in report_counts_match. */
      quoted_search_phrase = sql_quote (search_phrase);
      phrase_sql = g_string_new ("");
      if (exact)
        g_string_append_printf (phrase_sql,
                                " AND (port = '%s'"
                                " OR host = '%s'"
                                " OR nvt = '%s'"
                                " OR nvt IN (SELECT oid FROM nvts"
                                "            WHERE name = '%s'"
                                "             OR tag LIKE '%%%%=%s|%%%%'"
                                "             OR tag LIKE '%%%%=%s'"
                                "             OR cve LIKE '%%%%%s%%%%')"
                                " OR description = '%s')",
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase);
      else
        g_string_append_printf (phrase_sql,
                                " AND (port LIKE '%%%%%s%%%%'"
                                " OR host LIKE '%%%%%s%%%%'"
                                " OR nvt LIKE '%%%%%s%%%%'"
                                " OR nvt IN (SELECT oid FROM nvts"
                                "            WHERE name LIKE '%%%%%s%%%%'"
                                "             OR tag LIKE '%%%%=%%%%%s%%%%'"
                                "             OR cve LIKE '%%%%%s%%%%')"
                                " OR description LIKE '%%%%%s%%%%')",
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase,
                                quoted_search_phrase);
      g_free (quoted_search_phrase);

      return phrase_sql;
    }
  return NULL;
}

/**
 * @brief SQL for retrieving CVSS base.
 */
#define CVSS_BASE_SQL                                            \
  "(SELECT cvss_base FROM nvts WHERE nvts.oid = results.nvt)"

/**
 * @brief Filter columns for result iterator.
 */
#define RESULT_ITERATOR_FILTER_COLUMNS                                        \
  { GET_ITERATOR_FILTER_COLUMNS, "host", "location", "nvt",                   \
    "type", "original_type", "auto_type",                                     \
    "description", "task", "report", "cvss_base", "nvt_version",              \
    "severity", "original_severity", "vulnerability", "date", "report_id",    \
    "solution_type", "qod", "qod_type", "task_id", "cve", NULL }

/**
 * @brief Result iterator columns.
 */
#define RESULT_ITERATOR_COLUMNS                                               \
  {                                                                           \
    { "id", NULL },                                                           \
    { "uuid", NULL },                                                         \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)", "name" },              \
    { "''", "comment" },                                                      \
    { " iso_time ( date )", "creation_time" },                                \
    { " iso_time ( date )", "modification_time" },                            \
    { "date", "created" },                                                    \
    { "date", "modified" },                                                   \
    { "(SELECT name FROM users WHERE users.id = results.owner)",              \
      "_owner" },                                                             \
    { "owner", NULL },                                                        \
    { "host", NULL },                                                         \
    { "port", "location" },                                                   \
    { "nvt", NULL },                                                          \
    { "severity_to_type (severity)", "original_type" },                       \
    { "severity_to_type ((SELECT new_severity FROM result_new_severities"     \
      "                  WHERE result_new_severities.result = results.id"     \
      "                  AND result_new_severities.user"                      \
      "                      = (SELECT users.id"                              \
      "                         FROM current_credentials, users"              \
      "                         WHERE current_credentials.uuid = users.uuid)" \
      "                  AND result_new_severities.override = opts.override"  \
      "                  AND result_new_severities.dynamic = opts.dynamic"    \
      "                  LIMIT 1))", "type" },                                \
    { "(SELECT autofp FROM results_autofp"                                    \
      " WHERE (result = results.id) AND (autofp_selection = opts.autofp))",   \
      "auto_type" },                                                          \
    { "description", NULL },                                                  \
    { "task", NULL },                                                         \
    { "report", "report_rowid" },                                             \
    { "(SELECT cvss_base FROM nvts WHERE nvts.oid =  nvt)", "cvss_base" },    \
    { "nvt_version", NULL },                                                  \
    { "severity", "original_severity" },                                      \
    { "(SELECT new_severity FROM result_new_severities"                       \
      " WHERE result_new_severities.result = results.id"                      \
      " AND result_new_severities.user"                                       \
      "     = (SELECT users.id"                                               \
      "        FROM current_credentials, users"                               \
      "        WHERE current_credentials.uuid = users.uuid)"                  \
      " AND result_new_severities.override = opts.override"                   \
      " AND result_new_severities.dynamic = opts.dynamic"                     \
      " LIMIT 1)", "severity" },                                              \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "vulnerability" },                                                      \
    { "date" , NULL },                                                        \
    { "(SELECT uuid FROM reports WHERE id = report)", "report_id" },          \
    { "(SELECT solution_type FROM nvts WHERE nvts.oid = nvt)",                \
      "solution_type" },                                                      \
    { "qod", NULL },                                                          \
    { "qod_type", NULL },                                                     \
    { "qod_type", NULL },                                                     \
    { "(SELECT uuid FROM tasks WHERE id = task)", "task_id" },                \
    { "(SELECT cve FROM nvts WHERE oid = nvt)", "cve" },                      \
    { NULL, NULL }                                                            \
  }

/**
 * @brief Generate the extra_tables string for a result iterator.
 *
 * @param[in]  autofp    Whether to apply auto FP filter.
 * @param[in]  override  Whether to apply overrides.
 * @param[in]  dynamic   Whether to use dynamic severity scores.
 *
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
result_iterator_opts_table (int autofp, int override, int dynamic)
{
  return g_strdup_printf (", (SELECT"
                          "   %d AS autofp,"
                          "   %d AS override,"
                          "   %d AS dynamic) AS opts",
                          autofp,
                          override,
                          dynamic);
}

/**
 * @brief Initialise a result iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find result, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_result_get_iterator (iterator_t* iterator, const get_data_t *get,
                          int autofp, int override, int min_qod,
                          int dynamic_severity)
{
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = RESULT_ITERATOR_COLUMNS;
  int ret;
  gchar *min_qod_str, *min_qod_clause;
  gchar *extra_tables, *extra_where;

  extra_tables
    = result_iterator_opts_table (autofp, override, dynamic_severity);

  min_qod_str = g_strdup_printf ("%d", min_qod);
  min_qod_clause = where_qod (min_qod_str);
  g_free (min_qod_str);

  extra_where = g_strdup_printf("%s%s",
                                min_qod_clause,
                                get->trash
                                  ? "AND ((SELECT (hidden = 2) FROM tasks"
                                    "      WHERE tasks.id = task))"
                                  : "AND ((SELECT (hidden = 0) FROM tasks"
                                    "      WHERE tasks.id = task))");

  ret = init_get_iterator (iterator,
                            "result",
                            get,
                            columns,
                            columns,
                            filter_columns,
                            0,
                            extra_tables,
                            extra_where,
                            TRUE);
  g_free (extra_tables);
  g_free (extra_where);
  return ret;
}

/**
 * @brief Count the number of results.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of results in filtered set.
 */
int
result_count (const get_data_t *get,
              int autofp, int override, int min_qod,
              int dynamic_severity)
{
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = RESULT_ITERATOR_COLUMNS;
  int ret;
  gchar *min_qod_str, *min_qod_clause;
  gchar *extra_tables, *extra_where;

  extra_tables
    = result_iterator_opts_table (autofp, override, dynamic_severity);

  min_qod_str = g_strdup_printf ("%d", min_qod);
  min_qod_clause = where_qod (min_qod_str);
  g_free (min_qod_str);

  extra_where = g_strdup_printf("%s%s",
                                min_qod_clause,
                                get->trash
                                  ? "AND ((SELECT (hidden = 2) FROM tasks"
                                    "      WHERE tasks.id = task))"
                                  : "AND ((SELECT (hidden = 0) FROM tasks"
                                    "      WHERE tasks.id = task))");


  ret = count ("result", get,
                columns,
                columns,
                filter_columns, 0,
                extra_tables,
                extra_where,
                TRUE);
  g_free (extra_tables);
  g_free (extra_where);
  return ret;
}

/**
 * @brief Initialise a result iterator.
 *
 * The results are ordered by host, then port and type (severity) according
 * to sort_field.
 *
 * @param[in]  iterator       Iterator.
 * @param[in]  report         Report whose results the iterator loops over,
 *                            or 0 to use result.
 * @param[in]  result         Single result to iterate over.  0 for all.
 *                            Overridden by report.
 * @param[in]  first_result   The result to start from.  The results are 0
 *                            indexed.
 * @param[in]  max_results    The maximum number of results returned.
 * @param[in]  ascending      Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in report (for example, "hmlgdf" for
 *                            High, Medium, Low, loG, Debug and False positive).
 *                            All levels if NULL.
 * @param[in]  autofp         Whether to apply auto FP filter.
 * @param[in]  search_phrase  Phrase that results must include.  All results if
 *                            NULL or "".
 * @param[in]  search_phrase_exact  Whether search phrase is exact.
 * @param[in]  min_cvss_base  Minimum value for CVSS.  All results if NULL.
 * @param[in]  min_qod        Minimum value for QoD.  All results if NULL.
 * @param[in]  override       Whether to override the threat and CVSS.
 */
void
init_result_iterator (iterator_t* iterator, report_t report, result_t result,
                      int first_result, int max_results,
                      int ascending, const char* sort_field, const char* levels,
                      int autofp, const char* search_phrase,
                      int search_phrase_exact, const char* min_cvss_base,
                      const char* min_qod, int override)
{
  int dynamic_severity = setting_dynamic_severity_int (); // TODO: Add parameter

  GString *levels_sql, *phrase_sql, *cvss_sql;
  gchar *severity_sql, *qod_sql, *order_sql, *sql;

  assert ((report && result) == 0);

  /* TODO: The generated SQL is now out of hand. */

  if (dynamic_severity)
    severity_sql = g_strdup("CASE WHEN results.severity"
                            "          > " G_STRINGIFY (SEVERITY_LOG)
                            " THEN (SELECT CAST (" CVSS_BASE_SQL " AS REAL)"
                            "       FROM nvts"
                            "       WHERE nvts.oid = results.nvt)"
                            " ELSE results.severity END");
  else
    severity_sql = g_strdup("results.severity");

  /* Allocate the query. */

  if (report)
    {
      gchar *new_severity_sql, *auto_type_sql;

      if (sort_field == NULL) sort_field = "type";
      if (levels == NULL) levels = "hmlgdf";
      if (strcmp (sort_field, "location") == 0) sort_field = "port";

      phrase_sql = where_search_phrase (search_phrase, search_phrase_exact);
      cvss_sql = where_cvss_base (min_cvss_base);
      qod_sql = where_qod (min_qod);

      new_severity_sql
        = g_strdup_printf("(SELECT new_severity FROM result_new_severities"
                          " WHERE result_new_severities.result = results.id"
                          " AND result_new_severities.user"
                          "     = (SELECT id FROM users WHERE uuid = '%s')"
                          " AND override = %d"
                          " AND dynamic = %d"
                          " LIMIT 1)",
                          current_credentials.uuid,
                          override,
                          dynamic_severity);

      switch (autofp)
        {
          case 1:
            auto_type_sql = g_strdup_printf
              ("(SELECT autofp FROM results_autofp"
               " WHERE result = results.id"
               " AND autofp_selection = 1)");
            break;

          case 2:
            auto_type_sql = g_strdup_printf
              ("(SELECT autofp FROM results_autofp"
               " WHERE result = results.id"
               " AND autofp_selection = 2)");
             break;

           default:
             auto_type_sql = g_strdup ("NULL");
             break;
         }

      if (strcmp (sort_field, "ROWID") == 0)
        order_sql = g_strdup_printf (" ORDER BY results.id %s",
                                     ascending ? "ASC" : "DESC");
      else if (strcmp (sort_field, "port") == 0)
        order_sql = g_strdup_printf (" ORDER BY"
                                     " order_port (port) %s,"
                                     " inet (host),"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " DESC,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " nvt,"
                                     " description",
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);
      else if (strcmp (sort_field, "host") == 0)
        order_sql = g_strdup_printf (" ORDER BY"
                                     " inet (host) %s,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " DESC,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " nvt,"
                                     " description",
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);
      else if (strcmp (sort_field, "vulnerability") == 0)
        order_sql = g_strdup_printf (" ORDER BY"
                                     " vulnerability %s,"
                                     " order_port (port),"
                                     " inet (host),"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " DESC,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " nvt,"
                                     " description",
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);
      else if (strcmp (sort_field, "solution_type") == 0)
        order_sql = g_strdup_printf (" ORDER BY"
                                     " solution_type %s,"
                                     " vulnerability,"
                                     " order_port (port),"
                                     " inet (host),"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " DESC,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " nvt,"
                                     " description",
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);
      else if (strcmp (sort_field, "qod") == 0)
        order_sql = g_strdup_printf (" ORDER BY"
                                     " qod %s,"
                                     " vulnerability,"
                                     " order_port (port),"
                                     " inet (host),"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " DESC,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " nvt,"
                                     " description",
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);
      else
        order_sql = g_strdup_printf (" ORDER BY "
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN CAST (%s AS REAL)"
                                     "  ELSE " G_STRINGIFY (SEVERITY_FP)
                                     "  END)"
                                     " %s,"
                                     " (CASE WHEN %s IS NULL"
                                     "  THEN %s"
                                     "  ELSE %s"
                                     "  END)"
                                     " DESC,"
                                     " order_port (port),"
                                     " inet (host),"
                                     " (CASE"
                                     "  WHEN CAST (" CVSS_BASE_SQL " AS REAL)"
                                     "       >= 0.0"
                                     "  THEN CAST (" CVSS_BASE_SQL " AS REAL)"
                                     "  ELSE 0.0"
                                     "  END) DESC,"
                                     " nvt,"
                                     " description",
                                     auto_type_sql,
                                     new_severity_sql,
                                     ascending ? "ASC" : "DESC",
                                     auto_type_sql,
                                     new_severity_sql,
                                     auto_type_sql);

      levels_sql = where_levels_auto (levels, new_severity_sql, auto_type_sql);

      sql = g_strdup_printf ("SELECT results.id, uuid,"
                             " (SELECT name FROM nvts"
                             "  WHERE nvts.oid = results.nvt)"
                             "  AS name,"
                             " '' as comment,"
                             " iso_time (date),"
                             " iso_time (date),"
                             " date AS created,"
                             " date AS modified,"
                             " (SELECT name FROM users WHERE users.id"
                             "  = results.owner) AS _owner,"
                             " owner, host, port, nvt,"
                             " severity_to_type (%s) AS type,"
                             " severity_to_type (%s) AS new_type,"
                             " %s AS auto_type,"
                             " results.description,"
                             " results.task,"
                             " results.report,"
                             " " CVSS_BASE_SQL " AS cvss_base,"
                             " nvt_version,"
                             " %s AS severity,"
                             " %s AS new_severity,"
                             " (SELECT name FROM nvts"
                             "  WHERE nvts.oid = results.nvt)"
                             "  AS vulnerability,"
                             " date,"
                             " (SELECT uuid FROM reports"
                             "  WHERE id = results.report) AS report_id,"
                             " (SELECT solution_type FROM nvts"
                             "  WHERE oid = nvt) AS solution_type,"
                             " qod,"
                             " qod_type"
                             " FROM results"
                             " WHERE results.report = %llu"
                             "%s"
                             "%s"
                             "%s"
                             "%s"
                             "%s"
                             " LIMIT %s OFFSET %i;",
                             severity_sql,
                             new_severity_sql,
                             auto_type_sql,
                             severity_sql,
                             new_severity_sql,
                             report,
                             levels_sql ? levels_sql->str : "",
                             phrase_sql ? phrase_sql->str : "",
                             cvss_sql ? cvss_sql->str : "",
                             qod_sql ? qod_sql : "",
                             order_sql,
                             sql_select_limit (max_results),
                             first_result > 0 ? first_result : 0);

      if (levels_sql) g_string_free (levels_sql, TRUE);
      if (phrase_sql) g_string_free (phrase_sql, TRUE);
      if (cvss_sql) g_string_free (cvss_sql, TRUE);
      g_free (qod_sql);
      g_free (auto_type_sql);
      g_free (new_severity_sql);
      g_free (order_sql);
    }
  else if (result)
    {
      gchar *new_severity_sql, *auto_type_sql;

      new_severity_sql
        = g_strdup_printf("(SELECT new_severity FROM result_new_severities"
                          " WHERE result_new_severities.result = results.id"
                          " AND result_new_severities.user"
                          "     = (SELECT id FROM users WHERE uuid = '%s')"
                          " AND override = %d"
                          " AND dynamic = %d"
                          " LIMIT 1)",
                          current_credentials.uuid,
                          override,
                          dynamic_severity);

      switch (autofp)
        {
          case 1:
            auto_type_sql = g_strdup_printf
              ("(SELECT autofp FROM results_autofp"
               " WHERE result = results.id"
               " AND autofp_selection = 1)");
            break;

          case 2:
            auto_type_sql = g_strdup_printf
              ("(SELECT autofp FROM results_autofp"
               " WHERE result = results.id"
               " AND autofp_selection = 2)");
             break;

           default:
             auto_type_sql = g_strdup ("NULL");
             break;
         }

      sql = g_strdup_printf ("SELECT results.id, uuid,"
                             " (SELECT name FROM nvts"
                             "  WHERE nvts.oid = results.nvt)"
                             "  AS name,"
                             " '' as comment,"
                             " iso_time (date),"
                             " iso_time (date),"
                             " date AS created,"
                             " date AS modified,"
                             " (SELECT name FROM users WHERE users.id"
                             "  = results.owner) AS _owner,"
                             " owner, host, port, nvt,"
                             " severity_to_type (%s), severity_to_type (%s),"
                             " %s, description,"
                             " results.task,"
                             " results.report,"
                             " NULL,"
                             " results.nvt_version,"
                             " %s,"
                             " %s,"
                             " (SELECT name FROM nvts"
                             "  WHERE nvts.oid = results.nvt)"
                             "  AS vulnerability,"
                             " date,"
                             " (SELECT uuid FROM reports"
                             "  WHERE id = results.report) AS report_id,"
                             " (SELECT solution_type FROM nvts"
                             "  WHERE oid = nvt) AS solution_type,"
                             " qod,"
                             " qod_type"
                             " FROM results"
                             " WHERE id = %llu;",
                             severity_sql,
                             new_severity_sql,
                             auto_type_sql,
                             severity_sql,
                             new_severity_sql,
                             result);

      g_free (auto_type_sql);
      g_free (new_severity_sql);
    }
  else
    sql = g_strdup_printf ("SELECT results.id, uuid,"
                           " (SELECT name FROM nvts"
                           "  WHERE nvts.oid = results.nvt)"
                           "  AS name,"
                           " '' as comment,"
                           " iso_time (date),"
                           " iso_time (date),"
                           " date AS created,"
                           " date AS modified,"
                           " (SELECT name FROM users WHERE users.id"
                           "  = results.owner) AS _owner,"
                           " results.owner, host, port, nvt,"
                           " severity_to_type (%s), severity_to_type (%s),"
                           " severity_to_type (%s), description,"
                           " results.task, results.report, NULL,"
                           " nvt_version, %s, %s,"
                           " (SELECT name FROM nvts"
                           "  WHERE nvts.oid = results.nvt)"
                           "  AS vulnerability,"
                           " date"
                           " FROM results, reports"
                           " WHERE results.report = reports.id"
                           " AND reports.owner ="
                           " (SELECT id FROM users WHERE uuid = '%s');",
                           severity_sql,
                           severity_sql,
                           severity_sql,
                           severity_sql,
                           severity_sql,
                           current_credentials.uuid);

  init_iterator (iterator, sql);
  g_free (severity_sql);
  g_free (sql);
}

/**
 * @brief Get the result from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The result.
 */
result_t
result_iterator_result (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (result_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the host from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The host of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_host, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the port from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The port of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_port, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the NVT OID from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The NVT OID of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the NVT name from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_name (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_name (nvti);
  return NULL;
}

/**
 * @brief Get the NVT family from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The family of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_family (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_family (nvti);
  return NULL;
}

/**
 * @brief Get the NVT CVSS base value from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS base of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_cvss_base (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_cvss_base (nvti);
  return NULL;
}

/**
 * @brief Get the NVT CVE from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVE of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_cve (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_cve (nvti);
  return NULL;
}

/**
 * @brief Get the NVT BID from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The BID of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_bid (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_bid (nvti);
  return NULL;
}

/**
 * @brief Get the NVT XREF from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The XREF of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_xref (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_xref (nvti);
  return NULL;
}

/**
 * @brief Get the NVT tags from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The tags of the NVT that produced the result, or NULL on error.
 */
const char*
result_iterator_nvt_tag (iterator_t *iterator)
{
  nvti_t *nvti;
  if (iterator->done) return NULL;
  nvti = nvtis_lookup (nvti_cache, result_iterator_nvt_oid (iterator));
  if (nvti)
    return nvti_tag (nvti);
  return NULL;
}

/**
 * @brief Get the original type from a result iterator.
 *
 * This is the column 'type'.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The original type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_original_type, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the type from a result iterator.
 *
 * This is the the autofp adjusted overridden type.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
const char*
result_iterator_type (iterator_t *iterator)
{
  if (iterator->done) return NULL;
  /* auto_type */
  if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5))
    return "False Positive";
  /* new_type */
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the descr from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The descr of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_descr, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the task from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The task associated with the result, or 0 on error.
 */
task_t
result_iterator_task (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7);
}

/**
 * @brief Get the report from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The report associated with the result, or 0 on error.
 */
report_t
result_iterator_report (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
}

/**
 * @brief Get the NVT version used during the scan from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The version of NVT used by the scan that produced the result.
 *         Caller must only use before calling cleanup_iterator.
 */
const char*
result_iterator_scan_nvt_version (iterator_t *iterator)
{
  const char* ret;

  if (iterator->done)
    return NULL;

  /* nvt_version */
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10);
  return ret ? ret : "";
}

/**
 * @brief Get the original severity from a result iterator.
 *
 * This is the original severity without overrides and autofp.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The original severity of the result.  Caller must only use before
 *         calling cleanup_iterator.
 */
const char*
result_iterator_original_severity (iterator_t *iterator)
{
  const char* ret;

  if (iterator->done)
    return NULL;

  /* severity */
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11);
  return ret ? ret : "";
}

/**
 * @brief Get the severity from a result iterator.
 *
 * This is the the autofp adjusted overridden severity.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
const char*
result_iterator_severity (iterator_t *iterator)
{
  const char* ret;

  if (iterator->done)
    return NULL;

  /* auto_type */
  if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5))
    return G_STRINGIFY (SEVERITY_FP);

  /* new_severity */
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 12);
  return ret ? ret : "";
}

/**
 * @brief Get the severity from a result iterator as double.
 *
 * This is the the autofp adjusted overridden severity.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
double
result_iterator_severity_double (iterator_t *iterator)
{
  if (iterator->done)
    return 0.0;

  /* auto_type */
  if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5))
    return SEVERITY_FP;

  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 12);
}

/**
 * @brief Get the original severity/threat level from a result iterator.
 *
 * This is the original level without overrides and autofp.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The original threat level of the result.  Caller must only use before
 *         calling cleanup_iterator.
 */
const char*
result_iterator_original_level (iterator_t *iterator)
{
  double severity;
  const char* ret;

  if (iterator->done)
    return NULL;

  if (iterator_null (iterator, GET_ITERATOR_COLUMN_COUNT + 11))
    return NULL;

  /* severity */
  severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 11);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}

/**
 * @brief Get the severity/threat level from a result iterator.
 *
 * This is the the autofp adjusted overridden level.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The threat level of the result.  Caller must only use before
 *         calling cleanup_iterator.
 */
const char*
result_iterator_level (iterator_t *iterator)
{
  double severity;
  const char* ret;

  if (iterator->done)
    return NULL;

  /* auto_type */
  if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5))
    return "False Positive";

  /* new_severity */
  if (iterator_null (iterator, GET_ITERATOR_COLUMN_COUNT + 12))
    return NULL;

  severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 12);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}

/**
 * @brief Get the qod from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The qod of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_qod, GET_ITERATOR_COLUMN_COUNT + 17);

/**
 * @brief Get the qod_type from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The qod type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_qod_type, GET_ITERATOR_COLUMN_COUNT + 18);

/**
 * @brief Initialise a host iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  report    Report whose hosts the iterator loops over.
 * @param[in]  host      Single host to iterate over.  All hosts if NULL.
 * @param[in]  report_host  Single report host to iterate over.  All if 0.
 */
void
init_host_iterator (iterator_t* iterator, report_t report, const char *host,
                    report_host_t report_host)
{
  if (report)
    {
      if (report_host)
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report)"
                       " FROM report_hosts WHERE id = %llu"
                       " AND report = %llu"
                       "%s%s%s"
                       " ORDER BY inet (host);",
                       report_host,
                       report,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
      else
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report)"
                       " FROM report_hosts WHERE report = %llu"
                       "%s%s%s"
                       " ORDER BY inet (host);",
                       report,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
    }
  else
    {
      if (report_host)
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report)"
                       " FROM report_hosts WHERE id = %llu"
                       "%s%s%s"
                       " ORDER BY inet (host);",
                       report_host,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
      else
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report)"
                       " FROM report_hosts"
                       "%s%s%s"
                       " ORDER BY inet (host);",
                       host ? " WHERE host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
    }
}


/**
 * @brief Get the report host from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Report host.
 */
static report_host_t
host_iterator_report_host (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (report_host_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the host from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The host of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_host, 1);

/**
 * @brief Get the start time from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The start time of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_start_time, 2);

/**
 * @brief Get the end time from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The end time of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_end_time, 3);

/**
 * @brief Get the current port from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Current port.
 */
int
host_iterator_current_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 4);
  return ret;
}

/**
 * @brief Get the max port from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Current port.
 */
int
host_iterator_max_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 5);
  return ret;
}

/**
 * @brief Get the report from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The report of the host.
 */
static report_t
host_iterator_report (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (report_host_t) iterator_int64 (iterator, 6);
}

/**
 * @brief Get the report UUID from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the report of the host.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (host_iterator_report_uuid, 7);

/**
 * @brief Initialise a report errors iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  report   The report.
 */
void
init_report_errors_iterator (iterator_t* iterator, report_t report)
{
  if (report)
    init_iterator (iterator,
                   "SELECT results.host, results.port, results.nvt,"
                   " results.description,"
                   " coalesce((SELECT name FROM nvts"
                   "           WHERE nvts.oid = results.nvt), ''),"
                   " coalesce((SELECT cvss_base FROM nvts"
                   "           WHERE nvts.oid = results.nvt), ''),"
                   " results.nvt_version, results.severity"
                   " FROM results"
                   " WHERE results.type = 'Error Message'"
                   "  AND results.report = %llu",
                   report);
}

/**
 * @brief Get the host from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The host of the report error message.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_host, 0);

/**
 * @brief Get the port from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The port of the report error message.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_port, 1);

/**
 * @brief Get the nvt oid from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The nvt of the report error message.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_nvt_oid, 2);

/**
 * @brief Get the description from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The description of the report error message.  Caller must use only
 * before calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_desc, 3);

/**
 * @brief Get the nvt name from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The nvt of the report error message.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_nvt_name, 4);

/**
 * @brief Get the nvt cvss base from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The nvt cvss base of the report error message.  Caller must use only
 *         before calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_nvt_cvss, 5);

/**
 * @brief Get the nvt cvss base from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The nvt version at scan time of the report error message.
 *         Caller must use only before calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_scan_nvt_version, 6);

/**
 * @brief Get the nvt cvss base from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity at scan time of the report error message.
 *         Caller must use only before calling cleanup_iterator.
 */
DEF_ACCESS (report_errors_iterator_severity, 7);

/**
 * @brief Initialise a report host details iterator.
 *
 * @param[in]  iterator     Iterator.
 * @param[in]  report_host  Report host whose details the iterator loops over.
 *                          All report_hosts if NULL.
 */
static void
init_report_host_details_iterator (iterator_t* iterator,
                                   report_host_t report_host)
{
  /* The 'detected_at' and 'detected_by' entries are filtered out of the final
   * reports as they are only used internally for product detection. */
  init_iterator (iterator,
                 "SELECT id, name, value, source_type, source_name,"
                 " source_description, NULL"
                 " FROM report_host_details WHERE report_host = %llu"
                 " AND NOT name IN ('detected_at', 'detected_by')"
                 " UNION"
                 " SELECT 0, 'Closed CVE', cve, 'openvasmd', oid,"
                 "  nvts.name, cvss_base"
                 " FROM nvts, report_host_details"
                 " WHERE cve != 'NOCVE'"
                 " AND family IN (" LSC_FAMILY_LIST ")"
                 " AND nvts.oid = report_host_details.source_name"
                 " AND report_host = %llu"
                 " AND report_host_details.name = 'EXIT_CODE'"
                 " AND report_host_details.value = 'EXIT_NOTVULN';",
                 report_host,
                 report_host);
}

/**
 * @brief Get the name from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the report host detail.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_name, 1);

/**
 * @brief Get the value from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the report host detail.  Caller must use only before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_value, 2);

/**
 * @brief Get the source type from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source type of the report host detail.  Caller must use only
 *         before calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_source_type, 3);

/**
 * @brief Get the source name from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source name of the report host detail.  Caller must use only
 *         before calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_source_name, 4);

/**
 * @brief Get the source description from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source description of the report host detail.  Caller must use
 *         only before calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_source_desc, 5);

/**
 * @brief Get the extra info from a report host details iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Extra info of the report host detail.  Caller must use
 *         only before calling cleanup_iterator.
 */
DEF_ACCESS (report_host_details_iterator_extra, 6);

/**
 * @brief Initialise an asset iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  first_result  The host to start from.  The hosts are 0
 *                           indexed.
 * @param[in]  max_results   The maximum number of hosts returned.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in hosts (for example, "hml" for
 *                            High, Medium and Low).  All levels if NULL.
 * @param[in]  search_phrase  Phrase that host IPs must include.  All
 *                            hosts if NULL or "".
 * @param[in]  apply_overrides  Whether to apply overrides.
 */
static void
init_asset_iterator (iterator_t* iterator, int first_result,
                     int max_results, const char *levels,
                     const char *search_phrase, int apply_overrides)
{
  assert (current_credentials.uuid);

  if (levels && strlen (levels))
    {
      GString *levels_sql;
      gchar *severity_sql, *new_severity_sql, *last_report_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup("CASE WHEN results.severity"
                                "          > " G_STRINGIFY (SEVERITY_LOG)
                                " THEN coalesce ((SELECT CAST (cvss_base"
                                "                              AS REAL)"
                                "                 FROM nvts"
                                "                 WHERE nvts.oid"
                                "                         = results.nvt),"
                                "                results.severity)"
                                " ELSE results.severity END");
      else
        severity_sql = g_strdup ("results.severity");

      if (apply_overrides)
        {
          gchar *ov, *owned_clause;

          owned_clause = where_owned_for_get ("override", NULL);

          ov = g_strdup_printf
                ("SELECT overrides.new_severity"
                 " FROM overrides"
                 " WHERE overrides.nvt = results.nvt"
                 " AND %s"
                 " AND ((overrides.end_time = 0)"
                 "      OR (overrides.end_time >= m_now ()))"
                 /** @todo Include tasks.hidden and task pref in_assets? */
                 " AND (overrides.task ="
                 "      (SELECT reports.task FROM reports"
                 "       WHERE reports.id = results.report)"
                 "      OR overrides.task = 0)"
                 " AND (overrides.result = results.id"
                 "      OR overrides.result = 0)"
                 " AND (overrides.hosts is NULL"
                 "      OR overrides.hosts = ''"
                 "      OR hosts_contains (overrides.hosts, results.host))"
                 " AND (overrides.port is NULL"
                 "      OR overrides.port = ''"
                 "      OR overrides.port = results.port)"
                 " AND severity_matches_ov (%s, overrides.severity)"
                 " ORDER BY overrides.result DESC, overrides.task DESC,"
                 " overrides.port DESC, overrides.severity ASC,"
                 " overrides.creation_time DESC"
                 " LIMIT 1",
                 owned_clause,
                 severity_sql);

          g_free (owned_clause);

          new_severity_sql = g_strdup_printf ("coalesce ((%s), %s)",
                                              ov, severity_sql);

          g_free (severity_sql);
          g_free (ov);
        }
      else
        new_severity_sql = g_strdup_printf ("%s",
                                            severity_sql);

      levels_sql = where_levels (levels, new_severity_sql);

      last_report_sql
       = g_strdup_printf (" (SELECT report FROM report_hosts"
                          "  WHERE report_hosts.host = distinct_host"
                          "  AND end_time IS NOT NULL"
                          "  AND report_hosts.report"
                          "      IN (SELECT reports.id FROM reports"
                          "          WHERE user_owns ('task', reports.task))"
                          "  AND (SELECT reports.scan_run_status = %u"
                          "       FROM reports"
                          "       WHERE reports.id = report)"
                          "  AND (SELECT hidden FROM tasks"
                          "       WHERE tasks.id"
                          "             = (SELECT task FROM reports"
                          "                WHERE reports.id = report))"
                          "      = 0"
                          "  AND (SELECT value FROM task_preferences"
                          "       WHERE task_preferences.task"
                          "             = (SELECT task FROM reports"
                          "                WHERE reports.id = report)"
                          "       AND task_preferences.name = 'in_assets')"
                          "      = 'yes'"
                          "  ORDER BY id DESC LIMIT 1)",
                          TASK_STATUS_DONE);

      if (search_phrase && strlen (search_phrase))
        {
          gchar *quoted_search_phrase;

          quoted_search_phrase = sql_quote (search_phrase);
          init_iterator
           (iterator,
            "SELECT"
            " distinct_host"
            " FROM (SELECT DISTINCT host AS distinct_host, inet (host)"
            "       FROM report_hosts"
            "       ORDER BY inet (host))"
            "      AS distinct_host_subquery"
            /* Search IP. */
            " WHERE (distinct_host LIKE '%%%s%%%'"
            /* Search hostname. */
            "        OR EXISTS"
            "        (SELECT * FROM report_host_details"
            "         WHERE report_host"
            "               = (SELECT id FROM report_hosts"
            "                  WHERE report = %s"
            "                  AND host = distinct_host)"
            "         AND (name = 'hostname'"
            "              OR name = 'best_os_txt'"
            "              OR name = 'best_os_cpe' OR name = 'App'"
            "              OR name = 'ports')"
            "         AND source_type = 'nvt'"
            "         AND value LIKE '%%%s%%'))"
            /* Filter levels. */
            " AND EXISTS (SELECT results.id"
            "             FROM results"
            "             WHERE results.report = %s"
            "             AND results.host = distinct_host"
            "             AND qod >= " G_STRINGIFY (MIN_QOD_DEFAULT)
            "             %s)"
            " LIMIT %s OFFSET %i;",
            quoted_search_phrase,
            last_report_sql,
            quoted_search_phrase,
            last_report_sql,
            levels_sql ? levels_sql->str : "",
            sql_select_limit (max_results),
            first_result);
          g_free (quoted_search_phrase);
        }
      else
        init_iterator
         (iterator,
          "SELECT"
          " distinct_host"
          " FROM (SELECT DISTINCT host AS distinct_host, inet (host)"
          "       FROM report_hosts"
          "       ORDER BY inet (host))"
          "      AS distinct_host_subquery"
          " WHERE EXISTS (SELECT results.id"
          "               FROM results"
          "               WHERE results.report = %s"
          "               AND results.host = distinct_host"
            "             AND qod >= " G_STRINGIFY (MIN_QOD_DEFAULT)
          "               %s)"
          " LIMIT %s OFFSET %i;",
          last_report_sql,
          levels_sql ? levels_sql->str : "",
          sql_select_limit (max_results),
          first_result);

      if (levels_sql)
        g_string_free (levels_sql, TRUE);
      g_free (new_severity_sql);
      g_free (last_report_sql);
    }
  else if (search_phrase && strlen (search_phrase))
    {
      gchar *quoted_search_phrase;

      quoted_search_phrase = sql_quote (search_phrase);
      init_iterator (iterator,
                     "SELECT host"
                     " FROM report_hosts"
                     " WHERE report_hosts.report"
                     "     IN (SELECT reports.id FROM reports"
                     "         WHERE user_owns ('task', reports.task))"
                     " AND (SELECT tasks.hidden FROM tasks, reports"
                     "      WHERE reports.task = tasks.id"
                     "      AND reports.id = report_hosts.report)"
                     "     = 0"
                     " AND (SELECT value FROM task_preferences, tasks,"
                     "                        reports"
                     "      WHERE reports.task = tasks.id"
                     "      AND reports.id = report_hosts.report"
                     "      AND task_preferences.task = tasks.id"
                     "      AND task_preferences.name = 'in_assets')"
                     "     = 'yes'"
                     " AND report_hosts.end_time IS NOT NULL"
                     " GROUP BY host"
                     " HAVING host LIKE '%%%s%%'"
                     " OR EXISTS"
                     " (SELECT * FROM report_host_details"
                     "  WHERE report_hosts.id = report_host"
                     "  AND (name = 'hostname' OR name = 'best_os_txt'"
                     "       OR name = 'best_os_cpe' OR name = 'App'"
                     "       OR name = 'ports')"
                     "  AND source_type = 'nvt'"
                     "  AND value LIKE '%%%s%%')"
                     " ORDER BY inet (host)"
                     " LIMIT %s OFFSET %i;",
                     quoted_search_phrase,
                     quoted_search_phrase,
                     sql_select_limit (max_results),
                     first_result);
      g_free (quoted_search_phrase);
    }
  else
    init_iterator (iterator,
                   "SELECT DISTINCT host, inet (host) FROM report_hosts"
                   " WHERE report_hosts.report"
                   "     IN (SELECT reports.id FROM reports"
                   "         WHERE user_owns ('task', reports.task))"
                   " AND (SELECT tasks.hidden FROM tasks, reports"
                   "      WHERE reports.task = tasks.id"
                   "      AND reports.id = report_hosts.report)"
                   "     = 0"
                   " AND (SELECT value FROM task_preferences, tasks,"
                   "                        reports"
                   "      WHERE reports.task = tasks.id"
                   "      AND reports.id = report_hosts.report"
                   "      AND task_preferences.task = tasks.id"
                   "      AND task_preferences.name = 'in_assets')"
                   "     = 'yes'"
                   " AND report_hosts.end_time IS NOT NULL"
                   " ORDER BY inet (host)"
                   " LIMIT %s OFFSET %i;",
                   sql_select_limit (max_results),
                   first_result);
}

/**
 * @brief Get the IP from a asset iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Host IP.
 */
DEF_ACCESS (asset_iterator_ip, 0);

/**
 * @brief Set the end time of a task.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New time.  Freed before return.  If NULL, clear end time.
 */
void
set_task_end_time (task_t task, char* time)
{
  if (time)
    {
      sql ("UPDATE tasks SET end_time = %i WHERE id = %llu;",
           parse_iso_time (time),
           task);
      free (time);
    }
  else
    sql ("UPDATE tasks SET end_time = NULL WHERE id = %llu;",
         task);
}

/**
 * @brief Get the start time of a scan.
 *
 * @param[in]  report  The report associated with the scan.
 *
 * @return Start time of scan, in a newly allocated string.
 */
char*
scan_start_time (report_t report)
{
  char *time = sql_string ("SELECT iso_time (start_time)"
                           " FROM reports WHERE id = %llu;",
                           report);
  return time ? time : g_strdup ("");
}

/**
 * @brief Get the start time of a scan, in seconds since the epoch.
 *
 * @param[in]  report  The report associated with the scan.
 *
 * @return Start time of scan, in seconds.
 */
int
scan_start_time_epoch (report_t report)
{
  return sql_int ("SELECT start_time FROM reports WHERE id = %llu;",
                  report);
}

/**
 * @brief Set the start time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  Start time.  In ISO format.
 */
void
set_scan_start_time (report_t report, const char* timestamp)
{
  sql ("UPDATE reports SET start_time = %i WHERE id = %llu;",
       parse_iso_time (timestamp),
       report);
}

/**
 * @brief Set the start time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  Start time. Epoch format.
 */
void
set_scan_start_time_epoch (report_t report, time_t timestamp)
{
  sql ("UPDATE reports SET start_time = %i WHERE ROWID = %llu;",
       timestamp, report);
}

/**
 * @brief Set the start time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  Start time.  In OTP format (ctime).
 */
void
set_scan_start_time_otp (report_t report, const char* timestamp)
{
  sql ("UPDATE reports SET start_time = %i WHERE id = %llu;",
       parse_otp_time (timestamp),
       report);
}

/**
 * @brief Get the end time of a scan.
 *
 * @param[in]  report  The report associated with the scan.
 *
 * @return End time of scan, in a newly allocated string.
 */
char*
scan_end_time (report_t report)
{
  char *time = sql_string ("SELECT iso_time (end_time)"
                           " FROM reports WHERE id = %llu;",
                           report);
  return time ? time : g_strdup ("");
}

/**
 * @brief Get the end time of a scan.
 *
 * @param[in]  uuid  The report associated with the scan.
 *
 * @return End time of scan, in a newly allocated string.
 */
char*
scan_end_time_uuid (const char *uuid)
{
  char *time, *quoted_uuid;
  quoted_uuid = sql_quote (uuid);
  time = sql_string ("SELECT iso_time (end_time)"
                     " FROM reports WHERE uuid = '%s';",
                     quoted_uuid);
  return time ? time : g_strdup ("");
}

/**
 * @brief Set the end time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  End time. Epoch format.
 */
void
set_scan_end_time_epoch (report_t report, time_t timestamp)
{
  if (timestamp)
    sql ("UPDATE reports SET end_time = %i WHERE ROWID = %llu;",
         timestamp, report);
}

/**
 * @brief Set the end time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  End time.  ISO format.  If NULL, clear end time.
 */
void
set_scan_end_time (report_t report, const char* timestamp)
{
  if (timestamp)
    sql ("UPDATE reports SET end_time = %i WHERE id = %llu;",
         parse_iso_time (timestamp), report);
  else
    sql ("UPDATE reports SET end_time = NULL WHERE id = %llu;",
         report);
}

/**
 * @brief Set the end time of a scan.
 *
 * @param[in]  report     The report associated with the scan.
 * @param[in]  timestamp  End time.  OTP format (ctime).  If NULL, clear end
 *                        time.
 */
void
set_scan_end_time_otp (report_t report, const char* timestamp)
{
  if (timestamp)
    sql ("UPDATE reports SET end_time = %i WHERE id = %llu;",
         parse_otp_time (timestamp), report);
  else
    sql ("UPDATE reports SET end_time = NULL WHERE id = %llu;",
         report);

  report_cache_counts (report);
}

/**
 * @brief Set the end time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  timestamp  End time.  ISO format.
 */
void
set_scan_host_end_time (report_t report, const char* host,
                        const char* timestamp)
{
  gchar *quoted_host;
  quoted_host = sql_quote (host);
  if (sql_int ("SELECT COUNT(*) FROM report_hosts"
               " WHERE report = %llu AND host = '%s';",
               report, quoted_host))
    sql ("UPDATE report_hosts SET end_time = %i"
         " WHERE report = %llu AND host = '%s';",
         parse_iso_time (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, 0, parse_iso_time (timestamp));
  g_free (quoted_host);
}

/**
 * @brief Set the end time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  timestamp  End time.  OTP format (ctime).
 */
void
set_scan_host_end_time_otp (report_t report, const char* host,
                            const char* timestamp)
{
  gchar *quoted_host;
  quoted_host = sql_quote (host);
  if (sql_int ("SELECT COUNT(*) FROM report_hosts"
               " WHERE report = %llu AND host = '%s';",
               report, quoted_host))
    sql ("UPDATE report_hosts SET end_time = %i"
         " WHERE report = %llu AND host = '%s';",
         parse_otp_time (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, 0, parse_otp_time (timestamp));
  g_free (quoted_host);
}

/**
 * @brief Set the start time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  timestamp  Start time.  ISO format.
 */
void
set_scan_host_start_time (report_t report, const char* host,
                          const char* timestamp)
{
  gchar *quoted_host;
  quoted_host = sql_quote (host);
  if (sql_int ("SELECT COUNT(*) FROM report_hosts"
               " WHERE report = %llu AND host = '%s';",
               report, quoted_host))
    sql ("UPDATE report_hosts SET start_time = %i"
         " WHERE report = %llu AND host = '%s';",
         parse_iso_time (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, parse_iso_time (timestamp), 0);
  g_free (quoted_host);
}

/**
 * @brief Set the start time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  timestamp  Start time.  OTP format (ctime).
 */
void
set_scan_host_start_time_otp (report_t report, const char* host,
                              const char* timestamp)
{
  gchar *quoted_host;
  quoted_host = sql_quote (host);
  if (sql_int ("SELECT COUNT(*) FROM report_hosts"
               " WHERE report = %llu AND host = '%s';",
               report, quoted_host))
    sql ("UPDATE report_hosts SET start_time = %i"
         " WHERE report = %llu AND host = '%s';",
         parse_otp_time (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, parse_otp_time (timestamp), 0);
  g_free (quoted_host);
}

/**
 * @brief Get the timestamp of a report.
 *
 * @todo Lacks permission check.  Caller contexts all have permission
 *       checks before calling this so it's safe.  Rework callers so
 *       they pass report_t instead of UUID string.
 *
 * @param[in]   report_id    UUID of report.
 * @param[out]  timestamp    Timestamp on success.  Caller must free.
 *
 * @return 0 on success, -1 on error.
 */
int
report_timestamp (const char* report_id, gchar** timestamp)
{
  const char* stamp;
  time_t time = sql_int ("SELECT date FROM reports where uuid = '%s';",
                         report_id);
  stamp = iso_time (&time);
  if (stamp == NULL) return -1;
  *timestamp = g_strdup (stamp);
  return 0;
}

/**
 * @brief Return the run status of the scan associated with a report.
 *
 * @param[in]   report  Report.
 * @param[out]  status  Scan run status.
 *
 * @return 0 on success, -1 on error.
 */
int
report_scan_run_status (report_t report, int* status)
{
  *status = sql_int ("SELECT scan_run_status FROM reports"
                     " WHERE reports.id = %llu;",
                     report);
  return 0;
}

/**
 * @brief Return the run status of the scan associated with a report.
 *
 * @param[in]   report  Report.
 * @param[out]  status  Scan run status.
 *
 * @return 0 on success, -1 on error.
 */
int
set_report_scan_run_status (report_t report, task_status_t status)
{
  sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;",
       status,
       report);
  return 0;
}

/**
 * @brief Return SQL for the auto_type column.
 *
 * @param[in]  report     Report.
 * @param[in]  autofp     Whether to apply the auto FP filter.
 *
 * @return Message count.
 */
static gchar *
column_auto_type (report_t report, int autofp)
{
  gchar *auto_type_sql;
  switch (autofp)
    {
      case 1:
        auto_type_sql = g_strdup_printf
          ("  (CASE WHEN"
           "   (((SELECT family FROM nvts WHERE oid = results.nvt)"
           "     IN (" LSC_FAMILY_LIST "))"
           "    OR results.nvt = '0'" /* Open ports previously had 0 NVT. */
           "    OR EXISTS"
           "    (SELECT id FROM nvts"
           "     WHERE oid = results.nvt"
           "     AND"
           "     (cve = 'NOCVE'"
           "      OR cve NOT IN (SELECT cve FROM nvts"
           "                     WHERE oid IN (SELECT source_name"
           "                                   FROM report_host_details"
           "                                   WHERE report_host"
           "                                   = (SELECT id"
           "                                      FROM report_hosts"
           "                                      WHERE report = %llu"
           "                                      AND host = results.host)"
           "                                   AND name = 'EXIT_CODE'"
           "                                   AND value = 'EXIT_NOTVULN')"
           "                     AND family IN (" LSC_FAMILY_LIST ")))))"
           "   THEN NULL"
           "   ELSE 1 END)",
           report);
         break;

      case 2:
        auto_type_sql = g_strdup_printf
          ("  (CASE WHEN"
           "   ((SELECT family FROM nvts WHERE oid = results.nvt)"
           "    IN (" LSC_FAMILY_LIST ")"
           "    OR results.nvt = '0'" /* Open ports previously had 0 NVT. */
           "    OR EXISTS"
           "    (SELECT id FROM nvts AS outer_nvts"
           "     WHERE oid = results.nvt"
           "     AND"
           "     (cve = 'NOCVE'"
           "      OR NOT EXISTS"
           "         (SELECT cve FROM nvts"
           "          WHERE oid IN (SELECT source_name"
           "                        FROM report_host_details"
           "                        WHERE report_host"
           "                        = (SELECT id"
           "                           FROM report_hosts"
           "                           WHERE report = %llu"
           "                           AND host = results.host)"
           "                        AND name = 'EXIT_CODE'"
           "                        AND value = 'EXIT_NOTVULN')"
           "          AND family IN (" LSC_FAMILY_LIST ")"
           /* The CVE of the result NVT is outer_nvts.cve.  The CVE of the
            * NVT that has registered the "closed" host detail is nvts.cve.
            * Either can be a list of CVEs. */
           "          AND common_cve (nvts.cve, outer_nvts.cve)))))"
           "   THEN NULL"
           "   ELSE 1 END)",
           report);
         break;

       default:
         auto_type_sql = g_strdup (" NULL");
         break;
     }
  return auto_type_sql;
}

/**
 * @brief Get the number of results in the scan associated with a report.
 *
 * @param[in]   report         Report.
 * @param[in]   levels         String describing threat levels (message types)
 *                             to include in count (for example, "hmlgd" for
 *                             High, Medium, Low, loG and Debug).  All levels if
 *                             NULL.
 * @param[in]   search_phrase  Phrase that results must include.  All results if
 *                             NULL or "".
 * @param[in]   search_phrase_exact  Whether search phrase is exact.
 * @param[in]   min_cvss_base  Minimum QoD of included results.  All
 *                             results if NULL.
 * @param[in]   min_cvss_base  Minimum CVSS base of included results.  All
 *                             results if NULL.
 * @param[in]   override       Whether to override threats.
 * @param[in]   autofp         Whether to apply the auto FP filter.
 * @param[out]  count          Total number of results in the scan.
 *
 * @return 0 on success, -1 on error.
 */
int
report_scan_result_count (report_t report, const char* levels,
                          const char* search_phrase, int search_phrase_exact,
                          const char* min_cvss_base, const char* min_qod,
                          int override, int autofp, int* count)
{
  GString *levels_sql, *phrase_sql, *cvss_sql;
  gchar *qod_sql;
  gchar *new_severity_sql = NULL;

  phrase_sql = where_search_phrase (search_phrase, search_phrase_exact);
  cvss_sql = where_cvss_base (min_cvss_base);
  qod_sql = where_qod (min_qod);

  if (report_counts_cache_exists (report, override)
      && autofp == 0
      && (min_cvss_base == NULL || strcmp (min_cvss_base, "") == 0)
      && (search_phrase == NULL || strcmp (search_phrase, "") == 0)
      && (min_qod == NULL
          || strcmp (min_qod, "") == 0
          || strcmp (min_qod, G_STRINGIFY (MIN_QOD_DEFAULT)) == 0))
    {
      *count = sql_int ("SELECT sum (count)"
                        " FROM report_counts"
                        " WHERE report = %llu"
                        "   AND override = %d"
                        "   AND \"user\" = (SELECT id FROM users"
                        "                   WHERE users.uuid = '%s')"
                        "   AND severity >= " G_STRINGIFY (SEVERITY_FP) ";",
                        report, override, current_credentials.uuid);
      return 0;
    }

  if (override)
    {
      gchar *severity_sql, *ov, *auto_type_sql;
      gchar *owned_clause;

      auto_type_sql = NULL;
      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup("CASE WHEN results.severity"
                                "          > " G_STRINGIFY (SEVERITY_LOG)
                                " THEN coalesce ((SELECT CAST (cvss_base"
                                "                              AS REAL)"
                                "                 FROM nvts"
                                "                 WHERE nvts.oid"
                                "                         = results.nvt),"
                                "                results.severity)"
                                " ELSE results.severity END");
      else
        severity_sql = g_strdup ("results.severity");

      owned_clause = where_owned_for_get ("override", NULL);

      ov = g_strdup_printf
            ("SELECT overrides.new_severity"
             " FROM overrides"
             " WHERE overrides.nvt = results.nvt"
             " AND %s"
             " AND ((overrides.end_time = 0)"
             "      OR (overrides.end_time >= m_now ()))"
             " AND (overrides.task = results.task"
             "      OR overrides.task = 0)"
             " AND (overrides.result = results.id"
             "      OR overrides.result = 0)"
             " AND (overrides.hosts is NULL"
             "      OR overrides.hosts = ''"
             "      OR hosts_contains (overrides.hosts, results.host))"
             " AND (overrides.port is NULL"
             "      OR overrides.port = ''"
             "      OR overrides.port = results.port)"
             " AND severity_matches_ov (%s, overrides.severity)"
             " ORDER BY overrides.result DESC, overrides.task DESC,"
             " overrides.port DESC, overrides.severity ASC,"
             " overrides.creation_time DESC"
             " LIMIT 1",
             owned_clause,
             severity_sql);

      g_free (owned_clause);

      new_severity_sql = g_strdup_printf ("coalesce ((%s), %s)",
                                          ov,
                                          severity_sql);

      auto_type_sql = column_auto_type (report, autofp);
      levels_sql = where_levels_auto (levels, new_severity_sql, auto_type_sql);

      g_free (auto_type_sql);
      g_free (severity_sql);
      g_free (ov);
    }
  else
    levels_sql = where_levels_type (levels);

  *count = sql_int ("SELECT count(results.id)"
                    " FROM results"
                    " WHERE results.report = %llu"
                    " AND (CAST (%s AS REAL)"
                    "       >= " G_STRINGIFY (SEVERITY_FP) ")"
                    "%s%s%s%s;",
                    report,
                    new_severity_sql ? new_severity_sql : "severity",
                    levels_sql ? levels_sql->str : "",
                    phrase_sql ? phrase_sql->str : "",
                    cvss_sql ? cvss_sql->str : "",
                    qod_sql ? qod_sql : "");

  if (levels_sql) g_string_free (levels_sql, TRUE);
  if (phrase_sql) g_string_free (phrase_sql, TRUE);
  if (cvss_sql) g_string_free (cvss_sql, TRUE);
  g_free (qod_sql);
  g_free (new_severity_sql);

  return 0;
}

/**
 * @brief Check if a result matches the autofp filter criteria.
 *
 * @param[in]  results        Result iterator.
 * @param[in]  autofp         Whether to apply the auto FP filter.
 *
 * @return 1 if match, 0 otherwise.
 */
static int
report_counts_autofp_match (iterator_t *results, int autofp)
{
  if (strcmp (iterator_string (results, 1), "0") == 0)
    /* Open port special case.  These previously had nvt 0. */
    return 1;

  switch (autofp)
    {
      case 1:
        if (sql_int ("SELECT count (*) FROM nvts"
                     " WHERE oid = '%s'"
                     " AND"
                     " (family IN (" LSC_FAMILY_LIST ")"
                     "  OR cve = 'NOCVE'"
                     "  OR cve NOT IN (SELECT cve FROM nvts"
                     "                 WHERE oid IN (SELECT source_name"
                     "                               FROM report_host_details"
                     "                               WHERE report_host"
                     "                               = (SELECT id"
                     "                                  FROM report_hosts"
                     "                                  WHERE report = %llu"
                     "                                  AND host = '%s')"
                     "                               AND name = 'EXIT_CODE'"
                     "                               AND value"
                     "                               = 'EXIT_NOTVULN')"
                     "                 AND family IN (" LSC_FAMILY_LIST ")));",
                     iterator_string (results, 1),
                     iterator_int64 (results, 6),
                     iterator_string (results, 3)))
          return 1;
        break;
      case 2:
        if (sql_int ("SELECT count (*) FROM nvts AS outer_nvts"
                     " WHERE oid = '%s'"
                     " AND"
                     " (family IN (" LSC_FAMILY_LIST ")"
                     "  OR cve = 'NOCVE'"
                     "  OR NOT EXISTS"
                     "     (SELECT cve FROM nvts"
                     "      WHERE oid IN (SELECT source_name"
                     "                    FROM report_host_details"
                     "                    WHERE report_host"
                     "                    = (SELECT id FROM report_hosts"
                     "                       WHERE report = %llu"
                     "                       AND host = '%s')"
                     "                    AND name = 'EXIT_CODE'"
                     "                    AND value = 'EXIT_NOTVULN')"
                     "      AND family IN (" LSC_FAMILY_LIST ")"
                     /* The CVE of the result NVT is outer_nvts.cve.  The CVE
                      * of the NVT that has registered the "closed" host
                      * detail is nvts.cve.  Either can be a list of CVEs. */
                     "      AND common_cve (nvts.cve, outer_nvts.cve)));",
                     iterator_string (results, 1),
                     iterator_int64 (results, 6),
                     iterator_string (results, 3)))
          return 1;
        break;
    }
  return 0;
}

/**
 * @brief Check if a result matches the filter criteria.
 *
 * @param[in]  results        Result iterator.
 * @param[in]  search_phrase  Search phrase.
 * @param[in]  search_phrase_exact  Whether search phrase is exact.
 * @param[in]  min_cvss_base  Minimum CVSS base.
 * @param[in]  min_cvss_base  Minimum QoD.
 * @param[in]  autofp         Whether to apply the auto FP filter.
 *
 * @return 1 if match, 0 otherwise.
 */
static int
report_counts_match (iterator_t *results, const char *search_phrase,
                     int search_phrase_exact, const char *min_cvss_base,
                     const char* min_qod, int autofp)
{
  if (autofp && (report_counts_autofp_match (results, autofp) == 0))
    return 0;

  if (min_qod)
    {
      int min_qod_int;
      if (sscanf (min_qod, "%d", &min_qod_int) != 1)
        min_qod_int = -1;

      if (iterator_int (results, 8) < min_qod_int)
        return 0;
    }

  /* This must match the conditions in the SQL in where_search_phrase. */
  if (search_phrase && search_phrase_exact)
    {
      if (/* Port. */
          (strcmp (iterator_string (results, 4), search_phrase) == 0)
          /* Host. */
          || (strcmp (iterator_string (results, 3), search_phrase) == 0)
          /* NVT OID. */
          || (strcmp (iterator_string (results, 1), search_phrase) == 0)
          /* Description. */
          || (strcmp (iterator_string (results, 5), search_phrase) == 0)
          /* NVT Name, Tag or CVE. */
          || sql_int ("SELECT EXISTS (SELECT 1 FROM nvts"
                      "               WHERE oid = '%s'"
                      "               AND (name = '%s'"
                      "                    OR tag LIKE '%%%%=%s|%%%%'"
                      "                    OR tag LIKE '%%%%=%s'"
                      "                    OR cve LIKE '%%%%%s%%%%'))",
                      iterator_string (results, 1),
                      search_phrase,
                      search_phrase,
                      search_phrase,
                      search_phrase))
        {
          if (min_cvss_base && iterator_int (results, 1))
            {
              gchar *quoted_min_cvss_base;
              quoted_min_cvss_base = sql_quote (min_cvss_base);
              if (sql_int ("SELECT EXISTS (SELECT id FROM nvts"
                           "               WHERE nvts.oid = '%s')"
                           "       AND (SELECT (CAST (cvss_base AS REAL))"
                           "                   >= CAST ('%s' AS REAL)"
                           "            FROM nvts"
                           "            WHERE nvts.oid = '%s');",
                           iterator_string (results, 1),
                           quoted_min_cvss_base,
                           iterator_string (results, 1)))
                return 1;
            }
          else
            return 1;
        }
    }
  else if (search_phrase)
    {
      if (/* Port. */
          (strcasestr (iterator_string (results, 4), search_phrase) != 0)
          /* Host. */
          || (strcasestr (iterator_string (results, 3), search_phrase) != 0)
          /* NVT OID. */
          || (strcasestr (iterator_string (results, 1), search_phrase) != 0)
          /* Description. */
          || (strcasestr (iterator_string (results, 5), search_phrase) != 0)
          /* NVT Name, Tag or CVE. */
          || sql_int ("SELECT EXISTS (SELECT 1 FROM nvts"
                      "               WHERE oid = '%s'"
                      "               AND (name LIKE '%%%%%s%%%%'"
                      "                    OR tag LIKE '%%%%=%%%%%s%%%%'"
                      "                    OR cve LIKE '%%%%%s%%%%'))",
                      iterator_string (results, 1),
                      search_phrase,
                      search_phrase,
                      search_phrase))
        {
          if (min_cvss_base && iterator_int (results, 1))
            {
              gchar *quoted_min_cvss_base;
              quoted_min_cvss_base = sql_quote (min_cvss_base);
              if (sql_int ("SELECT EXISTS (SELECT id FROM nvts"
                           "               WHERE nvts.oid = '%s')"
                           "       AND (SELECT (CAST (cvss_base AS REAL))"
                           "                   >= CAST ('%s' AS REAL)"
                           "            FROM nvts"
                           "            WHERE nvts.oid = '%s');",
                           iterator_string (results, 1),
                           quoted_min_cvss_base,
                           iterator_string (results, 1)))
                {
                  g_free (quoted_min_cvss_base);
                  return 1;
                }
              g_free (quoted_min_cvss_base);
            }
          else
            return 1;
        }
    }
  else if (min_cvss_base && iterator_int (results, 1))
    {
      gchar *quoted_min_cvss_base;
      quoted_min_cvss_base = sql_quote (min_cvss_base);
      if (sql_int ("SELECT EXISTS (SELECT id FROM nvts"
                   "               WHERE nvts.oid = '%s')"
                   "       AND (SELECT (CAST (cvss_base AS REAL))"
                   "                   >= CAST ('%s' AS REAL)"
                   "            FROM nvts"
                   "            WHERE nvts.oid = '%s');",
                   iterator_string (results, 1),
                   quoted_min_cvss_base,
                   iterator_string (results, 1)))
        {
          g_free (quoted_min_cvss_base);
          return 1;
        }
      g_free (quoted_min_cvss_base);
    }
  else
    return 1;
  return 0;
}

/**
 * @brief Prepare quick statement for report_severity_data.
 *
 * @return Statement.
 */
sql_stmt_t *
report_severity_data_prepare ()
{
  sql_stmt_t *stmt;
  gchar *owned_clause;

  owned_clause = where_owned_for_get ("override", NULL);
  stmt = sql_prepare ("SELECT 1 FROM overrides"
                      " WHERE (overrides.nvt = $1)"
                      " AND %s"
                      " AND ((overrides.end_time = 0)"
                      "      OR (overrides.end_time >= m_now ()))",
                      owned_clause);
  g_free (owned_clause);
  if (stmt == NULL)
    {
      g_warning ("%s: sql_prepare stmt failed\n", __FUNCTION__);
      abort ();
    }
  return stmt;
}

/**
 * @brief Prepare quick statement for report_severity_data.
 *
 * @return Statement.
 */
sql_stmt_t *
report_severity_data_prepare_full (task_t task)
{
  sql_stmt_t *full_stmt;
  gchar *owned_clause;

  owned_clause = where_owned_for_get ("override", NULL);
  full_stmt = sql_prepare
               ("SELECT severity_to_type (overrides.new_severity),"
                 "       overrides.new_severity"
                 " FROM overrides"
                 " WHERE overrides.nvt = $1" // 1
                 " AND %s"
                 " AND ((overrides.end_time = 0)"
                 "      OR (overrides.end_time >= m_now ()))"
                 " AND (overrides.task = 0"
                 "      OR overrides.task = %llu)"
                 " AND (overrides.result = 0"
                 "      OR overrides.result = $2)" // 2
                 " AND (overrides.hosts is NULL"
                 "      OR overrides.hosts = ''"
                 "      OR hosts_contains (overrides.hosts, $3))" // 3
                 " AND (overrides.port is NULL"
                 "      OR overrides.port = ''"
                 "      OR overrides.port = $4)" // 4
                 " AND severity_matches_ov ($5," // 5
                 "                          overrides.severity)"
                 " ORDER BY overrides.result DESC, overrides.task DESC,"
                 " overrides.port DESC, overrides.severity ASC,"
                 " overrides.modification_time DESC;",
                 owned_clause,
                 task);
  g_free (owned_clause);
  if (full_stmt == NULL)
    {
      g_warning ("%s: sql_prepare full_stmt failed\n", __FUNCTION__);
      abort ();
    }
  return full_stmt;
}

/**
 * @brief Get the result severity counts for a report.
 *
 * @param[in]  report     Report.
 * @param[in]  override   Whether to override the threat.
 * @param[in]  host       Host to which to limit the count.  NULL to allow all.
 * @param[in]  min_cvss_base  Minimum CVSS base of filtered results.  All
 *                            results if NULL.
 * @param[in]  min_qod    Minimum QoD of filtered results.  All
 *                        results if NULL.
 * @param[in]  search_phrase  Phrase that filtered results must include.  All results
 *                            if NULL or "".
 * @param[in]  search_phrase_exact     Whether search phrase is exact.
 * @param[in]  autofp                  Whether to apply the auto FP filter.
 * @param[out] severity_data           The severity data struct to store counts in.
 * @param[out] filtered_severity_data  The severity data struct to store counts in.
 */
static void
report_severity_data (report_t report, int override,
                      const char *host, const char *min_cvss_base,
                      const char *min_qod,
                      const char *search_phrase, int search_phrase_exact,
                      int autofp, severity_data_t* severity_data,
                      severity_data_t* filtered_severity_data)
{
  iterator_t results;
  task_t task;

  sql_stmt_t *stmt, *full_stmt;
  gchar *quoted_host, *severity_sql, *qod_sql, *owned_clause;
  int ret;

  owned_clause = where_owned_for_get ("override", NULL);

  if (override
      && sql_int ("SELECT count(*)"
                  " FROM overrides"
                  " WHERE %s"
                  " AND ((overrides.end_time = 0)"
                  "      OR (overrides.end_time >= m_now ()))",
                  owned_clause))
    {
      /* Prepare quick inner statement. */

      stmt = report_severity_data_prepare ();

      /* Prepare full inner statement. */

      report_task (report, &task);

      full_stmt = report_severity_data_prepare_full (task);

      /* Loop through all results. */
      if (host)
        quoted_host = sql_quote (host);
      else
        quoted_host = NULL;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup("CASE WHEN results.severity"
                                "          > " G_STRINGIFY (SEVERITY_LOG)
                                " THEN coalesce ((SELECT CAST (cvss_base"
                                "                              AS REAL)"
                                "                 FROM nvts"
                                "                 WHERE nvts.oid"
                                "                         = results.nvt),"
                                "                results.severity)"
                                " ELSE results.severity END");
      else
        severity_sql = g_strdup ("results.severity");

      qod_sql = where_qod (min_qod);

      init_iterator (&results,
                      "SELECT results.id, results.nvt, results.type,"
                      " results.host, results.port, results.description,"
                      " results.report, %s, qod"
                      " FROM results"
                      " WHERE"
                      "%s%s%s"
                      " results.report = %llu"
                      "%s;",
                      severity_sql,
                      host ? " results.host = '" : "",
                      host ? quoted_host : "",
                      host ? "' AND" : "",
                      report,
                      qod_sql);
      g_free (severity_sql);
      if (host)
        g_free (quoted_host);
      while (next (&results))
        {
          const char *nvt, *new_type;
          double new_severity;

          nvt = iterator_string (&results, 1);

          while (1)
            {
              /* Bind the current result values into the quick statement. */

              if (sql_bind_text (stmt, 1, nvt, -1))
                {
                  g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
                  abort ();
                }

              /* Run the quick inner statement to check for overrides. */

              ret = sql_exec (stmt);
              if (ret == -2 || ret == 2)
                {
                  /* Gave up with statement reset, or schema error.  Retry. */
                  sql_finalize (stmt);
                  stmt = report_severity_data_prepare ();
                  continue;
                }
              if (ret < 0)
                {
                  g_warning ("%s: sql_exec failed\n", __FUNCTION__);
                  abort ();
                }
              break;
            }

          /* Check the result. */

          if (ret == 0)
            {
              new_severity = iterator_double (&results, 7);
              new_type = iterator_string (&results, 2);

              if (new_type)
                {
                  if (severity_data)
                    {
                      if (autofp == 0
                          || report_counts_autofp_match (&results, autofp)
                          || new_severity == SEVERITY_LOG)
                        severity_data_add (severity_data, new_severity);
                      else
                        severity_data_add (severity_data, SEVERITY_FP);
                    }

                  if (filtered_severity_data)
                    {
                      if (report_counts_match (&results, search_phrase,
                                                search_phrase_exact,
                                                min_cvss_base, min_qod, autofp))
                        severity_data_add (filtered_severity_data,
                                           new_severity);
                      else if (report_counts_match (&results, search_phrase,
                                                    search_phrase_exact,
                                                    min_cvss_base, min_qod, 0))
                        severity_data_add (filtered_severity_data,
                                           SEVERITY_FP);
                    }
                }
            }
          else
            {
              result_t result;
              const char *host, *port;
              double severity;

              /* There is an override on this NVT, get the new threat value. */

              while (1)
                {
                  /* Bind the current result values into the full statement. */

                  if (sql_bind_text (full_stmt, 1, nvt, -1))
                    {
                      g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
                      abort ();
                    }

                  result = (result_t) iterator_int64 (&results, 0);
                  if (sql_bind_int64 (full_stmt, 2, &result))
                    {
                      g_warning ("%s: sql_bind_int64 failed\n", __FUNCTION__);
                      abort ();
                    }

                  host = iterator_string (&results, 3);
                  if (sql_bind_text (full_stmt, 3, host, -1))
                    {
                      g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
                      abort ();
                    }

                  port = iterator_string (&results, 4);
                  if (sql_bind_text (full_stmt, 4, port, -1))
                    {
                      g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
                      abort ();
                    }

                  severity = iterator_double (&results, 7);
                  if (sql_bind_double (full_stmt, 5, &severity))
                    {
                      g_warning ("%s: sql_bind_double failed\n", __FUNCTION__);
                      abort ();
                    }

                  /* Run the full inner statement. */

                  ret = sql_exec (full_stmt);
                  if (ret == -2 || ret == 2)
                    {
                      /* Gave up with statement reset, or schema error.
                       * Retry. */
                      sql_finalize (full_stmt);
                      full_stmt = report_severity_data_prepare_full (task);
                      continue;
                    }
                  if (ret < 0)
                    {
                      g_warning ("%s: sql_exec failed\n", __FUNCTION__);
                      abort ();
                    }
                  break;
                }

              /* Check the result. */

              if (ret == 0)
                {
                  new_type = iterator_string (&results, 2);
                  new_severity = iterator_double (&results, 7);
                }
              else
                {
                  new_type = sql_column_text (full_stmt, 0);
                  new_severity = sql_column_double (full_stmt, 1);
                }

              if (new_type)
                {
                  if (severity_data)
                    {
                      if (autofp == 0
                          || report_counts_autofp_match (&results, autofp)
                          || new_severity == SEVERITY_LOG)
                        severity_data_add (severity_data, new_severity);
                      else
                        severity_data_add (severity_data, SEVERITY_FP);
                    }

                  if (filtered_severity_data)
                    {
                      if (report_counts_match (&results, search_phrase,
                                                search_phrase_exact,
                                                min_cvss_base, min_qod, autofp))
                        severity_data_add (filtered_severity_data,
                                           new_severity);
                      else if (report_counts_match (&results, search_phrase,
                                                    search_phrase_exact,
                                                    min_cvss_base, min_qod, 0))
                        severity_data_add (filtered_severity_data,
                                           SEVERITY_FP);
                    }
                }

              /* Reset the full inner statement. */

              if (sql_reset (full_stmt))
                {
                  g_warning ("%s: sql_reset failed\n", __FUNCTION__);
                  abort ();
                }
            }

          /* Reset the quick inner statement. */

          if (sql_reset (stmt))
            {
              g_warning ("%s: sql_reset failed\n", __FUNCTION__);
              abort ();
            }
        }
      cleanup_iterator (&results);
      sql_finalize (stmt);
      sql_finalize (full_stmt);
    }
  else
    {
      /* Loop through all results. */
      if (host)
        quoted_host = sql_quote (host);
      else
        quoted_host = NULL;

      severity_sql
        = g_strdup_printf ("(SELECT new_severity FROM result_new_severities"
                           " WHERE result_new_severities.result = results.id"
                           "   AND result_new_severities.user"
                           "         = (SELECT users.id"
                           "            FROM current_credentials, users"
                           "            WHERE current_credentials.uuid"
                           "                    = users.uuid)"
                           "   AND result_new_severities.override = %d"
                           "   AND result_new_severities.dynamic = %d"
                           " LIMIT 1)",
                           override,
                           setting_dynamic_severity_int ());

      qod_sql = where_qod (min_qod);

      init_iterator (&results,
                      "SELECT results.id, results.nvt, results.type,"
                      " results.host, results.port, results.description,"
                      " results.report, %s, results.qod"
                      " FROM results"
                      " WHERE"
                      "%s%s%s"
                      " results.report = %llu"
                      " %s;",
                      severity_sql,
                      host ? " results.host = '" : "",
                      host ? quoted_host : "",
                      host ? "' AND" : "",
                      report,
                      qod_sql);

      g_free (qod_sql);
      g_free (severity_sql);
      if (host)
        g_free (quoted_host);

      while (next (&results))
        {
          const char *new_type;
          double new_severity;

          /* Check the result. */
          new_type = iterator_string (&results, 2);
          new_severity = iterator_double (&results, 7);

          if (new_type)
            {
              if (severity_data)
                {
                  if (autofp == 0
                      || report_counts_autofp_match (&results, autofp)
                      || new_severity == SEVERITY_LOG)
                    severity_data_add (severity_data, new_severity);
                  else
                    severity_data_add (severity_data, SEVERITY_FP);
                }

              if (filtered_severity_data)
                {
                  if (report_counts_match (&results, search_phrase,
                                            search_phrase_exact,
                                            min_cvss_base, min_qod, autofp))
                    severity_data_add (filtered_severity_data,
                                       new_severity);
                  else if (report_counts_match (&results, search_phrase,
                                                search_phrase_exact,
                                                min_cvss_base, min_qod, 0))
                    severity_data_add (filtered_severity_data,
                                       SEVERITY_FP);
                }
            }
        }
      cleanup_iterator (&results);
    }

  g_free (owned_clause);
}

/**
 * @brief Get the message counts for a report given the UUID.
 *
 * @todo Lacks permission check.  Caller contexts all have permission
 *       checks before calling this so it's safe.  Rework callers to
 *       use report_counts_id instead.
 *
 * @param[in]   report_id    ID of report.
 * @param[out]  debugs       Number of debug messages.
 * @param[out]  holes        Number of hole messages.
 * @param[out]  infos        Number of info messages.
 * @param[out]  logs         Number of log messages.
 * @param[out]  warnings     Number of warning messages.
 * @param[out]  false_positives  Number of false positives.
 * @param[out]  severity     Maximum severity score.
 * @param[in]   override     Whether to override the threat.
 * @param[in]   autofp       Whether to apply the auto FP filter.
 *
 * @return 0 on success, -1 on error.
 */
int
report_counts (const char* report_id, int* debugs, int* holes, int* infos,
               int* logs, int* warnings, int* false_positives, double* severity,
               int override, int autofp, int min_qod)
{
  report_t report;
  gchar *min_qod_str = g_strdup_printf ("%d", min_qod);
  int ret;
  // TODO Wrap in transaction.
  if (find_report_with_permission (report_id, &report, "get_reports"))
    return -1;
  // TODO Check if report was found.
  ret = report_counts_id (report, debugs, holes, infos, logs, warnings,
                          false_positives, severity, override, NULL, autofp,
                          min_qod_str);
  return ret;
}

/**
 * @brief Test if a counts cache exists for a report and the current user.
 * @param[in]           report    The report to check.
 * @param[in]           override  Whether to check for overridden results.
 *
 * @return 1 if cache exists, 0 otherwise.
 */
static int
report_counts_cache_exists (report_t report, int override)
{
  if (setting_dynamic_severity_int ())
    return 0;
  else
    return sql_int ("SELECT EXISTS (SELECT * FROM report_counts"
                    " WHERE report = %llu"
                    "   AND override = %d"
                    "   AND \"user\" = (SELECT id FROM users"
                    "                   WHERE users.uuid = '%s')"
                    "   AND (end_time = 0 OR end_time >= m_now ()));",
                    report, override, current_credentials.uuid);
}

/**
 * @brief Get cached result counts for a report and the current user.
 *
 * @param[in]           report    The report to get counts from.
 * @param[in]           override  Whether to get overridden results.
 * @param[out]          data      The severity_data_t to save counts in.
 */
static void
report_counts_from_cache (report_t report, int override, severity_data_t* data)
{
  iterator_t iterator;
  init_iterator (&iterator,
                 "SELECT severity, count FROM report_counts"
                 " WHERE report = %llu"
                 "   AND override = %i"
                 "   AND \"user\" = (SELECT id FROM users"
                 "                   WHERE users.uuid = '%s')"
                 "   AND (end_time = 0 OR end_time >= m_now ());",
                 report, override, current_credentials.uuid);
  while (next (&iterator))
    {
      severity_data_add_count (data,
                               iterator_double (&iterator, 0),
                               iterator_int (&iterator, 1));
    }
  cleanup_iterator (&iterator);
}

/**
 * @brief Cache the message counts for a report.
 *
 * @param[in]   report    Report.
 * @param[in]   override  Whether overrides were applied to the results.
 * @param[in]   data      Severity data struct containing the message counts.
 * @param[in]   make_transaction  True to wrap in an exclusive transaction.
 *
 * @return      0 if successful, 1 gave up, -1 error (see sql_giveup).
 */
static int
cache_report_counts (report_t report, int override, severity_data_t* data,
                     int make_transaction)
{
  /* Try cache results.  Give up if the database is locked because this could
   * happen while the caller has an SQL statement open.  If another process
   * tries to write to the database between the statement open and
   * cache_report_counts then they'll deadlock. */
  int i, ret;
  double severity;
  int end_time;

  // Do not cache results when using dynamic severity.
  if (setting_dynamic_severity_int ())
    return 0;

  if (make_transaction)
    {
      ret = sql_begin_exclusive_giveup ();
      if (ret)
        return ret;
    }

  ret = sql_giveup ("DELETE FROM report_counts"
                    " WHERE report = %llu"
                    "   AND override = %i"
                    "   AND \"user\" = (SELECT id FROM users"
                    "                   WHERE users.uuid = '%s');",
                    report, override, current_credentials.uuid);
  if (ret)
    {
      if (make_transaction)
        sql ("ROLLBACK;");
      return ret;
    }

  if (data->total == 0)
    {
      /* Create dummy entry for empty reports */
      ret = sql_giveup ("INSERT INTO report_counts"
                        " (report, \"user\", override, severity, count,"
                        "  end_time)"
                        " VALUES (%llu,"
                        "         (SELECT id FROM users"
                        "          WHERE users.uuid = '%s'),"
                        "         %d, " G_STRINGIFY (SEVERITY_MISSING) ","
                        "         0, 0);",
                        report, current_credentials.uuid, override);
      if (ret)
        {
          if (make_transaction)
            sql ("ROLLBACK;");
          return ret;
        }
    }
  else
    {
      i = 0;
      if (override)
        end_time = sql_int ("SELECT coalesce(min(end_time), 0)"
                            " FROM overrides, results"
                            " WHERE overrides.nvt = results.nvt"
                            " AND results.report = %llu"
                            " AND overrides.end_time >= m_now ();",
                            report);
      else
        end_time = 0;

      severity = severity_data_value (i);
      while (severity <= (data->max + (1.0
                                       / SEVERITY_SUBDIVISIONS
                                       / SEVERITY_SUBDIVISIONS))
             && severity != SEVERITY_MISSING)
        {
          if (data->counts[i] > 0)
            {
              ret = sql_giveup ("INSERT INTO report_counts"
                                " (report, \"user\", override, severity, count,"
                                "  end_time)"
                                " VALUES (%llu,"
                                "         (SELECT id FROM users"
                                "          WHERE users.uuid = '%s'),"
                                "         %d, %1.1f, %d, %d);",
                                report, current_credentials.uuid, override,
                                severity, data->counts[i], end_time);
              if (ret)
                {
                  if (make_transaction)
                    sql ("ROLLBACK;");
                  return ret;
                }
            }
          i++;
          severity = severity_data_value (i);
        }
    }
  if (make_transaction)
    {
      ret = sql_giveup ("COMMIT;");
      if (ret)
        {
          sql ("ROLLBACK;");
          return ret;
        }
    }
  return 0;
}

/**
 * @brief Get the message counts for a report.
 *
 * @param[in]   report    Report.
 * @param[out]  debugs    Number of debug messages.
 * @param[out]  holes     Number of hole messages.
 * @param[out]  infos     Number of info messages.
 * @param[out]  logs      Number of log messages.
 * @param[out]  warnings  Number of warning messages.
 * @param[out]  false_positives    Number of false positive messages.
 * @param[out]  severity  Maximum severity of the report.
 * @param[in]   override           Whether to override the threat.
 * @param[in]   host               Host to which to limit the count.  NULL to allow all.
 * @param[in]   min_cvss_base      Minimum CVSS base of filtered results.  All
 *                                 results if NULL.
 * @param[in]   min_qod            Minimum QoD of filtered results.  All
 *                                 results if NULL.
 * @param[in]   search_phrase      Phrase that filtered results must include.
 *                                 All results if NULL or "".
 * @param[in]   search_phrase_exact  Whether search phrase is exact.
 * @param[in]   autofp             Whether to apply the auto FP filter.
 * @param[out]  filtered_debugs    Number of debug messages after filtering.
 * @param[out]  filtered_holes     Number of hole messages after filtering.
 * @param[out]  filtered_infos     Number of info messages after filtering.
 * @param[out]  filtered_logs      Number of log messages after filtering.
 * @param[out]  filtered_warnings  Number of warning messages after filtering.
 * @param[out]  filtered_false_positives  Number of false positive messages after
 *                                        filtering.
 * @param[out]  filtered_severity  Maximum severity after filtering.
 *
 * @return 0 on success, -1 on error.
 */
static int
report_counts_id_filt (report_t report, int* debugs, int* holes, int* infos,
                       int* logs, int* warnings, int* false_positives,
                       double* severity, int override, const char *host,
                       const char *min_cvss_base, const char *min_qod,
                       const char *search_phrase, int search_phrase_exact,
                       int autofp,
                       int* filtered_debugs, int* filtered_holes,
                       int* filtered_infos, int* filtered_logs,
                       int* filtered_warnings, int* filtered_false_positives,
                       double* filtered_severity)
{
  int filtered_requested, cache_exists;
  const char *severity_class;
  severity_data_t severity_data, filtered_severity_data;

  filtered_requested = (filtered_holes || filtered_warnings || filtered_infos
                        || filtered_logs || filtered_false_positives
                        || filtered_severity);
  severity_class = setting_severity ();

  if (current_credentials.uuid == NULL
      || strcmp (current_credentials.uuid, "") == 0)
    g_warning ("%s: called by NULL or dummy user", __FUNCTION__);

  cache_exists = report_counts_cache_exists (report, override);

  init_severity_data (&severity_data);
  init_severity_data (&filtered_severity_data);

  /* This adds time and is out of scope of OMP threat levels, so skip it */
  if (debugs)
    *debugs = 0;

  if (filtered_debugs)
    *filtered_debugs = 0;

  if (search_phrase && strcmp (search_phrase, "") == 0)
    search_phrase = NULL;

  if (min_cvss_base && strcmp (min_cvss_base, "") == 0)
    min_cvss_base = NULL;

  // TODO: Cache different min_qod values
  if (cache_exists && autofp == 0 && host == NULL && min_cvss_base == NULL
      && ((min_qod && strcmp (min_qod, G_STRINGIFY (MIN_QOD_DEFAULT)) == 0)
          || min_qod == NULL)
      && search_phrase == NULL)
    {
      /* Get unfiltered counts from cache. */
      report_counts_from_cache (report, override, &severity_data);
      if (filtered_requested)
        report_counts_from_cache (report, override, &filtered_severity_data);
    }
  else
    {
      /* Recalculate. */
      if (filtered_requested)
        report_severity_data (report, override, host, min_cvss_base, min_qod,
                              search_phrase, search_phrase_exact,
                              autofp, &severity_data, &filtered_severity_data);
      else
        report_severity_data (report, override, host, min_cvss_base, min_qod,
                              search_phrase, search_phrase_exact,
                              autofp, &severity_data, NULL);
    }

  severity_data_level_counts (&severity_data, severity_class,
                              NULL, NULL, false_positives,
                              logs, infos, warnings, holes);
  severity_data_level_counts (&filtered_severity_data, severity_class,
                              NULL, NULL, filtered_false_positives,
                              filtered_logs, filtered_infos,
                              filtered_warnings, filtered_holes);

  if (severity)
    *severity = severity_data.max;
  if (filtered_severity && filtered_requested)
    *filtered_severity = filtered_severity_data.max;

  if (autofp == 0 && host == NULL
      && cache_exists == 0 && min_cvss_base == NULL && search_phrase == NULL)
    cache_report_counts (report, override, &severity_data, 1);

  cleanup_severity_data (&severity_data);
  cleanup_severity_data (&filtered_severity_data);

  return 0;
}

/**
 * @brief Get the message counts for a report.
 *
 * @param[in]   report    Report.
 * @param[out]  debugs    Number of debug messages.
 * @param[out]  holes     Number of hole messages.
 * @param[out]  infos     Number of info messages.
 * @param[out]  logs      Number of log messages.
 * @param[out]  warnings  Number of warning messages.
 * @param[out]  false_positives  Number of false positive messages.
 * @param[out]  severity  Maximum severity score.
 * @param[in]   override  Whether to override the threat.
 * @param[in]   host      Host to which to limit the count.  NULL to allow all.
 * @param[in]   autofp    Whether to apply the auto FP filter.
 * @param[in]   min_qod   Minimum QoD of results to count.
 *
 * @return 0 on success, -1 on error.
 */
int
report_counts_id (report_t report, int* debugs, int* holes, int* infos,
                  int* logs, int* warnings, int* false_positives,
                  double* severity, int override, const char *host, int autofp,
                  const char* min_qod)
{
  return report_counts_id_filt (report, debugs, holes, infos, logs, warnings,
                                false_positives, severity, override, host,
                                NULL, min_qod, NULL, 0, autofp, NULL, NULL,
                                NULL, NULL, NULL, NULL, NULL);

}

/**
 * @brief Get the maximum severity of a report.
 *
 * @param[in]  report     Report.
 * @param[in]  overrides  Whether to apply overrides.
 * @param[in]  min_qod    Minimum QoD of results to count.
 *
 * @return Severity score of the report.
 */
double
report_severity (report_t report, int overrides, int min_qod)
{
  double severity;
  iterator_t iterator;

  // TODO: Cache for non-default QoD.
  init_iterator (&iterator,
                 "SELECT max(severity)"
                 " FROM report_counts"
                 " WHERE report = %llu"
                 " AND override = %d"
                 " AND (end_time = 0 or end_time >= m_now ());",
                 report, overrides);
  if (next (&iterator)
      && min_qod == MIN_QOD_DEFAULT
      && (iterator_null (&iterator, 0) == 0))
    {
      g_debug ("%s: max(severity)=%s", __FUNCTION__,
               iterator_string (&iterator, 0));
      severity = iterator_double (&iterator, 0);
    }
  else
    {
      gchar *min_qod_str = g_strdup_printf ("%d", min_qod);
      g_debug ("%s: could not get max from cache", __FUNCTION__);
      report_counts_id (report, NULL, NULL, NULL, NULL, NULL,
                        NULL, &severity, overrides, NULL, 0,
                        min_qod_str);
      g_free (min_qod_str);
    }
  cleanup_iterator (&iterator);
  return severity;
}

/**
 * @brief Delete a report.
 *
 * It's up to the caller to provide the transaction.
 *
 * @param[in]  report  Report.
 *
 * @return 0 success, 1 report is hidden, 2 report is in use, -1 error.
 */
int
delete_report_internal (report_t report)
{
  task_t task;
  char *slave_task_uuid;

  if (sql_int ("SELECT hidden FROM reports WHERE id = %llu;", report))
    return 1;

  if (sql_int ("SELECT count(*) FROM reports WHERE id = %llu"
               " AND (scan_run_status = %u OR scan_run_status = %u"
               " OR scan_run_status = %u OR scan_run_status = %u"
               " OR scan_run_status = %u);",
               report,
               TASK_STATUS_RUNNING,
               TASK_STATUS_REQUESTED,
               TASK_STATUS_DELETE_REQUESTED,
               TASK_STATUS_DELETE_ULTIMATE_REQUESTED,
               TASK_STATUS_STOP_REQUESTED,
               TASK_STATUS_STOP_REQUESTED_GIVEUP,
               TASK_STATUS_STOP_WAITING))
    return 2;

  if (report_task (report, &task))
    return -1;

  /* Remove any associated slave task. */

  slave_task_uuid = report_slave_task_uuid (report);
  if (slave_task_uuid)
    {
      slave_t slave;

      /* A stopped report leaves the task on the slave.  Try delete the task. */

      /** @todo Store slave on report, in case task is assigned new slave. */
      /** @todo Even that may fail because the slave itself may change. */
      slave = task_slave (task);
      /* For now just forget about it if the slave is 0. */
      if (slave)
        delete_slave_task (slave, slave_task_uuid);
    }

  /* Remove the report data. */

  sql ("DELETE FROM report_host_details WHERE report_host IN"
       " (SELECT id FROM report_hosts WHERE report = %llu);",
       report);
  sql ("DELETE FROM report_hosts WHERE report = %llu;", report);

  sql ("UPDATE tags"
       " SET resource = 0, resource_location = " G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource IN"
       "   (SELECT id FROM results WHERE report = %llu);",
       report);
  sql ("UPDATE tags_trash"
       " SET resource = 0, resource_location = " G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource IN"
       "   (SELECT id FROM results WHERE report = %llu);",
       report);
  sql ("DELETE FROM results WHERE report = %llu;", report);

  sql ("DELETE FROM report_counts WHERE report = %llu;", report);
  sql ("DELETE FROM reports WHERE id = %llu;", report);

  /* Adjust permissions. */

  permissions_set_orphans ("report", report, LOCATION_TABLE);
  tags_set_orphans ("report", report, LOCATION_TABLE);

  /* Update the task state. */

  switch (sql_int64 (&report,
                     "SELECT max (id) FROM reports WHERE task = %llu",
                     task))
    {
      case 0:
        if (report)
          {
            int status;
            if (report_scan_run_status (report, &status))
              return -1;
            sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;",
                 status,
                 task);
          }
        else
          sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;",
               TASK_STATUS_NEW,
               task);
        break;
      case 1:        /* Too few rows in result of query. */
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }

  return 0;
}

/**
 * @brief Modify a report.
 *
 * @param[in]   report_id       UUID of report.
 * @param[in]   comment         Comment on report.
 *
 * @return 0 success, 1 failed to find report, 2 report_id required, 3 comment
 * required, 99 permission denied, -1 internal error.
 */
int
modify_report (const char *report_id, const char *comment)
{
  gchar *quoted_comment;
  report_t report;

  if (report_id == NULL)
    return 2;

  if (comment == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_report") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  report = 0;
  if (find_report_with_permission (report_id, &report, "modify_report"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (report == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_comment = sql_quote (comment ? comment : "");

  sql ("UPDATE reports SET"
       " comment = '%s'"
       " WHERE id = %llu;",
       quoted_comment,
       report);

  g_free (quoted_comment);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete a report.
 *
 * @param[in]  report_id  UUID of report.
 * @param[in]  dummy      Dummy arg to match other delete functions.
 *
 * @return 0 success, 1 report is hidden, 2 report is in use, 99 permission
 *         denied, -1 error.
 */
int
delete_report (const char *report_id, int dummy)
{
  report_t report;
  int ret;

  sql_begin_exclusive ();

  if (user_may ("delete_report") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  report = 0;
  if (find_report_with_permission (report_id, &report, "delete_report"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (report == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  ret = delete_report_internal (report);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Return the slave progress of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Number of reports.
 */
int
report_slave_progress (report_t report)
{
  return sql_int ("SELECT slave_progress FROM reports WHERE id = %llu;",
                  report);
}

/**
 * @brief Set slave progress of a report.
 *
 * @param[in]  report    The report.
 * @param[in]  progress  The new progress value.
 *
 * @return 0 success.
 */
int
set_report_slave_progress (report_t report, int progress)
{
  sql ("UPDATE reports SET slave_progress = %i WHERE id = %llu;",
       progress,
       report);
  return 0;
}

/**
 * @brief Return the UUID of the task on the slave.
 *
 * @param[in]  report    The report.
 *
 * @return UUID of the slave task if any, else NULL.
 */
char*
report_slave_task_uuid (report_t report)
{
  char *uuid;

  uuid = sql_string ("SELECT slave_task_uuid FROM reports WHERE id = %llu;",
                     report);
  if (uuid && strlen (uuid))
    return uuid;
  free (uuid);
  return NULL;
}

/**
 * @brief Set the UUID of the slave task, on the local task.
 *
 * @param[in]  report    The report.
 * @param[in]  uuid  UUID.
 */
void
set_report_slave_task_uuid (report_t report, const char *uuid)
{
  gchar *quoted_uuid = sql_quote (uuid);
  sql ("UPDATE reports SET slave_task_uuid = '%s' WHERE id = %llu;",
       quoted_uuid,
       report);
  g_free (quoted_uuid);
}

/**
 * @brief Prepare a partial report for restarting the scan from the beginning.
 *
 * @param[in]  report  The report.
 */
void
trim_report (report_t report)
{
  /* Remove results for all hosts. */

  sql ("DELETE FROM results WHERE id IN"
       " (SELECT results.id FROM results"
       "  WHERE results.report = %llu);",
       report);

  /* Remove all hosts and host details. */

  sql ("DELETE FROM report_host_details WHERE report_host IN"
       " (SELECT id FROM report_hosts WHERE report = %llu);",
       report);
  sql ("DELETE FROM report_hosts"
       " WHERE report = %llu;",
       report);

  /* Clear and rebuild counts cache */
  sql ("DELETE FROM report_counts WHERE report = %llu;",
       current_report);
  report_cache_counts (current_report);
}

/**
 * @brief Prepare a partial report for resumption of the scan.
 *
 * @param[in]  report  The report.
 */
void
trim_partial_report (report_t report)
{
  /* Remove results for partial hosts. */

  sql ("DELETE FROM results WHERE id IN"
       " (SELECT results.id FROM results, report_hosts"
       "  WHERE results.report = %llu"
       "  AND report_hosts.report = %llu"
       "  AND results.host = report_hosts.host"
       "  AND report_hosts.end_time = 0);",
       report,
       report);

  /* Remove partial hosts and host details. */

  sql ("DELETE FROM report_host_details WHERE report_host IN"
       " (SELECT report_hosts.id FROM report_hosts"
       "  WHERE report_hosts.report = %llu"
       "  AND report_hosts.end_time = 0);",
       report);

  sql ("DELETE FROM report_hosts"
       " WHERE report = %llu"
       " AND end_time is NULL;",
       report);

  /* Clear and rebuild counts cache */
  sql ("DELETE FROM report_counts WHERE report = %llu;",
       report);
  report_cache_counts (report);
}

/**
 * @brief Compares two textual port representations, sorting descending
 * @brief by severity
 *
 * @param[in]  arg_one  First threat level.
 * @param[in]  arg_two  Second threat level.
 *
 * @return 1, 0 or -1 if first given severity is less than, equal to or greater
 *         than second.
 */
static gint
compare_severity_desc (gconstpointer arg_one, gconstpointer arg_two)
{
  double one_severity, two_severity;
  gchar *one = *((gchar**) arg_one);
  gchar *two = *((gchar**) arg_two);
  gint host;

  one += strlen (one) + 1;
  two += strlen (two) + 1;
  one_severity = g_strtod (one, NULL);
  two_severity = g_strtod (two, NULL);

  one += strlen (one) + 1;
  two += strlen (two) + 1;
  host = strcmp (one, two);
  if (host == 0)
    {
      if (one_severity > two_severity)
        return -1;
      else if (one_severity < two_severity)
        return 1;
      else
        {
          one = *((gchar**) arg_one);
          two = *((gchar**) arg_two);
          return strcmp (two, one);
        }
    }
  return host;
}

/**
 * @brief Compares two textual port representations, sorting descending
 * @brief by severity
 *
 * @param[in]  arg_one  First port.
 * @param[in]  arg_two  Second port.
 *
 * @return -1, 0 or 1 if first given severity is less than, equal to or greater
 *         than second.
 */
static gint
compare_severity_asc (gconstpointer arg_one, gconstpointer arg_two)
{
  double one_severity, two_severity;
  gchar *one = *((gchar**) arg_one);
  gchar *two = *((gchar**) arg_two);
  gint host;

  one += strlen (one) + 1;
  two += strlen (two) + 1;
  one_severity = g_strtod (one, NULL);
  two_severity = g_strtod (two, NULL);

  one += strlen (one) + 1;
  two += strlen (two) + 1;
  host = strcmp (one, two);
  if (host == 0)
    {
      if (one_severity < two_severity)
        return -1;
      else if (one_severity > two_severity)
        return 1;
      else
        {
          one = *((gchar**) arg_one);
          two = *((gchar**) arg_two);
          return strcmp (one, two);
        }
    }
  return host;
}

/**
 * @brief Compares two buffered results, sorting by port then severity.
 *
 * @param[in]  arg_one  First result.
 * @param[in]  arg_two  Second result.
 *
 * @return -1, 0 or 1 if first given result is less than, equal to or greater
 *         than second.
 */
static gint
compare_port_severity (gconstpointer arg_one, gconstpointer arg_two)
{
  int host;
  gchar *one = *((gchar**) arg_one);
  gchar *two = *((gchar**) arg_two);
  gchar *one_severity = one + strlen (one) + 1;
  gchar *two_severity = two + strlen (two) + 1;
  double severity_cmp = (g_strtod (two_severity, NULL)
                         - g_strtod (one_severity, NULL));

  host = strcmp (one_severity + strlen (one_severity) + 1,
                 two_severity + strlen (two_severity) + 1);
  if (host == 0)
    {
      int port = strcmp (one, two);
      if (port != 0)
        return port;
      else if (severity_cmp > 0)
        return 1;
      else if (severity_cmp < 0)
        return -1;
      else
        return 0;
    }
  return host;
}

/** @todo Defined in omp.c! */
void buffer_results_xml (GString *, iterator_t *, task_t, int, int, int, int,
                         int, int, int, const char *, iterator_t *, int);

/**
 * @brief Comparison returns.
 */
typedef enum
{
  COMPARE_RESULTS_CHANGED,
  COMPARE_RESULTS_ERROR,
  COMPARE_RESULTS_GONE,
  COMPARE_RESULTS_NEW,
  COMPARE_RESULTS_SAME
} compare_results_t;

/**
 * @brief Return the sort order of two results.
 *
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 *
 * @return < 0 if first comes before second, 0 if equal, > 0 if first comes
 *         after second.
 */
static compare_results_t
result_cmp (iterator_t *results, iterator_t *delta_results, int sort_order,
            const char* sort_field)
{
  const char *host, *delta_host, *port, *delta_port, *type, *delta_type;
  const char *nvt, *delta_nvt;
  int ret;
  double severity, delta_severity;

  if (sort_field == NULL) sort_field = "type";

  host = result_iterator_host (results);
  delta_host = result_iterator_host (delta_results);

  port = result_iterator_port (results);
  delta_port = result_iterator_port (delta_results);

  type = result_iterator_type (results);
  delta_type = result_iterator_type (delta_results);

  severity = result_iterator_severity_double (results);
  delta_severity = result_iterator_severity_double (delta_results);

  nvt = result_iterator_nvt_oid (results);
  delta_nvt = result_iterator_nvt_oid (delta_results);

  if (sort_order == 0)
    {
      /* Descending. */

      tracef ("   delta: %s: descending", __FUNCTION__);

      if (strcmp (sort_field, "ROWID") == 0)
        return result_iterator_result (results)
                < result_iterator_result (delta_results);

      ret = collate_ip (NULL, strlen (host), host, strlen (delta_host), delta_host);
      tracef ("   delta: %s: host: %s VS %s (%i)",
              __FUNCTION__, host, delta_host, ret);
      if (ret)
        return ret;

      if (strcmp (sort_field, "port") == 0)
        {
          /* Sorting port first. */

          tracef ("   delta: %s: port first", __FUNCTION__);

          ret = strcmp (port, delta_port);
          tracef ("   delta: %s: port: %s VS %s (%i)",
                  __FUNCTION__, port, delta_port, ret);
          if (ret)
            return -ret;

          tracef ("   delta: %s: severity: %e VS %e",
                  __FUNCTION__, severity, delta_severity);
          if (severity >= 0 && delta_severity >= 0)
            {
              if (severity > delta_severity)
                return -1;
              if (severity < delta_severity)
                return 1;
            }

          ret = collate_message_type (NULL,
                                      strlen (type), type,
                                      strlen (delta_type), delta_type);
          tracef ("   delta: %s: threat: %s VS %s (%i)",
                  __FUNCTION__, type, delta_type, ret);
          if (ret)
            return -ret;

          ret = strcmp (nvt, delta_nvt);
          tracef ("   delta: %s: NVT: %s VS %s (%i)",
                  __FUNCTION__, nvt, delta_nvt, ret);
          if (ret)
            return ret;

          return 0;
        }

      /* Sorting severity first. */

      tracef ("   delta: %s: severity first", __FUNCTION__);

      tracef ("   delta: %s: severity: %e VS %e",
              __FUNCTION__, severity, delta_severity);
      if (severity >= 0 && delta_severity >= 0)
        {
          if (severity > delta_severity)
            return -1;
          if (severity < delta_severity)
            return 1;
        }

      ret = collate_message_type (NULL,
                                  strlen (type), type,
                                  strlen (delta_type), delta_type);
      tracef ("   delta: %s: threat: %s VS %s (%i)",
              __FUNCTION__, type, delta_type, ret);
      if (ret)
        return -ret;

      ret = strcmp (port, delta_port);
      tracef ("   delta: %s: port: %s VS %s (%i)",
              __FUNCTION__, port, delta_port, ret);
      if (ret)
        return ret;

      ret = strcmp (nvt, delta_nvt);
      tracef ("   delta: %s: NVT: %s VS %s (%i)",
              __FUNCTION__, nvt, delta_nvt, ret);
      if (ret)
        return ret;

      return 0;
    }

  /* Ascending. */

  tracef ("   delta: %s: ascending", __FUNCTION__);

  if (strcmp (sort_field, "ROWID") == 0)
    return result_iterator_result (results)
            > result_iterator_result (delta_results);

  ret = collate_ip (NULL, strlen (host), host, strlen (delta_host), delta_host);
  if (ret)
    return ret;

  if (strcmp (sort_field, "port") == 0)
    {
      /* Sorting by port. */

      tracef ("   delta: %s: port first", __FUNCTION__);

      ret = strcmp (port, delta_port);
      if (ret)
        return ret;

      tracef ("   delta: %s: severity: %e VS %e",
              __FUNCTION__, severity, delta_severity);
      if (severity >= 0 && delta_severity >= 0)
        {
          if (severity > delta_severity)
            return -1;
          if (severity < delta_severity)
            return 1;
        }

      ret = collate_message_type (NULL,
                                  strlen (type), type,
                                  strlen (delta_type), delta_type);
      if (ret)
        return -ret;

      ret = strcmp (nvt, delta_nvt);
      tracef ("   delta: %s: NVT: %s VS %s (%i)",
              __FUNCTION__, nvt, delta_nvt, ret);
      if (ret)
        return ret;

      return 0;
    }

  /* Sorting by severity */

  tracef ("   delta: %s: severity first", __FUNCTION__);

  tracef ("   delta: %s: severity: %e VS %e",
          __FUNCTION__, severity, delta_severity);
  if (severity >= 0 && delta_severity >= 0)
    {
      if (severity > delta_severity)
        return 1;
      if (severity < delta_severity)
        return -1;
    }

  ret = collate_message_type (NULL,
                              strlen (type), type,
                              strlen (delta_type), delta_type);
  if (ret)
    return ret;

  ret = strcmp (port, delta_port);
  if (ret)
    return ret;

  ret = strcmp (nvt, delta_nvt);
  tracef ("   delta: %s: NVT: %s VS %s (%i)",
          __FUNCTION__, nvt, delta_nvt, ret);
  if (ret)
    return ret;

  return 0;
}

/**
 * @brief Compare two results.
 *
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 *
 * @return Result of comparison.
 */
static compare_results_t
compare_results (iterator_t *results, iterator_t *delta_results, int sort_order,
                 const char* sort_field)
{
  int ret;
  const char *descr, *delta_descr;

  tracef ("   delta: %s", __FUNCTION__);

  ret = result_cmp (results, delta_results, sort_order, sort_field);
  if (ret > 0)
    /* The delta result sorts first, so it is new. */
    return COMPARE_RESULTS_NEW;
  if (ret < 0)
    /* The 'results' result sorts first, so it has gone. */
    return COMPARE_RESULTS_GONE;

  descr = result_iterator_descr (results);
  delta_descr = result_iterator_descr (delta_results);

  tracef ("   delta: %s: descr: %s VS %s (%i)",
          __FUNCTION__, descr, delta_descr, strcmp (descr, delta_descr));

  ret = strcmp (descr, delta_descr);
  if (ret)
    return COMPARE_RESULTS_CHANGED;

  return COMPARE_RESULTS_SAME;
}

/**
 * @brief Compare two results, writing associated XML to a buffer.
 *
 * @param[in]  buffer         Buffer.
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  task           Task associated with report.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 * @param[in]  changed        Whether to include changed results.
 * @param[in]  gone           Whether to include gone results.
 * @param[in]  new            Whether to include new results.
 * @param[in]  same           Whether to include same results.
 * @param[in]  max_results    Value to decrement if result is buffered.
 * @param[in]  first_result   Skip result and decrement if positive.
 * @param[in]  used           0 if used, 1 if skipped.
 * @param[in]  would_use      0 if would use (first_result aside), 1 if skipped.
 *
 * @return Result of comparison.
 */
static compare_results_t
compare_and_buffer_results (GString *buffer, iterator_t *results,
                            iterator_t *delta_results, task_t task, int notes,
                            int notes_details, int overrides,
                            int overrides_details, int sort_order,
                            const char* sort_field, int changed, int gone,
                            int new, int same, int *max_results,
                            int *first_result, int *used, int *would_use)
{
  compare_results_t state;
  state = compare_results (results, delta_results, sort_order, sort_field);
  *used = 0;
  *would_use = 0;
  switch (state)
    {
      case COMPARE_RESULTS_CHANGED:
        if (changed)
          {
            *would_use = 1;
            if (*first_result)
              {
                tracef ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "changed",
                                  delta_results,
                                  1);
          }
        break;

      case COMPARE_RESULTS_GONE:
        if (gone)
          {
            *would_use = 1;
            if (*first_result)
              {
                tracef ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "gone",
                                  delta_results,
                                  0);
          }
        break;

      case COMPARE_RESULTS_NEW:
        if (new)
          {
            *would_use = 1;
            if (*first_result)
              {
                tracef ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  delta_results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "new",
                                  delta_results,
                                  0);
          }
        break;

      case COMPARE_RESULTS_SAME:
        if (same)
          {
            *would_use = 1;
            if (*first_result)
              {
                tracef ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "same",
                                  delta_results,
                                  0);
          }
        break;

      default:
        return COMPARE_RESULTS_ERROR;
    }

  return state;
}

/**
 * @brief Write to a file or close stream and exit.
 *
 * @param[in]   stream    Stream to write to.
 * @param[in]   format    Format specification.
 * @param[in]   args      Arguments.
 */
#define PRINT(stream, format, args...)                                       \
  do                                                                         \
    {                                                                        \
      gchar *msg;                                                            \
      msg = g_markup_printf_escaped (format, ## args);                       \
      if (fprintf (stream, "%s", msg) < 0)                                   \
        {                                                                    \
          g_free (msg);                                                      \
          fclose (stream);                                                   \
          return -1;                                                         \
        }                                                                    \
      g_free (msg);                                                          \
    }                                                                        \
  while (0)

/**
 * @brief Write XML to a file or close stream and return.
 *
 * @param[in]   stream  Stream to write to.
 * @param[in]   xml     XML.
 */
#define PRINT_XML(stream, xml)                                               \
  do                                                                         \
    {                                                                        \
      if (fprintf (stream, "%s", xml) < 0)                                   \
        {                                                                    \
          fclose (stream);                                                   \
          return -1;                                                         \
        }                                                                    \
    }                                                                        \
  while (0)

#if 0
void
dump (GArray *ports)
{
  int index;
  for (index = 0; index < ports->len; index++)
    {
      char *port = g_array_index (ports, char*, index);
      char *threat = port + strlen (port) + 1;
      tracef ("  == %s %s %s", threat + strlen (threat) + 1, port, threat);
    }
}
#endif

/**
 * @brief Add a port to a port tree.
 *
 * @param[in]  ports    The tree.
 * @param[in]  results  Result iterator on result whose port to add.
 */
static void
add_port (GTree *ports, iterator_t *results)
{
  const char *port, *host;
  double *old_severity, *severity;
  GTree *host_ports;

  /* Ensure there's an inner tree for the host. */

  host = result_iterator_host (results);
  host_ports = g_tree_lookup (ports, host);
  if (host_ports == NULL)
    {
      host_ports = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
                                    g_free);
      g_tree_insert (ports, g_strdup (host), host_ports);
    }

  /* Ensure the highest threat is recorded for the port in the inner tree. */

  port = result_iterator_port (results);
  severity = g_malloc (sizeof (double));
  *severity = result_iterator_severity_double (results);

  old_severity = g_tree_lookup (host_ports, port);
  tracef ("   delta: %s: adding %s severity %1.1f on host %s", __FUNCTION__,
          port, *severity, host);
  if (old_severity == NULL)
    g_tree_insert (host_ports, g_strdup (port), severity);
  else if (severity > old_severity)
    {
      *old_severity = *severity;
      g_free (severity);
    }
}

/**
 * @brief Print delta host ports.
 *
 * @param[in]  key     Port.
 * @param[in]  value   Threat.
 * @param[in]  data    Host and stream.
 */
static gboolean
print_host_port (gpointer key, gpointer value, gpointer data)
{
  gpointer *host_and_stream;
  host_and_stream = (gpointer*) data;
  tracef ("   delta: %s: host %s port %s", __FUNCTION__,
          (gchar*) host_and_stream[0], (gchar*) key);
  fprintf ((FILE*) host_and_stream[1],
           "<port>"
           "<host>%s</host>"
           "%s"
           "<severity>%1.1f</severity>"
           "<threat>%s</threat>"
           "</port>",
           (gchar*) host_and_stream[0],
           (gchar*) key,
           *((double*) value),
           severity_to_level (*((double*) value), 0));
  return FALSE;
}

/**
 * @brief Print delta ports.
 *
 * @param[in]  key     Host.
 * @param[in]  value   Port tree.
 * @param[in]  stream  Stream.
 */
static gboolean
print_host_ports (gpointer key, gpointer value, gpointer stream)
{
  gpointer host_and_stream[2];
  host_and_stream[0] = key;
  host_and_stream[1] = stream;
  tracef ("   delta: %s: host %s", __FUNCTION__, (gchar*) key);
  g_tree_foreach ((GTree*) value, print_host_port, host_and_stream);
  return FALSE;
}

/**
 * @brief Add port to ports array.
 *
 * @param[in]  key     Port.
 * @param[in]  value   Threat.
 * @param[in]  ports   Ports array.
 */
static gboolean
array_add_port (gpointer key, gpointer value, gpointer ports)
{
  gpointer *port_threat;
  port_threat = g_malloc (2 * sizeof (gpointer));
  port_threat[0] = key;
  port_threat[1] = value;
  array_add ((array_t*) ports, port_threat);
  return FALSE;
}

/**
 * @brief Print delta ports, in descending order.
 *
 * @param[in]  key     Host.
 * @param[in]  value   Port tree.
 * @param[in]  stream  Stream.
 */
static gboolean
print_host_ports_desc (gpointer key, gpointer value, gpointer stream)
{
  guint index;
  array_t *ports;

  tracef ("   delta: %s: host %s", __FUNCTION__, (gchar*) key);

  /* Convert tree to array. */

  ports = make_array ();
  g_tree_foreach ((GTree*) value, array_add_port, ports);

  /* Print the array backwards. */

  index = ports->len;
  while (index--)
    {
      gpointer *port_threat;
      port_threat = g_ptr_array_index (ports, index);
      fprintf ((FILE*) stream,
               "<port>"
               "<host>%s</host>"
               "%s"
               "<severity>%1.1f</severity>"
               "<threat>%s</threat>"
               "</port>",
               (gchar*) key,
               (gchar*) port_threat[0],
               *((double*) port_threat[1]),
               severity_to_level (*((double*) port_threat[1]), 0));
    }

  array_free (ports);

  return FALSE;
}

/**
 * @brief Compare port severities, ascending.
 *
 * @param[in]  one  First.
 * @param[in]  two  Second.
 */
static gint
compare_ports_severity (gconstpointer one, gconstpointer two)
{
  gpointer *port_threat_one, *port_threat_two;
  port_threat_one = *((gpointer**) one);
  port_threat_two = *((gpointer**) two);
  if (*((double*) port_threat_one[1]) > *((double*) port_threat_two[1]))
    return 1;
  else if (*((double*) port_threat_one[1]) < *((double*) port_threat_two[1]))
    return -1;
  else
    return 0;
}

/**
 * @brief Compare port severities, descending.
 *
 * @param[in]  one  First.
 * @param[in]  two  Second.
 */
static gint
compare_ports_severity_desc (gconstpointer one, gconstpointer two)
{
  gpointer *port_threat_one, *port_threat_two;
  port_threat_one = *((gpointer**) one);
  port_threat_two = *((gpointer**) two);
  if (*((double*) port_threat_one[1]) < *((double*) port_threat_two[1]))
    return 1;
  else if (*((double*) port_threat_one[1]) > *((double*) port_threat_two[1]))
    return -1;
  else
    return 0;
}

/**
 * @brief Print delta ports, ordering by severity.
 *
 * @param[in]  key        Host.
 * @param[in]  value      Port tree.
 * @param[in]  stream     Stream.
 * @param[in]  ascending  Ascending or descending.
 */
static gboolean
print_host_ports_by_severity (gpointer key, gpointer value, gpointer stream,
                              int ascending)
{
  guint index, len;
  array_t *ports;

  tracef ("   delta: %s: host %s", __FUNCTION__, (gchar*) key);

  /* Convert tree to array. */

  ports = make_array ();
  g_tree_foreach ((GTree*) value, array_add_port, ports);

  /* Sort the array. */

  if (ascending)
    g_ptr_array_sort (ports, compare_ports_severity);
  else
    g_ptr_array_sort (ports, compare_ports_severity_desc);

  /* Print the sorted array. */

  index = 0;
  len = ports->len;
  while (index < len)
    {
      gpointer *port_threat;
      port_threat = g_ptr_array_index (ports, index);
      fprintf ((FILE*) stream,
               "<port>"
               "<host>%s</host>"
               "%s"
               "<severity>%1.1f</severity>"
               "<threat>%s</threat>"
               "</port>",
               (gchar*) key,
               (gchar*) port_threat[0],
               *((double*) port_threat[1]),
               severity_to_level (*((double*) port_threat[1]), 0));
      index++;
    }

  array_free (ports);

  return FALSE;
}

/**
 * @brief Print delta ports, ordering by severity descending.
 *
 * @param[in]  key     Host.
 * @param[in]  value   Port tree.
 * @param[in]  stream  Stream.
 */
static gboolean
print_host_ports_by_severity_desc (gpointer key, gpointer value,
                                   gpointer stream)
{
  return print_host_ports_by_severity (key, value, stream, 0);
}

/**
 * @brief Print delta ports, ordering by severity ascending.
 *
 * @param[in]  key     Host.
 * @param[in]  value   Port tree.
 * @param[in]  stream  Stream.
 */
static gboolean
print_host_ports_by_severity_asc (gpointer key, gpointer value,
                                  gpointer stream)
{
  return print_host_ports_by_severity (key, value, stream, 1);
}

/**
 * @brief Free delta host ports.
 *
 * @param[in]  host_ports  Ports.
 * @param[in]  dummy       Dummy.
 */
static gboolean
free_host_ports (GTree *host_ports, gpointer dummy)
{
  g_tree_destroy (host_ports);
  return FALSE;
}

/**
 * @brief Get N'th last report_host given a host.
 *
 * The last report_host is at position 1, the second last at position 2, and
 * so on.
 *
 * @param[in]  host         Host.
 * @param[in]  report_host  Report host.
 * @param[in]  position     Position from end.
 */
static gboolean
host_nthlast_report_host (const char *host, report_host_t *report_host,
                          int position)
{
  gchar *quoted_host;

  assert (current_credentials.uuid);

  if (position == 0)
    position = 1;

  quoted_host = sql_quote (host);
  switch (sql_int64 (report_host,
                     "SELECT id FROM report_hosts WHERE host = '%s'"
                     " AND user_owns ('task',"
                     "                (SELECT reports.task FROM reports"
                     "                 WHERE reports.id"
                     "                       = report_hosts.report))"
                     " AND (SELECT tasks.hidden FROM tasks, reports"
                     "      WHERE reports.task = tasks.id"
                     "      AND reports.id = report_hosts.report)"
                     "     = 0"
                     " AND (SELECT value FROM task_preferences, tasks,"
                     "                        reports"
                     "      WHERE reports.task = tasks.id"
                     "      AND reports.id = report_hosts.report"
                     "      AND task_preferences.task = tasks.id"
                     "      AND task_preferences.name = 'in_assets')"
                     "     = 'yes'"
                     " AND report_hosts.end_time IS NOT NULL"
                     " ORDER BY id DESC LIMIT 1 OFFSET %i;",
                     quoted_host,
                     position - 1))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *report_host = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return TRUE;
        break;
    }

  g_free (quoted_host);
  return FALSE;
}

/**
 * @brief Count host reports.
 *
 * @param[in]  host  Host.
 *
 * @return Report count.
 */
static int
host_report_count (const char *host)
{
  int count;
  gchar *quoted_host;
  assert (current_credentials.uuid);
  quoted_host = sql_quote (host);
  count = sql_int ("SELECT count (*) FROM report_hosts WHERE host = '%s'"
                   "  AND (SELECT reports.owner FROM reports"
                   "       WHERE reports.id = report_hosts.report)"
                   "      = (SELECT id FROM users"
                   "         WHERE users.uuid = '%s')"
                   "  AND (SELECT tasks.hidden FROM tasks, reports"
                   "       WHERE reports.task = tasks.id"
                   "       AND reports.id = report_hosts.report)"
                   "      = 0"
                   "  AND (SELECT reports.scan_run_status FROM reports"
                   "       WHERE reports.id = report_hosts.report)"
                   "      = %u;",
                   quoted_host,
                   current_credentials.uuid,
                   TASK_STATUS_DONE);
  g_free (quoted_host);
  return count;
}

/**
 * @brief Count hosts.
 *
 * @return Host count.
 */
static int
host_count ()
{
  return sql_int ("SELECT count (DISTINCT host) FROM report_hosts"
                  " WHERE host != 'localhost';");
}

/**
 * @brief Count hosts with filtering.
 *
 * @param[in]  levels         Levels.
 * @param[in]  search_phrase  Phrase that host IPs must include.  All hosts
 *                            if NULL or "".
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return Host count.
 */
static int
filtered_host_count (const char *levels, const char *search_phrase,
                     int apply_overrides)
{
  assert (current_credentials.uuid);

  if (levels && strlen (levels))
    {
      int ret;
      GString *levels_sql;
      gchar *severity_sql, *new_severity_sql, *last_report_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup("CASE WHEN results.severity"
                                "          > " G_STRINGIFY (SEVERITY_LOG)
                                " THEN coalesce ((SELECT CAST (cvss_base"
                                "                              AS REAL)"
                                "                 FROM nvts"
                                "                 WHERE nvts.oid"
                                "                         = results.nvt),"
                                "                results.severity)"
                                " ELSE results.severity END");
      else
        severity_sql = g_strdup ("results.severity");

      if (apply_overrides)
        {
          gchar *ov, *owned_clause;

          owned_clause = where_owned_for_get ("override", NULL);

          ov = g_strdup_printf
                ("SELECT overrides.new_severity"
                 " FROM overrides"
                 " WHERE overrides.nvt = results.nvt"
                 " AND %s"
                 " AND ((overrides.end_time = 0)"
                 "      OR (overrides.end_time >= m_now ()))"
                 " AND (overrides.task ="
                 "      (SELECT reports.task FROM reports"
                 "       WHERE reports.id = results.report)"
                 "      OR overrides.task = 0)"
                 " AND (overrides.result = results.id"
                 "      OR overrides.result = 0)"
                 " AND (overrides.hosts is NULL"
                 "      OR overrides.hosts = ''"
                 "      OR hosts_contains (overrides.hosts, results.host))"
                 " AND (overrides.port is NULL"
                 "      OR overrides.port = ''"
                 "      OR overrides.port = results.port)"
                 " AND (severity_matches_ov (%s, overrides.severity))"
                 " ORDER BY overrides.result DESC, overrides.task DESC,"
                 " overrides.port DESC, overrides.severity ASC,"
                 " overrides.creation_time DESC"
                 " LIMIT 1",
                 owned_clause,
                 severity_sql);

          g_free (owned_clause);

          new_severity_sql = g_strdup_printf ("coalesce ((%s),%s)",
                                              ov, severity_sql);

          g_free (ov);
        }
      else
        new_severity_sql = g_strdup_printf ("%s", severity_sql);

      g_free (severity_sql);

      levels_sql = where_levels (levels, new_severity_sql);

      last_report_sql
       = g_strdup_printf (" (SELECT report FROM report_hosts"
                          "  WHERE report_hosts.host = distinct_host"
                          "  AND end_time IS NOT NULL"
                          "  AND report_hosts.report"
                          "      IN (SELECT reports.id FROM reports"
                          "          WHERE user_owns ('task', reports.task))"
                          "  AND (SELECT reports.scan_run_status = %u"
                          "       FROM reports"
                          "       WHERE reports.id = report)"
                          "  AND (SELECT hidden FROM tasks"
                          "       WHERE tasks.id"
                          "             = (SELECT task FROM reports"
                          "                WHERE reports.id = report))"
                          "      = 0"
                          "  AND (SELECT value FROM task_preferences"
                          "       WHERE task_preferences.task"
                          "             = (SELECT task FROM reports"
                          "                WHERE reports.id = report)"
                          "       AND task_preferences.name = 'in_assets')"
                          "      = 'yes'"
                          "  ORDER BY id DESC LIMIT 1)",
                          TASK_STATUS_DONE);

      if (search_phrase && strlen (search_phrase))
        {
          gchar *quoted_search_phrase;

          quoted_search_phrase = sql_quote (search_phrase);
          ret = sql_int
                 ("SELECT"
                  " count (*)"
                  " FROM (SELECT DISTINCT host AS distinct_host"
                  "       FROM report_hosts)"
                  "      AS distinct_host_subquery"
                  /* Search IP. */
                  " WHERE (distinct_host LIKE '%%%s%%%'"
                  /* Search hostname. */
                  "        OR EXISTS"
                  "        (SELECT * FROM report_host_details"
                  "         WHERE report_host"
                  "               = (SELECT id FROM report_hosts"
                  "                  WHERE report = %s"
                  "                  AND host = distinct_host)"
                  "         AND (name = 'hostname'"
                  "              OR name = 'best_os_txt'"
                  "              OR name = 'best_os_cpe' OR name = 'App'"
                  "              OR name = 'ports')"
                  "         AND source_type = 'nvt'"
                  "         AND value LIKE '%%%s%%'))"
                  " AND EXISTS (SELECT results.id, %s AS new_severity"
                  "             FROM results"
                  "             WHERE results.report = %s"
                  "             AND results.host = distinct_host"
                  "             AND qod >= " G_STRINGIFY (MIN_QOD_DEFAULT)
                  "             %s);",
                  quoted_search_phrase,
                  last_report_sql,
                  quoted_search_phrase,
                  last_report_sql,
                  new_severity_sql,
                  levels_sql ? levels_sql->str : "");
          g_free (quoted_search_phrase);
        }
      else
        ret = sql_int
               ("SELECT"
                " count (*)"
                " FROM (SELECT DISTINCT host AS distinct_host"
                "       FROM report_hosts)"
                "      AS distinct_host_subquery"
                " WHERE EXISTS (SELECT results.id, %s AS new_severity"
                "               FROM results"
                "               WHERE results.report = %s"
                "               AND results.host = distinct_host"
                "               AND qod >= " G_STRINGIFY (MIN_QOD_DEFAULT)
                "               %s);",
                new_severity_sql,
                last_report_sql,
                levels_sql ? levels_sql->str : "");

      if (levels_sql)
        g_string_free (levels_sql, TRUE);
      g_free (new_severity_sql);
      g_free (last_report_sql);

      return ret;
    }
  else if (search_phrase && strlen (search_phrase))
    {
      int retn;
      gchar *quoted_search_phrase;

      quoted_search_phrase = sql_quote (search_phrase);
      retn = sql_int ("SELECT count(*) FROM"
                      " (SELECT host"
                      "  FROM report_hosts"
                      "  WHERE report_hosts.report"
                      "        IN (SELECT reports.id FROM reports"
                      "            WHERE user_owns ('task', reports.task))"
                      "  AND (SELECT tasks.hidden FROM tasks, reports"
                      "       WHERE reports.task = tasks.id"
                      "       AND reports.id = report_hosts.report)"
                      "      = 0"
                      "  AND report_hosts.end_time IS NOT NULL"
                      "  GROUP BY host"
                      "  HAVING host LIKE '%%%s%%'"
                      "  OR EXISTS"
                      "  (SELECT * FROM report_host_details"
                      "   WHERE report_hosts.id = report_host"
                      "   AND (name = 'hostname' OR name = 'best_os_txt'"
                      "        OR name = 'best_os_cpe' OR name = 'App'"
                      "        OR name = 'ports')"
                      "   AND source_type = 'nvt'"
                      "   AND value LIKE '%%%s%%')"
                      "  ORDER BY inet (host);",
                      quoted_search_phrase,
                      quoted_search_phrase);
      g_free (quoted_search_phrase);
      return retn;
    }

  return sql_int ("SELECT count(*) FROM"
                  " (SELECT DISTINCT host FROM report_hosts"
                  "  WHERE report_hosts.report"
                  "        IN (SELECT reports.id FROM reports"
                  "            WHERE user_owns ('task', reports.task))"
                  "  AND (SELECT tasks.hidden FROM tasks, reports"
                  "       WHERE reports.task = tasks.id"
                  "       AND reports.id = report_hosts.report)"
                  "      = 0"
                  "  AND report_hosts.end_time IS NOT NULL);");
}

/**
 * @brief Buffer host.
 */
struct buffer_host
{
  gchar *ip;                  ///< IP of host.
  report_host_t report_host;  ///< Report host of host.
};

/**
 * @brief Buffer host type.
 */
typedef struct buffer_host buffer_host_t;

/**
 * @brief Free print_report_xml prognostic host buffer.
 *
 * @param[in]  buffer  The buffer.
 */
static void
free_buffer (array_t *buffer)
{
  guint index;
  /* Free the buffer. */
  index = buffer->len;
  while (index--)
    {
      buffer_host_t *buffer_host;
      buffer_host = (buffer_host_t*) g_ptr_array_index (buffer, index);
      if (buffer_host)
        {
          g_free (buffer_host->ip);
          g_free (buffer_host);
        }
    }
  g_ptr_array_free (buffer, TRUE);
}

/**
 * @brief Get filter term corresponding to report filtering.
 *
 * @param[in]  sort_order  Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "type".
 * @param[in]  result_hosts_only  Whether to show only hosts with results.
 * @param[in]  min_cvss_base      Minimum CVSS base of included results.  All
 *                                results if NULL.
 * @param[in]  min_qod        Minimum QoD of included results.  All
 *                            results if NULL.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in]  delta_states   String describing delta states to include in count
 *                            (for example, "sngc" Same, New, Gone and Changed).
 *                            All levels if NULL.
 * @param[in]  search_phrase      Phrase that results must include.  All results
 *                                if NULL or "".
 * @param[in]  search_phrase_exact  Whether search phrase is exact.
 * @param[in]  autofp             Whether to apply the auto FP filter.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  first_result       The result to start from.  The results are 0
 *                                indexed.
 * @param[in]  max_results        The maximum number of results returned.
 *
 * @return Filter term.
 */
static gchar *
report_filter_term (int sort_order, const char* sort_field,
                    int result_hosts_only,
                    const char *min_cvss_base, const char *min_qod,
                    const char *levels, const char *delta_states,
                    const char *search_phrase, int search_phrase_exact,
                    int autofp, int notes, int overrides,
                    int first_result, int max_results, const char *zone)
{
  return g_strdup_printf ("%s%s%s"
                          "%s%s=%s"
                          " result_hosts_only=%i"
                          " min_cvss_base=%s"
                          " min_qod=%s"
                          " levels=%s"
                          " autofp=%i"
                          " notes=%i"
                          " overrides=%i"
                          " first=%i"
                          " rows=%i"
                          " delta_states=%s"
                          "%s%s%s",
                          (search_phrase
                           && strlen (search_phrase)
                           && search_phrase_exact)
                            ? "=" : "",
                          search_phrase && strlen (search_phrase) ? "\"" : "",
                          search_phrase ? search_phrase : "",
                          search_phrase && strlen (search_phrase) ? "\" " : "",
                          sort_order ? "sort" : "sort-reverse",
                          sort_field ? sort_field : "id",
                          result_hosts_only,
                          min_cvss_base ? min_cvss_base : "",
                          min_qod ? min_qod : "",
                          levels ? levels : "hmlgd",
                          autofp,
                          notes,
                          overrides,
                          first_result + 1,
                          max_results,
                          delta_states ? delta_states : "cgns",
                          (zone && strlen (zone)) ? " timezone=\"" : "",
                          (zone && strlen (zone)) ? zone : "",
                          (zone && strlen (zone)) ? "\"" : "");
}

/**
 * @brief Count a report's total number of hosts.
 *
 * @return Host count.
 */
static int
report_host_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT id) FROM report_hosts"
                  " WHERE report = %llu;",
                  report);
}

/**
 * @brief Count a report's total number of tcp/ip ports.
 * @brief Ignores ports entries in "general/..." form.
 *
 * @return Ports count.
 */
static int
report_port_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT port) FROM results"
                  " WHERE report = %llu AND port != ''"
                  "  AND port NOT LIKE 'general/%';",
                  report);
}

/**
 * @brief Count a prognostic report host's total number of tcp/ip ports.
 * @brief Ignores ports entries in "general/..." form.
 *
 * @return Ports count for prognostic report host.
 */
static int
prognostic_host_port_count (report_t report, const char *host)
{
  return sql_int ("SELECT count (DISTINCT port) FROM results"
                  " WHERE report = %llu AND host = '%s'"
                  "  AND port NOT LIKE 'general/%';",
                  report,
                  host);
}

/**
 * @brief Count a report's total number of closed cves.
 *
 * @return Closed CVE count.
 */
static int
report_closed_cve_count (report_t report)
{
  return sql_int (" SELECT count(id) FROM nvts"
                  " WHERE cve != 'NOCVE'"
                  " AND family IN (" LSC_FAMILY_LIST ")"
                  " AND oid IN"
                  " (SELECT source_name FROM report_host_details"
                  "  WHERE report_host IN "
                  "   (SELECT id FROM report_hosts WHERE report = %llu)"
                  "  AND name = 'EXIT_CODE'"
                  "  AND value = 'EXIT_NOTVULN');",
                  report);
}

/**
 * @brief Count a report's total number of vulnerabilities.
 *
 * @return Vulnerabilities count.
 */
static int
report_vuln_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT nvt) FROM results"
                  " WHERE report = %llu AND nvt != '0'"
                  " AND severity != " G_STRINGIFY (SEVERITY_ERROR) ";",
                  report);
}

/**
 * @brief Count a report's total number of detected Operating Systems.
 *
 * @return OS count.
 */
static int
report_os_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT value) FROM report_host_details"
                  " WHERE report_host IN"
                  "  (SELECT id from report_hosts WHERE report = %llu)"
                  "  AND name = 'best_os_cpe';",
                  report);
}

/**
 * @brief Count a report's total number of detected Apps.
 *
 * @return App count.
 */
static int
report_app_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT value) FROM report_host_details"
                  " WHERE report_host IN"
                  "  (SELECT id from report_hosts WHERE report = %llu)"
                  "  AND name = 'App';",
                  report);
}

/**
 * @brief Count a report's total number of found SSL Certificates.
 *
 * @return SSL Certificates count.
 */
static int
report_ssl_cert_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT id) FROM report_host_details"
                  " WHERE report_host IN"
                  "  (SELECT id from report_hosts WHERE report = %llu)"
                  "  AND name = 'SSLInfo';",
                  report);
}

/**
 * @brief Count a report's total number of error messages.
 *
 * @return Error Messages count.
 */
static int
report_error_count (report_t report)
{
  return sql_int ("SELECT count (id) FROM results"
                  " WHERE report = %llu and type = 'Error Message';",
                  report);
}

/**
 * @brief Write report host detail to file stream.
 *
 * @param[in]   stream    Stream to write to.
 * @param[in]   details   Pointer to report host details iterator.
 */
#define PRINT_REPORT_HOST_DETAIL(stream, details)                          \
  do                                                                       \
    {                                                                      \
      PRINT(stream,                                                        \
            "<detail>"                                                     \
            "<name>%s</name>"                                              \
            "<value>%s</value>"                                            \
            "<source>"                                                     \
            "<type>%s</type>"                                              \
            "<name>%s</name>"                                              \
            "<description>%s</description>"                                \
            "</source>"                                                    \
            "<extra>%s</extra>"                                            \
            "</detail>",                                                   \
            report_host_details_iterator_name (details),                   \
            report_host_details_iterator_value (details),                  \
            report_host_details_iterator_source_type (details),            \
            report_host_details_iterator_source_name (details),            \
            report_host_details_iterator_source_desc (details),            \
            report_host_details_iterator_extra (details) ?                 \
             report_host_details_iterator_extra (details)                  \
             : "");                                                        \
    }                                                                      \
  while (0)

/**
 * @brief Print the XML for a report's host details to a file stream.
 * @param[in]  report_host  The report host.
 * @param[in]  stream       File stream to write to.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_host_details_xml (report_host_t report_host, FILE *stream)
{
  iterator_t details;

  init_report_host_details_iterator
   (&details, report_host);
  while (next (&details))
    PRINT_REPORT_HOST_DETAIL (stream, &details);
  cleanup_iterator (&details);

  return 0;
}

/**
 * @brief Write report error message to file stream.
 *
 * @param[in]   stream      Stream to write to.
 * @param[in]   errors      Pointer to report error messages iterator.
 */
#define PRINT_REPORT_ERROR(stream, errors)                                 \
  do                                                                       \
    {                                                                      \
      PRINT (stream,                                                       \
             "<error>"                                                     \
             "<host>%s</host>"                                             \
             "<port>%s</port>"                                             \
             "<description>%s</description>"                               \
             "<nvt oid=\"%s\">"                                            \
             "<name>%s</name>"                                             \
             "<cvss_base>%s</cvss_base>"                                   \
             "</nvt>"                                                      \
             "<scan_nvt_version>%s</scan_nvt_version>"                     \
             "<severity>%s</severity>"                                     \
             "</error>",                                                   \
             report_errors_iterator_host (errors),                         \
             report_errors_iterator_port (errors),                         \
             report_errors_iterator_desc (errors),                         \
             report_errors_iterator_nvt_oid (errors),                      \
             report_errors_iterator_nvt_name (errors),                     \
             report_errors_iterator_nvt_cvss (errors),                     \
             report_errors_iterator_scan_nvt_version (errors),             \
             report_errors_iterator_severity (errors));                    \
    }                                                                      \
  while (0)

/**
 * @brief Print the XML for a report's error messages to a file stream.
 * @param[in]  report   The report.
 * @param[in]  stream   File stream to write to.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_errors_xml (report_t report, FILE *stream)
{
  iterator_t errors;

  init_report_errors_iterator
   (&errors, report);

  PRINT (stream, "<errors><count>%i</count>", report_error_count (report));
  while (next (&errors))
    PRINT_REPORT_ERROR (stream, &errors);
  cleanup_iterator (&errors);
  PRINT (stream, "</errors>");

  return 0;
}

/**
 * @brief Print the XML for a report of type assets.
 * @param[in]  out              File stream to write to.
 * @param[in]  host             Host or NULL.
 * @param[in]  first_result     The result to start from. The results are 0
 *                              indexed.
 * @param[in]  max_results      The maximum number of results returned.
 * @param[in]  levels           String describing threat levels (message types)
 * @param[in]  search_phrase    Phrase that results must include.
 * @param[in]  pos              Position of report from end.
 * @param[in]  get              GET command data.
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  autofp           Whether to apply the auto FP filter.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_assets_xml (FILE *out, const char *host, int first_result, int
                         max_results, gchar *levels, gchar *search_phrase,
                         int pos, const get_data_t *get, int apply_overrides,
                         int autofp)
{
  iterator_t hosts;

  if (host)
    {
      PRINT (out,
             "<host_count>"
             "<full>1</full>"
             "<filtered>1</filtered>"
             "</host_count>"
             "<hosts start=\"1\" max=\"1\"/>");
    }
  else
    {
      // TODO Slow (about 30% of assets page).
      init_asset_iterator (&hosts, first_result, max_results, levels,
                           search_phrase, apply_overrides);
      PRINT (out,
             "<host_count>"
             "<full>%i</full>"
             "<filtered>%i</filtered>"
             "</host_count>",
             host_count (),
             // TODO Slow (about 30% of assets page).
             filtered_host_count (levels, search_phrase, apply_overrides));
      PRINT (out,
             "<hosts start=\"%i\" max=\"%i\"/>",
             /* Add 1 for 1 indexing. */
             first_result + 1,
             max_results);
    }

  while (host || next (&hosts))
    {
      report_host_t report_host;
      const char *ip;

      ip = host ? host : asset_iterator_ip (&hosts);

      if (host_nthlast_report_host (ip, &report_host, pos))
        {
          if (host == NULL)
            cleanup_iterator (&hosts);

          return -1;
        }

      if (report_host)
        {
          iterator_t report_hosts;
          init_host_iterator (&report_hosts, 0, NULL, report_host);
          if (next (&report_hosts))
            {
              iterator_t details;
              report_t report;
              int holes, infos, logs, warnings, false_positives;
              double severity, highest_cvss;

              PRINT (out,
                     "<host>"
                     "<ip>%s</ip>"
                     "<start>%s</start>"
                     "<end>%s</end>",
                     ip,
                     host_iterator_start_time (&report_hosts),
                     host_iterator_end_time (&report_hosts));

              PRINT (out,
                     "<detail>"
                     "<name>report/@id</name>"
                     "<value>%s</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>UUID of current report</description>"
                     "</source>"
                     "</detail>",
                     host_iterator_report_uuid (&report_hosts));

              PRINT (out,
                     "<detail>"
                     "<name>report_count</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Number of reports</description>"
                     "</source>"
                     "</detail>",
                     host_report_count (ip));

              report = host_iterator_report (&report_hosts);

              report_counts_id (report, NULL, &holes, &infos, &logs,
                                &warnings, &false_positives, &severity,
                                apply_overrides, ip, autofp,
                                G_STRINGIFY (MIN_QOD_DEFAULT));

              PRINT (out,
                     "<detail>"
                     "<name>report/result_count/high</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Number of highs</description>"
                     "</source>"
                     "</detail>",
                     holes);

              PRINT (out,
                     "<detail>"
                     "<name>report/result_count/medium</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Number of mediums</description>"
                     "</source>"
                     "</detail>",
                     warnings);

              PRINT (out,
                     "<detail>"
                     "<name>report/result_count/low</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Number of lows</description>"
                     "</source>"
                     "</detail>",
                     infos);

              PRINT (out,
                     "<detail>"
                     "<name>report/result_count/log</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Number of logs</description>"
                     "</source>"
                     "</detail>",
                     logs);
              /* Print all the host details. */

              highest_cvss = -1;
              init_report_host_details_iterator
               (&details, report_host);
              while (next (&details))
                {
                  const char *value;
                  value = report_host_details_iterator_value (&details);

                  PRINT_REPORT_HOST_DETAIL (out, &details);

                  if (manage_scap_loaded ()
                      && get->details
                      && (strcmp (report_host_details_iterator_name
                                   (&details),
                                  "App")
                          == 0))
                    {
                      iterator_t prognosis;
                      double cvss;
                      int first;

                      /* Print details of all CVEs on the App. */

                      first = 1;
                      cvss = -1;
                      init_prognosis_iterator (&prognosis, value);
                      while (next (&prognosis))
                        {
                          if (first)
                            {
                              cvss = prognosis_iterator_cvss_double
                                      (&prognosis);
                              first = 0;
                            }

                          PRINT (out,
                                 "<detail>"
                                 "<name>%s/CVE</name>"
                                 "<value>%s</value>"
                                 "</detail>"
                                 "<detail>"
                                 "<name>%s/%s/CVSS</name>"
                                 "<value>%s</value>"
                                 "</detail>",
                                 value,
                                 prognosis_iterator_cve (&prognosis),
                                 value,
                                 prognosis_iterator_cve (&prognosis),
                                 prognosis_iterator_cvss (&prognosis));
                        }

                      /* Print App prognosis, according to highest CVSS. */

                      if (cvss >= 0)
                        PRINT (out,
                               "<detail>"
                               "<name>%s/threat</name>"
                               "<value>%s</value>"
                               "</detail>",
                               value,
                               cvss_threat (cvss));
                      cleanup_iterator (&prognosis);
                    }

                  if (manage_scap_loaded ()
                      && (strcmp (report_host_details_iterator_name
                                   (&details),
                                  "App")
                          == 0))
                    {
                      int highest;
                      /* Check if this App's CVSS is the highest CVSS for
                       * this host. */
                      highest = cpe_highest_cvss (value);
                      if (highest > highest_cvss)
                        highest_cvss = highest;
                    }
                }
              cleanup_iterator (&details);

              /* Print prognosis of host, according to highest CVSS. */

              if (highest_cvss >= 0)
                PRINT (out,
                       "<detail>"
                       "<name>prognosis</name>"
                       "<value>%s</value>"
                       "</detail>",
                       cvss_threat (highest_cvss));

              PRINT (out,
                     "<detail>"
                     "<name>report/pos</name>"
                     "<value>%i</value>"
                     "<source>"
                     "<type></type>"
                     "<name>openvasmd</name>"
                     "<description>Position of report from end</description>"
                     "</source>"
                     "</detail>",
                     pos);
            }
          cleanup_iterator (&report_hosts);

          PRINT (out,
                 "</host>");
        }

      if (host)
        break;
    }
  if (host == NULL)
    cleanup_iterator (&hosts);

  return 0;
}

/**
 * @brief Print the XML for a report port summary to a file.
 *
 * @param[in]  report           The report.
 * @param[in]  out              File stream.
 * @param[in]  first_result     The result to start from.  The results are 0
 *                              indexed.
 * @param[in]  max_results      The maximum number of results returned.
 * @param[in]  sort_order       Whether to sort ascending or descending.
 * @param[in]  sort_field       Field to sort on, or NULL for "type".
 * @param[in]  levels           String describing threat levels (message types)
 * @param[in]  autofp           Whether to apply the auto FP filter.
 * @param[in]  search_phrase    Phrase that results must include.  All
 *                              results if NULL or "".
 * @param[in]  search_phrase_exact    Whether search phrase is exact.
 * @param[in]  min_cvss_base    Minimum CVSS base of included results. All
 *                              results if NULL.
 * @param[in]  min_qod          Minimum QoD of included results. All
 *                              results if NULL.
 * @param[in]  apply_overrides    Whether to apply overrides.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_port_xml (report_t report, FILE *out, int first_result,
                       int max_results, int sort_order, const char *sort_field,
                       const char *levels, int autofp,
                       const char *search_phrase, int search_phrase_exact,
                       const char *min_cvss_base, const char *min_qod,
                       int apply_overrides)
{
  iterator_t results;
  gchar *last_port, *last_host;
  GArray *ports = g_array_new (TRUE, FALSE, sizeof (gchar*));

  init_result_iterator
   (&results, report, 0,
    first_result,
    max_results,
    /* Sort by the requested field in the requested order, in case there is
     * a first_result and/or max_results (these are applied after the
     * sorting). */
    sort_order,
    sort_field,
    levels,
    autofp,
    search_phrase,
    search_phrase_exact,
    min_cvss_base,
    min_qod,
    apply_overrides);

  /* Buffer the results, removing duplicates. */

  last_port = NULL;
  last_host = NULL;
  while (next (&results))
    {
      const char *port = result_iterator_port (&results);
      const char *host = result_iterator_host (&results);

      if (last_port == NULL || strcmp (port, last_port)
          || strcmp (host, last_host))
        {
          const char *cvss;
          gchar *item;
          int port_len, cvss_len;

          g_free (last_port);
          last_port = g_strdup (port);
          g_free (last_host);
          last_host = g_strdup (host);

          cvss = result_iterator_severity (&results);
          if (cvss == NULL)
            cvss = "0.0";
          port_len = strlen (port);
          cvss_len = strlen (cvss);
          item = g_malloc (port_len
                            + cvss_len
                            + strlen (host)
                            + 3);
          g_array_append_val (ports, item);
          strcpy (item, port);
          strcpy (item + port_len + 1, cvss);
          strcpy (item + port_len + cvss_len + 2, host);
        }

    }
  g_free (last_port);
  g_free (last_host);

  /* Handle sorting by threat and ROWID. */

  if (sort_field == NULL || strcmp (sort_field, "port"))
    {
      int index, length;

      /** @todo Sort by ROWID if was requested. */

      /* Sort by port then severity. */

      g_array_sort (ports, compare_port_severity);

      /* Remove duplicates. */

      last_port = NULL;
      last_host = NULL;
      for (index = 0, length = ports->len; index < length; index++)
        {
          char *port = g_array_index (ports, char*, index);
          char *host = port + strlen (port) + 1;
          host += strlen (host) + 1;
          if (last_port
              && (strcmp (port, last_port) == 0)
              && (strcmp (host, last_host) == 0))
            {
              g_array_remove_index (ports, index);
              length = ports->len;
              index--;
            }
          else
            {
              last_port = port;
              last_host = host;
            }
        }

      /* Sort by severity. */

      if (sort_order)
        g_array_sort (ports, compare_severity_asc);
      else
        g_array_sort (ports, compare_severity_desc);
    }

  /* Write to file from the buffer. */

  PRINT (out,
           "<ports"
           " start=\"%i\""
           " max=\"%i\">"
           "<count>%i</count>",
           /* Add 1 for 1 indexing. */
           first_result + 1,
           max_results,
           report_port_count (report));
  {
    gchar *item;
    int index = 0;

    while ((item = g_array_index (ports, gchar*, index++)))
      {
        int port_len = strlen (item);
        int cvss_len = strlen (item + port_len + 1);
        PRINT (out,
               "<port>"
               "<host>%s</host>"
               "%s"
               "<severity>%s</severity>"
               "<threat>%s</threat>"
               "</port>",
               item + port_len + cvss_len + 2,
               item,
               item + port_len + 1,
               severity_to_level (g_strtod (item + port_len + 1, NULL), 0));
        g_free (item);
      }
    g_array_free (ports, TRUE);
  }
  PRINT (out, "</ports>");
  cleanup_iterator (&results);

  return 0;
}

/**
 * @brief Print the XML for a report of type assets.
 *
 * @param[in]  out              File stream to write to.
 * @param[in]  host             Host or NULL.
 * @param[in]  first_result     The result to start from. The results are 0
 *                              indexed.
 * @param[in]  max_results      The maximum number of results returned.
 * @param[in]  levels           String describing threat levels (message types)
 * @param[in]  search_phrase    Phrase that results must include.
 * @param[in]  pos              Position of report from end.
 * @param[in]  get              GET command data.
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  autofp           Whether to apply the auto FP filter.
 * @param[in]  host_search_phrase  Phrase that results must include.  All results
 *                                 if NULL or "".  For hosts.
 * @param[in]  host_levels         String describing threat levels (message types)
 *                                 to include in count (for example, "hmlgd" for
 *                                 High, Medium, Low, loG and Debug).  All levels if
 *                                 NULL.
 * @param[in]  host_first_result   The host result to start from.  The results
 *                                 are 0 indexed.
 * @param[in]  host_max_results    The host maximum number of results returned.
 * @param[in]  min_cvss_base       Minimum CVSS base of included results.  All
 *                                 results if NULL.
 * @param[in]  result_hosts_only   Whether to show only hosts with results.
 * @param[in]  sort_order          Whether to sort in ascending order.
 * @param[in]  sort_field          Name of the field to sort by.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_prognostic_xml (FILE *out, const char *host, int first_result, int
                             max_results, gchar *levels, gchar *search_phrase,
                             int pos, const get_data_t *get,
                             int apply_overrides, int autofp,
                             const char *host_search_phrase,
                             const char *host_levels, int host_first_result,
                             int host_max_results, gchar *min_cvss_base,
                             int result_hosts_only, int sort_order,
                             const char *sort_field)
{
  array_t *buffer, *apps;
  buffer_host_t *buffer_host;
  int index, skip, result_total, total_host_count;
  int holes, infos, logs, warnings;
  int f_holes, f_infos, f_logs, f_warnings;
  iterator_t hosts;

  if (host == NULL)
    {
      host_levels = host_levels ? host_levels : "hmlgd";

      init_asset_iterator (&hosts, host_first_result, host_max_results,
                           host_levels, host_search_phrase, apply_overrides);
    }

  buffer = make_array ();
  apps = make_array ();
  holes = warnings = infos = logs = 0;
  f_holes = f_warnings = f_infos = f_logs = 0;
  skip = 0;
  total_host_count = 0;
  result_total = 0;

  /* Output the results, buffering the associated hosts. */

  PRINT (out,
         "<results start=\"%i\" max=\"%i\">",
         /* Add 1 for 1 indexing. */
         first_result + 1,
         max_results);

  while (host || next (&hosts))
    {
      iterator_t report_hosts;
      report_host_t report_host;
      const char *ip;

      ip = host ? host : asset_iterator_ip (&hosts);

      if (host_nthlast_report_host (ip, &report_host, pos))
        {
          if (host == NULL)
            cleanup_iterator (&hosts);
          free_buffer (buffer);
          array_free (apps);
          return -1;
        }

      if (report_host)
        {
          int filtered, host_result_total;
          filtered = 0;
          host_result_total = 0;

          total_host_count++;

          prognostic_report_result_total (report_host, &host_result_total);
          result_total += host_result_total;

          prognostic_report_result_count (report_host, search_phrase,
                                          min_cvss_base,
                                          &filtered, &f_holes,
                                          &f_infos, &f_warnings);
          filtered = (strchr (levels, 'h') ? f_holes : 0)
                      + (strchr (levels, 'l') ? f_infos : 0)
                      + (strchr (levels, 'g') ? f_logs : 0)
                      + (strchr (levels, 'm') ? f_warnings : 0);
          if (!filtered)
            /* Skip this host. */
            report_host = 0;
        }

      if (report_host)
        {
          init_host_iterator (&report_hosts, 0, NULL, report_host);
          if (next (&report_hosts))
            {
              iterator_t prognosis;
              int buffered;

              buffered = 0;

              init_host_prognosis_iterator (&prognosis, report_host,
                                            0, -1,
                                            levels, search_phrase,
                                            min_cvss_base,
                                            sort_order, sort_field);
              while (next (&prognosis))
                {
                  const char *threat;

                  threat = cvss_threat (prognosis_iterator_cvss_double
                                         (&prognosis));

                  array_add_new_string (apps,
                                        prognosis_iterator_cpe (&prognosis));

                  if (skip < first_result)
                    {
                      /* Skip result. */
                      skip++;
                      continue;
                    }

                   if (max_results == 0)
                     continue;

                   buffered = 1;

                   PRINT (out,
                          "<result>"
                          "<host>%s</host>"
                          "<port>0</port>"
                          "<nvt oid=\"0\">"
                          "<name/>"
                          "</nvt>"
                          "<threat>%s</threat>"
                          "<description>"
                          "The host carries the product: %s\n"
                          "It is vulnerable according to: %s.\n"
                          "\n"
                          "%s"
                          "</description>"
                          "<cve id='%s'>"
                          "<cvss_base>%s</cvss_base>"
                          "<cpe id='%s'/>"
                          "</cve>"
                          "</result>",
                          ip,
                          threat,
                          prognosis_iterator_cpe (&prognosis),
                          prognosis_iterator_cve (&prognosis),
                          prognosis_iterator_description
                           (&prognosis),
                          prognosis_iterator_cve (&prognosis),
                          prognosis_iterator_cvss (&prognosis),
                          prognosis_iterator_cpe (&prognosis));

                   max_results--;
                 }
               if (buffered || (result_hosts_only == 0))
                 {
                   /* Buffer IP and report_host. */
                   buffer_host_t *buffer_host;
                   buffer_host = (buffer_host_t*) g_malloc (sizeof (buffer_host_t));
                   buffer_host->report_host = report_host;
                   buffer_host->ip = g_strdup (ip);
                   array_add (buffer, buffer_host);
                 }
              cleanup_iterator (&prognosis);
            }
          cleanup_iterator (&report_hosts);
        }

      if (host)
        break;
    }
  if (host == NULL)
    cleanup_iterator (&hosts);

  PRINT (out,
         "</results>");

  /* Output buffered hosts. */

  if (host)
    {
      PRINT (out,
             "<host_count>"
             "<full>1</full>"
             "<filtered>1</filtered>"
             "</host_count>"
             "<hosts start=\"1\" max=\"1\"/>");
    }
  else
    {
      PRINT (out,
             "<host_count>"
             "<full>%i</full>"
             "<filtered>%i</filtered>"
             "</host_count>",
             host_count (),
             buffer->len);
      PRINT (out,
             "<hosts start=\"%i\" max=\"%i\"/>",
             /* Add 1 for 1 indexing. */
             host_first_result + 1,
             host_max_results);
    }

  array_terminate (buffer);
  index = 0;
  while ((buffer_host = g_ptr_array_index (buffer, index++)))
    {
      iterator_t report_hosts;
      init_host_iterator (&report_hosts, 0, NULL, buffer_host->report_host);
      if (next (&report_hosts))
        {
          report_t report;
          int h_holes, h_infos, h_logs, h_warnings, h_false_positives;
          double h_severity;

          PRINT (out,
                 "<host>"
                 "<ip>%s</ip>"
                 "<start>%s</start>"
                 "<end>%s</end>",
                 buffer_host->ip,
                 host_iterator_start_time (&report_hosts),
                 host_iterator_end_time (&report_hosts));

          PRINT (out,
                 "<detail>"
                 "<name>report/@id</name>"
                 "<value>%s</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>UUID of current report</description>"
                 "</source>"
                 "</detail>",
                 host_iterator_report_uuid (&report_hosts));

          PRINT (out,
                 "<detail>"
                 "<name>port_count</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Ports number of current host</description>"
                 "</source>"
                 "</detail>",
                 prognostic_host_port_count
                  (host_iterator_report (&report_hosts), buffer_host->ip));

          PRINT (out,
                 "<detail>"
                 "<name>report_count</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Number of reports</description>"
                 "</source>"
                 "</detail>",
                 host_report_count (buffer_host->ip));

          report = host_iterator_report (&report_hosts);

          report_counts_id (report, NULL, &h_holes, &h_infos, &h_logs,
                            &h_warnings, &h_false_positives, &h_severity, 0,
                            buffer_host->ip, 0,
                            G_STRINGIFY (MIN_QOD_DEFAULT));

          PRINT (out,
                 "<detail>"
                 "<name>report/result_count/high</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Number of highs</description>"
                 "</source>"
                 "</detail>",
                 h_holes);

          PRINT (out,
                 "<detail>"
                 "<name>report/result_count/medium</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Number of mediums</description>"
                 "</source>"
                 "</detail>",
                 h_warnings);

          PRINT (out,
                 "<detail>"
                 "<name>report/result_count/low</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Number of lows</description>"
                 "</source>"
                 "</detail>",
                 h_infos);

          if (print_report_host_details_xml
              (buffer_host->report_host, out))
            {
              array_free (apps);
              return -1;
            }

          PRINT (out,
                 "<detail>"
                 "<name>report/pos</name>"
                 "<value>%i</value>"
                 "<source>"
                 "<type></type>"
                 "<name>openvasmd</name>"
                 "<description>Position of report from end</description>"
                 "</source>"
                 "</detail>",
                 pos);

          PRINT (out,
                 "</host>");
        }
      cleanup_iterator (&report_hosts);
    }

  free_buffer (buffer);

  PRINT (out,
         "<result_count>"
         "%i"
         "<full>%i</full>"
         "<filtered>%i</filtered>"
         "<debug><full>0</full><filtered>0</filtered></debug>"
         "<hole><full>%i</full><filtered>%i</filtered></hole>"
         "<info><full>%i</full><filtered>%i</filtered></info>"
         "<log><full>%i</full><filtered>%i</filtered></log>"
         "<warning><full>%i</full><filtered>%i</filtered></warning>"
         "<false_positive>"
         "<full>0</full>"
         "<filtered>0</filtered>"
         "</false_positive>"
         "</result_count>",
         result_total,
         result_total,
         (strchr (levels, 'h') ? f_holes : 0)
          + (strchr (levels, 'l') ? f_infos : 0)
          + (strchr (levels, 'g') ? f_logs : 0)
          + (strchr (levels, 'm') ? f_warnings : 0),
         holes,
         (strchr (levels, 'h') ? f_holes : 0),
         infos,
         (strchr (levels, 'l') ? f_infos : 0),
         logs,
         (strchr (levels, 'g') ? f_logs : 0),
         warnings,
         (strchr (levels, 'm') ? f_warnings : 0));

  PRINT (out,
         "<hosts><count>%i</count></hosts>",
         total_host_count);

  PRINT (out,
         "<apps><count>%i</count></apps>",
         apps->len);

  array_free (apps);

  return 0;
}

/**
 * @brief Check whether the scan of a report is active.
 *
 * @param[in]  report         Report.
 *
 * @return 1 if active, else 0.
 */
static int
report_active (report_t report)
{
  int run_status;
  report_scan_run_status (report, &run_status);
  if (run_status == TASK_STATUS_REQUESTED
      || run_status == TASK_STATUS_RUNNING
      || run_status == TASK_STATUS_DELETE_REQUESTED
      || run_status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED_GIVEUP
      || run_status == TASK_STATUS_STOPPED)
    return 1;
  return 0;
}

/**
 * @brief Get progress for active report.
 *
 * @param[in]  report         Report.
 * @param[in]  maximum_hosts  Maximum number of hosts in target.
 * @param[out] hosts_xml      Return for hosts XML if required, else NULL.
 *
 * @return Progress XML.
 */
static int
report_progress_active (report_t report, int maximum_hosts, gchar **hosts_xml)
{
  long total = 0;
  int num_hosts = 0, total_progress;
  iterator_t hosts;
  GString *string;

  string = g_string_new ("");

  init_host_iterator (&hosts, report, NULL, 0);
  while (next (&hosts))
    {
      unsigned int max_port, current_port;
      long progress;

      max_port = host_iterator_max_port (&hosts);
      current_port = host_iterator_current_port (&hosts);
      if (max_port)
        {
          progress = (current_port * 100) / max_port;
          if (progress < 0) progress = 0;
          else if (progress > 100) progress = 100;
        }
      else
        progress = current_port ? 100 : 0;

      total += progress;
      num_hosts++;

      if (hosts_xml)
        g_string_append_printf (string,
                                "<host_progress>"
                                "<host>%s</host>"
                                "%li"
                                "</host_progress>",
                                host_iterator_host (&hosts),
                                progress);
    }
  cleanup_iterator (&hosts);

  total_progress = maximum_hosts
                   ? (total / maximum_hosts) : 0;

#if 1
  tracef ("   total: %li\n", total);
  tracef ("   num_hosts: %i\n", num_hosts);
  tracef ("   maximum_hosts: %i\n", maximum_hosts);
  tracef ("   total_progress: %i\n", total_progress);
#endif

  if (total_progress == 0) total_progress = 1;
  else if (total_progress == 100) total_progress = 99;

  if (hosts_xml)
    *hosts_xml = g_string_free (string, FALSE);
  else
    g_string_free (string, TRUE);

  return total_progress;
}

/**
 * @brief Calculate the progress of a report.
 *
 * @param[in]  report     Report.
 * @param[in]  task       Report's task.
 * @param[out] hosts_xml  Return for hosts XML if required, else NULL.
 *
 * @return Progress.
 */
int
report_progress (report_t report, task_t task, gchar **hosts_xml)
{
  target_t target;
  char *hosts, *exclude_hosts;
  int maximum_hosts, progress;

  if (report == 0)
    {
      if (hosts_xml)
        *hosts_xml = g_strdup ("");
      return -1;
    }

  /* Handles both slave and OSP cases. */
  if ((progress = report_slave_progress (report)))
    {
      if (hosts_xml)
        *hosts_xml = g_strdup ("");
      return progress;
    }

  target = task_target (task);
  if (task_target_in_trash (task))
    {
      hosts = target ? trash_target_hosts (target) : NULL;
      exclude_hosts = target ? trash_target_exclude_hosts
                                (target) : NULL;
    }
  else
    {
      hosts = target ? target_hosts (target) : NULL;
      exclude_hosts = target ? target_exclude_hosts (target) : NULL;
    }
  maximum_hosts = hosts ? manage_count_hosts (hosts, exclude_hosts) : 0;
  g_free (hosts);
  g_free (exclude_hosts);

  if (report_active (report))
    return report_progress_active (report, maximum_hosts, hosts_xml);

  if (hosts_xml)
    *hosts_xml = g_strdup ("");

  return -1;
}

/**
 * @brief Buffer XML for a severity class.
 *
 * @param[in]  severity  Severity name.
 *
 * @return Freshly allocated XML on success, else NULL.
 */
gchar *
severity_class_xml (const gchar *severity)
{
  if (severity)
    {
      if ((strcmp (severity, "nist") == 0)
          || (strcmp (severity, "bsi") == 0))
        return g_strdup_printf ("<severity_class"
                                " id=\"d4c74cda-89e1-11e3-9c29-406186ea4fc5\">"
                                "<name>nist</name>"
                                "<full_name>%s</full_name>"
                                "<severity_range>"
                                "<name>None</name>"
                                "<min>0.0</min>"
                                "<max>0.0</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>Low</name>"
                                "<min>0.1</min>"
                                "<max>3.9</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>Medium</name>"
                                "<min>4.0</min>"
                                "<max>6.9</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>High</name>"
                                "<min>7.0</min>"
                                "<max>10.0</max>"
                                "</severity_range>"
                                "</severity_class>",
                                strcmp (severity, "nist") == 0
                                 ? "NVD Vulnerability Severity Ratings"
                                 : "BSI Schwachstellenampel (Germany)");
      else if (strcmp (severity, "classic") == 0)
        return g_strdup_printf ("<severity_class"
                                " id=\"dc1d556a-89e1-11e3-bc21-406186ea4fc5\">"
                                "<name>classic</name>"
                                "<full_name>OpenVAS Classic</full_name>"
                                "<severity_range>"
                                "<name>None</name>"
                                "<min>0.0</min>"
                                "<max>0.0</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>Low</name>"
                                "<min>0.1</min>"
                                "<max>2.0</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>Medium</name>"
                                "<min>2.1</min>"
                                "<max>5.0</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>High</name>"
                                "<min>5.1</min>"
                                "<max>10.0</max>"
                                "</severity_range>"
                                "</severity_class>");
      else if (strcmp (severity, "pci-dss") == 0)
        return g_strdup_printf ("<severity_class"
                                " id=\"e442e476-89e1-11e3-bfc6-406186ea4fc5\">"
                                "<name>pci-dss</name>"
                                "<full_name>PCI-DSS</full_name>"
                                "<severity_range>"
                                "<name>None</name>"
                                "<min>0.0</min>"
                                "<max>3.9</max>"
                                "</severity_range>"
                                "<severity_range>"
                                "<name>High</name>"
                                "<min>4.0</min>"
                                "<max>10.0</max>"
                                "</severity_range>"
                                "</severity_class>");
    }
  return NULL;
}

/**
 * @brief Restore original TZ.
 *
 * @param[in]  tz  Original TZ.  Freed here.
 *
 * @return 0 success, -1 error.
 */
static int
tz_revert (gchar *zone, char *tz)
{
  if (zone && strlen (zone))
    {
      /* Revert to stored TZ. */
      if (tz)
        {
          if (setenv ("TZ", tz, 1) == -1)
            {
              g_warning ("%s: Failed to switch to original TZ", __FUNCTION__);
              g_free (tz);
              g_free (zone);
              return -1;
            }
        }
      else
        unsetenv ("TZ");

      g_free (tz);
    }
  g_free (zone);
  return 0;
}

/**
 * @brief Print the XML for a report to a file.
 *
 * @param[in]  host_summary_buffer  Summary.
 * @param[in]  host                 Host.
 * @param[in]  start                Start time, in ISO format.
 * @param[in]  end                  End time, in ISO format.
 */
static void
host_summary_append (GString *host_summary_buffer, const char *host,
                     const char *start_iso, const char *end_iso)
{
  if (host_summary_buffer)
    {
      struct tm start_tm, end_tm;
      char start[200], end[200];

      if (strptime (start_iso, "%FT%H:%M:%S", &start_tm) == NULL)
        {
          g_warning ("%s: Failed to parse start", __FUNCTION__);
          return;
        }

      if (strftime (start, 200, "%b %d, %H:%M:%S", &start_tm) == 0)
        {
          g_warning ("%s: Failed to format start", __FUNCTION__);
          return;
        }

      if (strptime (end_iso, "%FT%H:%M:%S", &end_tm) == NULL)
        {
          g_warning ("%s: Failed to parse end", __FUNCTION__);
          return;
        }

      if (strftime (end, 200, "%b %d, %H:%M:%S", &end_tm) == 0)
        {
          g_warning ("%s: Failed to format end", __FUNCTION__);
          return;
        }

      g_string_append_printf (host_summary_buffer,
                              "   %-15s   %-16s   %s\n",
                              host,
                              start,
                              end);
    }
}

/**
 * @brief Print the XML for a report to a file.
 *
 * @param[in]  report      The report.
 * @param[in]  delta       Report to compare with the report.
 * @param[in]  task        Task associated with report.
 * @param[in]  xml_file    File name.
 * @param[in]  get         GET command data.
 * @param[in]  sort_order  Whether to sort ascending or descending.
 * @param[in]  given_sort_field   Field to sort on, or NULL for "type".
 * @param[in]  result_hosts_only  Whether to show only hosts with results.
 * @param[in]  given_min_cvss_base  Minimum CVSS base of included results.  All
 *                                  results if NULL.
 * @param[in]  given_min_qod  Minimum QoD of included results.  All results
 *                            if NULL.
 * @param[in]  report_format  Format of report that will be created from XML.
 * @param[in]  given_levels   String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in]  given_delta_states  String describing delta states to include in
 *                                 count (for example, "sngc" Same, New, Gone
 *                                 and Changed).  All levels if NULL.
 * @param[in]  given_apply_overrides  Whether to apply overrides.
 * @param[in]  given_search_phrase  Phrase that results must include.  All
 *                                  results if NULL or "".
 * @param[in]  autofp             Whether to apply the auto FP filter.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  first_result       The result to start from.  The results are 0
 *                                indexed.
 * @param[in]  max_results        The maximum number of results returned.
 * @param[in]  type               Type of report, NULL, "scan" or "assets".
 * @param[in]  host               Host or NULL, when type "assets".
 * @param[in]  pos                Position of report from end, when type
 *                                "assets".
 * @param[in]  host_search_phrase  Phrase that results must include.  All results
 *                                 if NULL or "".  For hosts.
 * @param[in]  host_levels         String describing threat levels (message types)
 *                                 to include in count (for example, "hmlgd" for
 *                                 High, Medium, Low, loG and Debug).  All levels if
 *                                 NULL.
 * @param[in]  host_first_result   The host result to start from.  The results
 *                                 are 0 indexed.
 * @param[in]  host_max_results    The host maximum number of results returned.
 * @param[in]  ignore_pagination   Whether to ignore pagination data.
 * @param[in]  given_zone          Timezone.  NULL or "" for user's timezone.
 * @param[out] filter_term_return  Filter term used in report.
 * @param[out] zone_return         Actual zone used in report.
 * @param[out] host_summary    Summary of results per host.
 *
 * @return 0 on success, -1 error, 2 failed to find filter (before any printing).
 */
static int
print_report_xml (report_t report, report_t delta, task_t task, gchar* xml_file,
                  const get_data_t *get,
                  int sort_order, const char *given_sort_field, int result_hosts_only,
                  const char *given_min_cvss_base, const char *given_min_qod,
                  report_format_t report_format,
                  const char *given_levels, const char *given_delta_states,
                  int given_apply_overrides, const char *given_search_phrase,
                  int autofp, int notes, int notes_details, int overrides,
                  int overrides_details, int first_result, int max_results,
                  const char *type, const char *host, int pos,
                  const char *host_search_phrase, const char *host_levels,
                  int host_first_result, int host_max_results,
                  int ignore_pagination, const char *given_zone,
                  gchar **filter_term_return, gchar **zone_return,
                  gchar **host_summary)
{
  FILE *out;
  gchar *clean, *term, *sort_field, *levels, *search_phrase;
  gchar *min_cvss_base, *min_qod;
  gchar *delta_states, *timestamp;
  char *uuid, *tsk_uuid = NULL, *start_time, *end_time;
  int result_count, filtered_result_count, run_status;
  array_t *result_hosts;
  iterator_t results, delta_results, params;
  int debugs, holes, infos, logs, warnings, false_positives;
  int f_debugs, f_holes, f_infos, f_logs, f_warnings, f_false_positives;
  int orig_f_debugs, orig_f_holes, orig_f_infos, orig_f_logs;
  int orig_f_warnings, orig_f_false_positives, orig_filtered_result_count;
  int search_phrase_exact, apply_overrides;
  double severity, f_severity;
  gchar *tz, *zone;
  GString *host_summary_buffer;

  /* Init some vars to prevent warnings from older compilers. */
  orig_filtered_result_count = 0;
  orig_f_false_positives = orig_f_warnings = orig_f_logs = orig_f_infos = 0;
  orig_f_holes = orig_f_debugs = 0;

  /** @todo Leaks on error in PRINT.  The process normally exits then anyway. */

  /* run_status is set by report_scan_run_status when either of "delta" and
   * "report" are true.  run_status is only used by run_status_name, only when
   * either of "delta" and "report" are true, and only after a
   * report_scan_run_status call.  Still GCC 4.4.5 (Debian 4.4.5-8) gives a
   * "may be used uninitialized" warning, so init it here to quiet the
   * warning. */
  run_status = TASK_STATUS_INTERNAL_ERROR;

  if (type
      && strcmp (type, "scan")
      && strcmp (type, "assets")
      && strcmp (type, "prognostic"))
    return -1;

  if ((type == NULL) || (strcmp (type, "scan") == 0))
    {
      type = NULL;
      if (report == 0)
        {
          assert (0);
          return -1;
        }
    }

  out = fopen (xml_file, "w");

  if (out == NULL)
    {
      g_warning ("%s: fopen failed: %s\n",
                 __FUNCTION__,
                 strerror (errno));
      return -1;
    }

  assert (get);

  if ((get->filt_id && strlen (get->filt_id) && strcmp (get->filt_id, "0"))
      || (get->filter && strlen (get->filter)))
    {
      term = NULL;
      if (get->filt_id && strlen (get->filt_id) && strcmp (get->filt_id, "0"))
        {
          term = filter_term (get->filt_id);
          if (term == NULL)
            {
              fclose (out);
              return 2;
            }
        }

      /* Set the filter parameters from the filter term. */
      manage_report_filter_controls (term ? term : get->filter,
                                     &first_result, &max_results, &sort_field,
                                     &sort_order, &result_hosts_only,
                                     &min_cvss_base, &min_qod, &levels,
                                     &delta_states,
                                     &search_phrase, &search_phrase_exact,
                                     &autofp, &notes, &overrides,
                                     &apply_overrides, &zone);
    }
  else
    {
      sort_field = g_strdup (given_sort_field);
      levels = g_strdup (given_levels);
      search_phrase = g_strdup (given_search_phrase);
      search_phrase_exact = 0;
      min_cvss_base = g_strdup (given_min_cvss_base);
      min_qod = g_strdup (given_min_qod);
      delta_states = g_strdup (given_delta_states);
      apply_overrides = given_apply_overrides;
      zone = given_zone ? g_strdup (given_zone) : NULL;

      /* Build the filter term from the old style GET attributes. */
      term = report_filter_term (sort_order, sort_field, result_hosts_only,
                                 min_cvss_base, min_qod, levels, delta_states,
                                 search_phrase, search_phrase_exact, autofp,
                                 notes, overrides, first_result, max_results,
                                 zone);
    }

  levels = levels ? levels : g_strdup ("hmlgd");

  if (task && task_uuid (task, &tsk_uuid))
    {
      fclose (out);
      g_free (term);
      g_free (sort_field);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_cvss_base);
      g_free (min_qod);
      g_free (delta_states);
      return -1;
    }

  if (zone && strlen (zone))
    {
      /* Store current TZ. */
      tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

      if (setenv ("TZ", zone, 1) == -1)
        {
          g_warning ("%s: Failed to switch to timezone", __FUNCTION__);
          if (tz != NULL)
            setenv ("TZ", tz, 1);
          g_free (tz);
          g_free (zone);
          return -1;
        }

      tzset ();
    }
  else
    /* Keep compiler quiet. */
    tz = NULL;

  if (delta && report)
    {
      uuid = report_uuid (report);
      PRINT (out, "<report type=\"delta\" id=\"%s\">", uuid);
      free (uuid);
    }
  else if (report)
    {
      uuid = report_uuid (report);
      PRINT (out, "<report id=\"%s\">", uuid);
      free (uuid);
    }
  else if (type && (strcmp (type, "assets") == 0))
    PRINT (out, "<report scap_loaded=\"%i\" type=\"%s\">",
           manage_scap_loaded (),
           type);
  else
    PRINT (out, "<report type=\"%s\">", type);

  PRINT (out, "<omp><version>%s</version></omp>", OMP_VERSION);

  if (delta)
    {
      delta_states = delta_states ? delta_states : g_strdup("cgns");
      report_scan_run_status (delta, &run_status);

      uuid = report_uuid (delta);
      PRINT (out,
             "<delta>"
             "<report id=\"%s\">"
             "<scan_run_status>%s</scan_run_status>",
             uuid,
             run_status_name (run_status
                               ? run_status
                               : TASK_STATUS_INTERNAL_ERROR));

      if (report_timestamp (uuid, &timestamp))
        {
          free (uuid);
          g_free (sort_field);
          g_free (levels);
          g_free (search_phrase);
          g_free (min_cvss_base);
          g_free (min_qod);
          g_free (delta_states);
          tz_revert (zone, tz);
          return -1;
        }
      PRINT (out,
             "<timestamp>%s</timestamp>",
             timestamp);
      g_free (timestamp);

      start_time = scan_start_time (delta);
      PRINT (out,
             "<scan_start>%s</scan_start>",
             start_time);
      free (start_time);

      end_time = scan_end_time (delta);
      PRINT (out,
             "<scan_end>%s</scan_end>",
             end_time);
      free (end_time);

      PRINT (out,
             "</report>"
             "</delta>");
    }

  PRINT (out, "<report_format>");
  init_report_format_param_iterator (&params, report_format, 0, 1, NULL);
  while (next (&params))
    PRINT (out,
           "<param><name>%s</name><value>%s</value></param>",
           report_format_param_iterator_name (&params),
           report_format_param_iterator_value (&params));
  cleanup_iterator (&params);
  PRINT (out, "</report_format>");

  if (report)
    {
      if (delta == 0)
        {
          report_scan_result_count (report, NULL, NULL, 0, NULL, "0",
                                    apply_overrides, autofp,
                                    &result_count);
        }
      report_scan_result_count (report,
                                levels,
                                search_phrase,
                                search_phrase_exact,
                                min_cvss_base,
                                min_qod,
                                apply_overrides,
                                autofp,
                                &filtered_result_count);
      report_scan_run_status (report, &run_status);
    }

  clean = manage_clean_filter (term
                                ? term
                                : (get->filter ? get->filter : ""));
  g_free (term);
  term = clean;

  if (filter_term_return)
    *filter_term_return = g_strdup (term);

  PRINT
   (out,
    "<sort><field>%s<order>%s</order></field></sort>"
    "<filters id=\"%s\">"
    "<term>%s</term>"
    "%s"
    "<phrase>%s</phrase>"
    "<autofp>%i</autofp>"
    "<notes>%i</notes>"
    "<overrides>%i</overrides>"
    "<apply_overrides>%i</apply_overrides>"
    "<result_hosts_only>%i</result_hosts_only>"
    "<min_cvss_base>%s</min_cvss_base>"
    "<min_qod>%s</min_qod>"
    "<timezone>%s</timezone>",
    sort_field ? sort_field : "type",
    sort_order ? "ascending" : "descending",
    get->filt_id ? get->filt_id : "0",
    term,
    levels,
    search_phrase ? search_phrase : "",
    autofp,
    notes ? 1 : 0,
    overrides ? 1 : 0,
    apply_overrides ? 1 : 0,
    result_hosts_only ? 1 : 0,
    min_cvss_base ? min_cvss_base : "",
    min_qod ? min_qod : "",
    zone ? zone : "");

  g_free (term);

  if (strchr (levels, 'h'))
    PRINT (out, "<filter>High</filter>");
  if (strchr (levels, 'm'))
    PRINT (out, "<filter>Medium</filter>");
  if (strchr (levels, 'l'))
    PRINT (out, "<filter>Low</filter>");
  if (strchr (levels, 'g'))
    PRINT (out, "<filter>Log</filter>");
  if (strchr (levels, 'd'))
    PRINT (out, "<filter>Debug</filter>");
  if (strchr (levels, 'f'))
    PRINT (out, "<filter>False Positive</filter>");

  if (delta)
    {
      PRINT (out,
             "<host><ip>%s</ip></host>"
             "<delta>"
             "%s"
             "<changed>%i</changed>"
             "<gone>%i</gone>"
             "<new>%i</new>"
             "<same>%i</same>"
             "</delta>",
             host ? host : "",
             delta_states,
             strchr (delta_states, 'c') != NULL,
             strchr (delta_states, 'g') != NULL,
             strchr (delta_states, 'n') != NULL,
             strchr (delta_states, 's') != NULL);
    }
  else if (type && (strcmp (type, "prognostic") == 0))
    PRINT (out,
           "<host><ip>%s</ip></host>",
           host ? host : "");

  PRINT (out, "</filters>");

  {
    const char *severity_setting;
    gchar *class_xml;

    severity_setting = setting_severity ();
    class_xml = severity_class_xml (severity_setting);
    if (class_xml)
      {
        PRINT_XML (out, class_xml);
        g_free (class_xml);
      }
  }

  if (report)
    {
      uuid = report_uuid (report);

      PRINT (out,
            "<user_tags>"
            "<count>%i</count>",
            resource_tag_count ("report", report, 1));

      if (get->details || get->id)
        {
          iterator_t tags;

          init_resource_tag_iterator (&tags, "report", report, 1, NULL, 1);

          while (next (&tags))
            {
              PRINT (out,
                     "<tag id=\"%s\">"
                     "<name>%s</name>"
                     "<value>%s</value>"
                     "<comment>%s</comment>"
                     "</tag>",
                     resource_tag_iterator_uuid (&tags),
                     resource_tag_iterator_name (&tags),
                     resource_tag_iterator_value (&tags),
                     resource_tag_iterator_comment (&tags));
            }

          cleanup_iterator (&tags);
        }

      PRINT (out, "</user_tags>");

      free (uuid);
    }

  if (report)
    {
      PRINT
       (out,
        "<scan_run_status>%s</scan_run_status>",
        run_status_name (run_status
                          ? run_status
                          : TASK_STATUS_INTERNAL_ERROR));

      PRINT (out,
             "<hosts><count>%i</count></hosts>",
             report_host_count (report));

      PRINT (out,
             "<closed_cves><count>%i</count></closed_cves>",
             report_closed_cve_count (report));

      PRINT (out,
             "<vulns><count>%i</count></vulns>",
             report_vuln_count (report));

      PRINT (out,
             "<os><count>%i</count></os>",
             report_os_count (report));

      PRINT (out,
             "<apps><count>%i</count></apps>",
             report_app_count (report));

      PRINT (out,
             "<ssl_certs><count>%i</count></ssl_certs>",
             report_ssl_cert_count (report));

    }

  if (task && tsk_uuid)
    {
      char *tsk_name, *task_target_uuid, *comment;
      target_t target;
      gchar *progress_xml;

      tsk_name = task_name (task);

      comment = task_comment (task);

      target = task_target (task);
      if (task_target_in_trash (task))
        task_target_uuid = trash_target_uuid (target);
      else
        task_target_uuid = target_uuid (target);

      if ((target == 0)
          && (task_run_status (task) == TASK_STATUS_RUNNING))
        progress_xml = g_strdup_printf
                        ("%i",
                         task_upload_progress (task));
      else
        {
          int progress;
          progress = report_progress (report, task, NULL);
          progress_xml = g_strdup_printf ("%i", progress);
        }

      PRINT (out,
             "<task id=\"%s\">"
             "<name>%s</name>"
             "<comment>%s</comment>"
             "<target id=\"%s\">"
             "<trash>%i</trash>"
             "</target>"
             "<progress>%s</progress>"
             "</task>",
             tsk_uuid,
             tsk_name ? tsk_name : "",
             comment ? comment : "",
             task_target_uuid ? task_target_uuid : "",
             task_target_in_trash (task),
             progress_xml);
      g_free (progress_xml);
      free (comment);
      free (tsk_name);
      free (tsk_uuid);

      {
        char *slave_uuid, *slave_name, *slave_host, *slave_port, *source_iface;

        /* Info about the situation at the time of scan. */

        PRINT (out,
               "<scan>"
               "<task>");

        slave_uuid = report_slave_uuid (report);
        slave_name = report_slave_name (report);
        slave_host = report_slave_host (report);
        slave_port = report_slave_port (report);

        if (slave_uuid)
          /* @id "" means no slave.  Missing SLAVE means we don't know. */
          PRINT (out,
                 "<slave id=\"%s\">"
                 "<name>%s</name>"
                 "<host>%s</host>"
                 "<port>%s</port>"
                 "</slave>",
                 slave_uuid,
                 slave_name ? slave_name : "",
                 slave_host ? slave_host : "",
                 slave_port ? slave_port : "");

        free (slave_uuid);
        free (slave_name);
        free (slave_host);
        free (slave_port);

        source_iface = report_source_iface (report);

        if (source_iface)
          /* VALUE "" means preference was not set.  Missing PREFERENCE means
           * we don't know. */
          PRINT (out,
                 "<preferences>"
                 "<preference>"
                 "<name>Network Source Interface</name>"
                 "<scanner_name>source_iface</scanner_name>"
                 "<value>%s</value>"
                 "</preference>"
                 "</preferences>",
                 source_iface);

        free (source_iface);

        PRINT (out,
               "</task>"
               "</scan>");
      }
    }

  if (type && (strcmp (type, "assets") == 0))
    {
      int ret;

      ret = print_report_assets_xml (out, host, first_result,
                                     max_results, levels, search_phrase, pos,
                                     get, apply_overrides, autofp);
      g_free (sort_field);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_cvss_base);
      g_free (min_qod);
      g_free (delta_states);

      tz_revert (zone, tz);

      if (ret)
        return ret;

      PRINT (out, "</report>");

      if (fclose (out))
        {
          g_warning ("%s: fclose failed: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          return -1;
        }

      return 0;
    }

  if (type && (strcmp (type, "prognostic") == 0))
    {
      int ret;

      ret = print_report_prognostic_xml (out, host, first_result, max_results,
                                         levels, search_phrase, pos, get,
                                         apply_overrides, autofp,
                                         host_search_phrase, host_levels,
                                         host_first_result, host_max_results,
                                         min_cvss_base, result_hosts_only,
                                         sort_order, sort_field);

      g_free (sort_field);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_cvss_base);
      g_free (min_qod);
      g_free (delta_states);

      tz_revert (zone, tz);

      if (ret)
        return ret;

      PRINT (out, "</report>");

      if (fclose (out))
        {
          g_warning ("%s: fclose failed: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          return -1;
        }

      return 0;
    }

  uuid = report_uuid (report);
  if (report_timestamp (uuid, &timestamp))
    {
      free (uuid);
      tz_revert (zone, tz);
      return -1;
    }
  free (uuid);
  PRINT (out,
         "<timestamp>%s</timestamp>",
         timestamp);
  g_free (timestamp);

  start_time = scan_start_time (report);
  PRINT (out,
         "<scan_start>%s</scan_start>",
         start_time);
  free (start_time);

  {
    time_t start_time_epoch;
    const char *abbrev;
    gchar *report_zone;

    start_time_epoch = scan_start_time_epoch (report);
    abbrev = NULL;
    if (zone && strlen (zone))
      report_zone = g_strdup (zone);
    else
      report_zone = setting_timezone ();
    iso_time_tz (&start_time_epoch, report_zone, &abbrev);

    if (zone_return)
      *zone_return = g_strdup (report_zone ? report_zone : "");

    PRINT (out,
           "<timezone>%s</timezone>"
           "<timezone_abbrev>%s</timezone_abbrev>",
           report_zone
            ? report_zone
            : "Coordinated Universal Time",
           abbrev ? abbrev : "UTC");
    g_free (report_zone);
  }

  /* Port summary. */

  if (get->details && (delta == 0))
    {
      if (print_report_port_xml (report, out,
                                 ignore_pagination ? 0 : first_result,
                                 ignore_pagination ? -1 : max_results,
                                 sort_order, sort_field, levels, autofp,
                                 search_phrase, search_phrase_exact,
                                 min_cvss_base, min_qod, apply_overrides))
        {
          tz_revert (zone, tz);
          return -1;
        }
    }

  /* Prepare result counts. */

  report_counts_id_filt (report, &debugs, &holes, &infos, &logs,
                         &warnings, &false_positives, &severity,
                         apply_overrides, NULL, min_cvss_base, min_qod,
                         search_phrase, search_phrase_exact, autofp,
                         &f_debugs, &f_holes, &f_infos, &f_logs, &f_warnings,
                         &f_false_positives, &f_severity);

  /* Results. */

  if (delta && get->details)
    {
      init_result_iterator (&results, report, 0,
                            0,
                            -1,
                            sort_order,
                            sort_field,
                            levels,
                            autofp,
                            search_phrase,
                            search_phrase_exact,
                            min_cvss_base,
                            min_qod,
                            apply_overrides);

      init_result_iterator (&delta_results, delta, 0,
                            0,
                            -1,
                            sort_order,
                            sort_field,
                            levels,
                            autofp,
                            search_phrase,
                            search_phrase_exact,
                            min_cvss_base,
                            min_qod,
                            apply_overrides);
    }
  else if (get->details)
    init_result_iterator (&results, report, 0,
                          ignore_pagination ? 0 : first_result,
                          ignore_pagination ? -1 : max_results,
                          sort_order,
                          sort_field,
                          levels,
                          autofp,
                          search_phrase,
                          search_phrase_exact,
                          min_cvss_base,
                          min_qod,
                          apply_overrides);

  if (get->details)
    PRINT (out,
             "<results"
             " start=\"%i\""
             " max=\"%i\">",
             /* Add 1 for 1 indexing. */
             ignore_pagination ? 1 : first_result + 1,
             ignore_pagination ? -1 : max_results);
  if (get->details && result_hosts_only)
    result_hosts = make_array ();
  else
    /* Quiet erroneous compiler warning. */
    result_hosts = NULL;
  if (delta && get->details)
    {
      gboolean done, delta_done;
      int changed, gone, new, same;
      /* A tree of host, tree pairs, where the inner tree is a sorted tree
       * of port, threat pairs. */
      GTree *ports;

      orig_f_debugs = f_debugs;
      orig_f_holes = f_holes;
      orig_f_infos = f_infos;
      orig_f_logs = f_logs;
      orig_f_warnings = f_warnings;
      orig_f_false_positives = f_false_positives;
      orig_filtered_result_count = filtered_result_count;

      changed = (strchr (delta_states, 'c') != NULL);
      gone = (strchr (delta_states, 'g') != NULL);
      new = (strchr (delta_states, 'n') != NULL);
      same = (strchr (delta_states, 's') != NULL);

      ports = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
                               (GDestroyNotify) free_host_ports);

      /* Compare the results in the two iterators, which are sorted. */

      tracef ("   delta: %s: start", __FUNCTION__);
      done = !next (&results);
      delta_done = !next (&delta_results);
      while (1)
        {
          GString *buffer;
          compare_results_t state;
          int used, would_use;

          if (max_results == 0)
            break;

          if (done)
            {
              if (delta_done)
                break;
              if (new)
                /* Extra results in 'delta_results'. */
                do
                  {
                    const char *level;

                    tracef ("   delta: %s: extra from report 2: %s",
                            __FUNCTION__,
                            result_iterator_nvt_oid (&results));

                    if (first_result)
                      {
                        tracef ("   delta: skip");
                        first_result--;
                        continue;
                      }

                    /* Increase the result count. */
                    level = result_iterator_level (&delta_results);
                    orig_filtered_result_count++;
                    filtered_result_count++;
                    if (strcmp (level, "High") == 0)
                      {
                        orig_f_holes++;
                        f_holes++;
                      }
                    else if (strcmp (level, "Medium") == 0)
                      {
                        orig_f_warnings++;
                        f_warnings++;
                      }
                    else if (strcmp (level, "Low") == 0)
                      {
                        orig_f_infos++;
                        f_infos++;
                      }
                    else if (strcmp (level, "Log") == 0)
                      {
                        orig_f_logs++;
                        f_logs++;
                      }
                    else if (strcmp (level, "False Positive") == 0)
                      {
                        orig_f_false_positives++;
                        f_false_positives++;
                      }

                    tracef ("   delta: %s: extra from report 2: %s",
                            __FUNCTION__,
                            result_iterator_nvt_oid (&delta_results));
                    buffer = g_string_new ("");
                    buffer_results_xml (buffer,
                                        &delta_results,
                                        task,
                                        notes,
                                        notes_details,
                                        overrides,
                                        overrides_details,
                                        0,
                                        0,
                                        0,
                                        "new",
                                        NULL,
                                        0);
                    PRINT_XML (out, buffer->str);
                    g_string_free (buffer, TRUE);
                    if (result_hosts_only)
                      array_add_new_string (result_hosts,
                                            result_iterator_host (&delta_results));
                    add_port (ports, &delta_results);
                    max_results--;
                    if (max_results == 0)
                      break;
                  }
                while (next (&delta_results));
              delta_done = TRUE;
              break;
            }

          if (delta_done)
            {
              /* Extra results in 'results'. */
              if (gone)
                do
                  {
                    tracef ("   delta: %s: extra from report 1: %s",
                            __FUNCTION__,
                            result_iterator_nvt_oid (&results));
                    if (first_result)
                      {
                        tracef ("   delta: skip");
                        first_result--;
                        continue;
                      }
                    buffer = g_string_new ("");
                    buffer_results_xml (buffer,
                                        &results,
                                        task,
                                        notes,
                                        notes_details,
                                        overrides,
                                        overrides_details,
                                        0,
                                        0,
                                        0,
                                        "gone",
                                        NULL,
                                        0);
                    PRINT_XML (out, buffer->str);
                    g_string_free (buffer, TRUE);
                    if (result_hosts_only)
                      array_add_new_string (result_hosts,
                                            result_iterator_host (&results));
                    add_port (ports, &results);
                    max_results--;
                    if (max_results == 0)
                      break;
                  }
                while (next (&results));
              else
                do
                  {
                    const char *level;

                    /* Decrease the result count. */
                    level = result_iterator_level (&results);
                    orig_filtered_result_count--;
                    filtered_result_count--;
                    if (strcmp (level, "High") == 0)
                      {
                        orig_f_holes--;
                        f_holes--;
                      }
                    else if (strcmp (level, "Medium") == 0)
                      {
                        orig_f_warnings--;
                        f_warnings--;
                      }
                    else if (strcmp (level, "Low") == 0)
                      {
                        orig_f_infos--;
                        f_infos--;
                      }
                    else if (strcmp (level, "Log") == 0)
                      {
                        orig_f_logs--;
                        f_logs--;
                      }
                    else if (strcmp (level, "False Positive") == 0)
                      {
                        orig_f_false_positives--;
                        f_false_positives--;
                      }
                  }
                while (next (&results));
              done = TRUE;
              break;
            }

          /* Compare the two results. */

          buffer = g_string_new ("");
          state = compare_and_buffer_results (buffer,
                                              &results,
                                              &delta_results,
                                              task,
                                              notes,
                                              notes_details,
                                              overrides,
                                              overrides_details,
                                              sort_order,
                                              sort_field,
                                              changed,
                                              gone,
                                              new,
                                              same,
                                              &max_results,
                                              &first_result,
                                              &used,
                                              &would_use);
          if (state == COMPARE_RESULTS_ERROR)
            {
              g_warning ("%s: compare_and_buffer_results failed\n",
                         __FUNCTION__);
              g_free (sort_field);
              g_free (levels);
              g_free (search_phrase);
              g_free (min_cvss_base);
              g_free (min_qod);
              g_free (delta_states);
              cleanup_iterator (&results);
              cleanup_iterator (&delta_results);
              tz_revert (zone, tz);
              return -1;
            }
          PRINT_XML (out, buffer->str);
          g_string_free (buffer, TRUE);

          if ((used == 0)
              && ((state == COMPARE_RESULTS_GONE)
                  || (state == COMPARE_RESULTS_SAME)
                  || (state == COMPARE_RESULTS_CHANGED)))
            {
              const char *level;

              /* Decrease the result count. */
              level = result_iterator_level (&results);
              filtered_result_count--;
              if (strcmp (level, "High") == 0)
                {
                  f_holes--;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  f_warnings--;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  f_infos--;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  f_logs--;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  f_false_positives--;
                }
            }

          if ((would_use == 0)
              && ((state == COMPARE_RESULTS_GONE)
                  || (state == COMPARE_RESULTS_SAME)
                  || (state == COMPARE_RESULTS_CHANGED)))
            {
              const char *level;

              /* Decrease the result count. */
              level = result_iterator_level (&results);
              orig_filtered_result_count--;
              if (strcmp (level, "High") == 0)
                {
                  orig_f_holes--;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  orig_f_warnings--;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  orig_f_infos--;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  orig_f_logs--;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  orig_f_false_positives--;
                }
            }

          /* Move on to the next. */

          if (state == COMPARE_RESULTS_GONE)
            {
              /* "Used" just the 'results' result. */
              if (used)
                {
                  if (result_hosts_only)
                    array_add_new_string (result_hosts,
                                          result_iterator_host (&results));
                  add_port (ports, &results);
                }
              done = !next (&results);
            }
          else if ((state == COMPARE_RESULTS_SAME)
                   || (state == COMPARE_RESULTS_CHANGED))
            {
              /* "Used" both results. */
              if (used)
                {
                  if (result_hosts_only)
                    array_add_new_string (result_hosts,
                                          result_iterator_host (&results));
                  add_port (ports, &results);
                }
              done = !next (&results);
              delta_done = !next (&delta_results);
            }
          else if (state == COMPARE_RESULTS_NEW)
            {
              if (would_use)
                {
                  const char *level;

                  /* Would have "used" just the 'delta_results' result, on
                   * an earlier page. */

                  /* Increase the result count. */
                  level = result_iterator_level (&delta_results);
                  orig_filtered_result_count++;
                  if (strcmp (level, "High") == 0)
                    {
                      orig_f_holes++;
                    }
                  else if (strcmp (level, "Medium") == 0)
                    {
                      orig_f_warnings++;
                    }
                  else if (strcmp (level, "Low") == 0)
                    {
                      orig_f_infos++;
                    }
                  else if (strcmp (level, "Log") == 0)
                    {
                      orig_f_logs++;
                    }
                  else if (strcmp (level, "False Positive") == 0)
                    {
                      orig_f_false_positives++;
                    }
                }

              if (used)
                {
                  const char *level;

                  /* "Used" just the 'delta_results' result. */

                  /* Increase the result count. */
                  level = result_iterator_level (&delta_results);
                  filtered_result_count++;
                  if (strcmp (level, "High") == 0)
                    {
                      f_holes++;
                    }
                  else if (strcmp (level, "Medium") == 0)
                    {
                      f_warnings++;
                    }
                  else if (strcmp (level, "Low") == 0)
                    {
                      f_infos++;
                    }
                  else if (strcmp (level, "Log") == 0)
                    {
                      f_logs++;
                    }
                  else if (strcmp (level, "False Positive") == 0)
                    {
                      f_false_positives++;
                    }

                  if (result_hosts_only)
                    array_add_new_string (result_hosts,
                                          result_iterator_host
                                           (&delta_results));

                  add_port (ports, &delta_results);
                }
              delta_done = !next (&delta_results);
            }
          else
            assert (0);
        }

      /* Compare remaining results, for the filtered report counts. */

      tracef ("   delta: %s: counting rest", __FUNCTION__);
      while (1)
        {
          compare_results_t state;
          int used, would_use;

          if (done)
            {
              if (delta_done)
                break;
              if (new)
                /* Extra results in 'delta_results'. */
                do
                  {
                    const char *level;

                    tracef ("   delta: %s: extra from report 2: %s",
                            __FUNCTION__,
                            result_iterator_nvt_oid (&delta_results));

                    /* Increase the result count. */
                    level = result_iterator_level (&delta_results);
                    orig_filtered_result_count++;
                    if (strcmp (level, "High") == 0)
                      {
                        orig_f_holes++;
                      }
                    else if (strcmp (level, "Medium") == 0)
                      {
                        orig_f_warnings++;
                      }
                    else if (strcmp (level, "Low") == 0)
                      {
                        orig_f_infos++;
                      }
                    else if (strcmp (level, "Log") == 0)
                      {
                        orig_f_logs++;
                      }
                    else if (strcmp (level, "False Positive") == 0)
                      {
                        orig_f_false_positives++;
                      }
                  }
                while (next (&delta_results));
              break;
            }

          if (delta_done)
            {
              /* Extra results in 'results'. */
              if (gone)
                do
                  {
                    tracef ("   delta: %s: extra from report 1: %s",
                            __FUNCTION__,
                            result_iterator_nvt_oid (&results));

                    /* It's in the count already. */
                  }
                while (next (&results));
              else
                do
                  {
                    const char *level;

                    /* Decrease the result count. */
                    level = result_iterator_level (&results);
                    orig_filtered_result_count--;
                    if (strcmp (level, "High") == 0)
                      {
                        orig_f_holes--;
                      }
                    else if (strcmp (level, "Medium") == 0)
                      {
                        orig_f_warnings--;
                      }
                    else if (strcmp (level, "Low") == 0)
                      {
                        orig_f_infos--;
                      }
                    else if (strcmp (level, "Log") == 0)
                      {
                        orig_f_logs--;
                      }
                    else if (strcmp (level, "False Positive") == 0)
                      {
                        orig_f_false_positives--;
                      }
                  }
                while (next (&results));
              break;
            }

          /* Compare the two results. */

          state = compare_and_buffer_results (NULL,
                                              &results,
                                              &delta_results,
                                              task,
                                              notes,
                                              notes_details,
                                              overrides,
                                              overrides_details,
                                              sort_order,
                                              sort_field,
                                              changed,
                                              gone,
                                              new,
                                              same,
                                              &max_results,
                                              &first_result,
                                              &used,
                                              &would_use);
          if (state == COMPARE_RESULTS_ERROR)
            {
              g_warning ("%s: compare_and_buffer_results failed\n",
                         __FUNCTION__);
              g_free (sort_field);
              g_free (levels);
              g_free (search_phrase);
              g_free (min_cvss_base);
              g_free (min_qod);
              g_free (delta_states);
              cleanup_iterator (&results);
              cleanup_iterator (&delta_results);
              tz_revert (zone, tz);
              return -1;
            }

          if (state == COMPARE_RESULTS_NEW)
            {
              if (used)
                {
                  const char *level;

                  /* "Used" just the 'delta_results' result. */

                  /* Increase the result count. */
                  level = result_iterator_level (&delta_results);
                  orig_filtered_result_count++;
                  if (strcmp (level, "High") == 0)
                    {
                      orig_f_holes++;
                    }
                  else if (strcmp (level, "Medium") == 0)
                    {
                      orig_f_warnings++;
                    }
                  else if (strcmp (level, "Low") == 0)
                    {
                      orig_f_infos++;
                    }
                  else if (strcmp (level, "Log") == 0)
                    {
                      orig_f_logs++;
                    }
                  else if (strcmp (level, "False Positive") == 0)
                    {
                      orig_f_false_positives++;
                    }
                }
            }
          else if (used)
            {
              /* It's in the count already. */
            }
          else
            {
              const char *level;

              /* Decrease the result count. */
              level = result_iterator_level (&results);
              orig_filtered_result_count--;
              if (strcmp (level, "High") == 0)
                {
                  orig_f_holes--;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  orig_f_warnings--;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  orig_f_infos--;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  orig_f_logs--;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  orig_f_false_positives--;
                }
            }

          /* Move on to the next. */

          if (state == COMPARE_RESULTS_GONE)
            {
              /* "Used" just the 'results' result. */
              done = !next (&results);
            }
          else if ((state == COMPARE_RESULTS_SAME)
                   || (state == COMPARE_RESULTS_CHANGED))
            {
              /* "Used" both results. */
              done = !next (&results);
              delta_done = !next (&delta_results);
            }
          else if (state == COMPARE_RESULTS_NEW)
            {
              /* "Used" just the 'delta_results' result. */
              delta_done = !next (&delta_results);
            }
          else
            assert (0);
        }
      PRINT (out, "</results>");

      /* Write ports to file. */

      PRINT (out,
               "<ports"
               " start=\"%i\""
               " max=\"%i\">",
               /* Add 1 for 1 indexing. */
               first_result + 1,
               max_results);
      if (sort_field == NULL || strcmp (sort_field, "port"))
        {
          if (sort_order)
            g_tree_foreach (ports, print_host_ports_by_severity_asc, out);
          else
            g_tree_foreach (ports, print_host_ports_by_severity_desc, out);
        }
      else if (sort_order)
        g_tree_foreach (ports, print_host_ports, out);
      else
        g_tree_foreach (ports, print_host_ports_desc, out);
      g_tree_destroy (ports);
      PRINT (out, "</ports>");
    }
  else if (get->details)
    {
      while (next (&results))
        {
          GString *buffer = g_string_new ("");
          buffer_results_xml (buffer,
                              &results,
                              task,
                              notes,
                              notes_details,
                              overrides,
                              overrides_details,
                              1,
                              1,
                              0,
                              NULL,
                              NULL,
                              0);
          PRINT_XML (out, buffer->str);
          g_string_free (buffer, TRUE);
          if (result_hosts_only)
            array_add_new_string (result_hosts,
                                  result_iterator_host (&results));
        }
      PRINT (out, "</results>");
    }
  if (get->details)
    cleanup_iterator (&results);
  if (delta && get->details)
    cleanup_iterator (&delta_results);

  /* Print result counts and severity. */

  if (delta)
    /** @todo The f_debugs, etc. vars are setup to give the page count. */
    PRINT (out,
             "<result_count>"
             "<filtered>%i</filtered>"
             "<debug><filtered>%i</filtered></debug>"
             "<hole><filtered>%i</filtered></hole>"
             "<info><filtered>%i</filtered></info>"
             "<log><filtered>%i</filtered></log>"
             "<warning><filtered>%i</filtered></warning>"
             "<false_positive>"
             "<filtered>%i</filtered>"
             "</false_positive>"
             "</result_count>",
             orig_filtered_result_count,
             (strchr (levels, 'd') ? orig_f_debugs : 0),
             (strchr (levels, 'h') ? orig_f_holes : 0),
             (strchr (levels, 'l') ? orig_f_infos : 0),
             (strchr (levels, 'g') ? orig_f_logs : 0),
             (strchr (levels, 'm') ? orig_f_warnings : 0),
             (strchr (levels, 'f') ? orig_f_false_positives : 0));
  else
    {
      PRINT (out,
             "<result_count>"
             "%i"
             "<full>%i</full>"
             "<filtered>%i</filtered>"
             "<debug><full>%i</full><filtered>%i</filtered></debug>"
             "<hole><full>%i</full><filtered>%i</filtered></hole>"
             "<info><full>%i</full><filtered>%i</filtered></info>"
             "<log><full>%i</full><filtered>%i</filtered></log>"
             "<warning><full>%i</full><filtered>%i</filtered></warning>"
             "<false_positive>"
             "<full>%i</full>"
             "<filtered>%i</filtered>"
             "</false_positive>"
             "</result_count>",
             result_count,
             result_count,
             filtered_result_count,
             debugs,
             (strchr (levels, 'd') ? f_debugs : 0),
             holes,
             (strchr (levels, 'h') ? f_holes : 0),
             infos,
             (strchr (levels, 'l') ? f_infos : 0),
             logs,
             (strchr (levels, 'g') ? f_logs : 0),
             warnings,
             (strchr (levels, 'm') ? f_warnings : 0),
             false_positives,
             (strchr (levels, 'f') ? f_false_positives : 0));

      PRINT (out,
          "<severity>"
          "<full>%1.1f</full>"
          "<filtered>%1.1f</filtered>"
          "</severity>",
          severity,
          f_severity);
    }

  if (host_summary)
    {
      host_summary_buffer = g_string_new ("");
      g_string_append_printf (host_summary_buffer,
                              "   %-15s   %-16s   End\n",
                              "Host", "Start");
    }
  else
    host_summary_buffer = NULL;

  if (get->details && result_hosts_only)
    {
      gchar *host;
      int index = 0;
      array_terminate (result_hosts);
      while ((host = g_ptr_array_index (result_hosts, index++)))
        {
          gboolean present;
          iterator_t hosts;
          init_host_iterator (&hosts, report, host, 0);
          present = next (&hosts);
          if (delta && (present == FALSE))
            {
              cleanup_iterator (&hosts);
              init_host_iterator (&hosts, delta, host, 0);
              present = next (&hosts);
            }
          if (present)
            {
              host_summary_append (host_summary_buffer,
                                   host,
                                   host_iterator_start_time (&hosts),
                                   host_iterator_end_time (&hosts));
              PRINT (out,
                     "<host>"
                     "<ip>%s</ip>"
                     "<start>%s</start>"
                     "<end>%s</end>",
                     host,
                     host_iterator_start_time (&hosts),
                     host_iterator_end_time (&hosts)
                       ? host_iterator_end_time (&hosts)
                       : "");

              if (print_report_host_details_xml
                  (host_iterator_report_host (&hosts), out))
                {
                  tz_revert (zone, tz);
                  if (host_summary_buffer)
                    g_string_free (host_summary_buffer, TRUE);
                  return -1;
                }

              PRINT (out,
                     "</host>");

              PRINT (out,
                       "<host_start>"
                       "<host>%s</host>%s"
                       "</host_start>",
                       host,
                       host_iterator_start_time (&hosts));
              PRINT (out,
                       "<host_end>"
                       "<host>%s</host>%s"
                       "</host_end>",
                       host,
                       host_iterator_end_time (&hosts)
                         ? host_iterator_end_time (&hosts)
                         : "");
            }
          cleanup_iterator (&hosts);
        }
      array_free (result_hosts);
    }
  else if (get->details)
    {
      iterator_t hosts;
      init_host_iterator (&hosts, report, NULL, 0);
      while (next (&hosts))
        {
          host_summary_append (host_summary_buffer,
                               host_iterator_host (&hosts),
                               host_iterator_start_time (&hosts),
                               host_iterator_end_time (&hosts));
          PRINT (out,
                 "<host>"
                 "<ip>%s</ip>"
                 "<start>%s</start>"
                 "<end>%s</end>",
                 host_iterator_host (&hosts),
                 host_iterator_start_time (&hosts),
                 host_iterator_end_time (&hosts)
                   ? host_iterator_end_time (&hosts)
                   : "");

          if (print_report_host_details_xml
              (host_iterator_report_host (&hosts), out))
            {
              tz_revert (zone, tz);
              if (host_summary_buffer)
                g_string_free (host_summary_buffer, TRUE);
              return -1;
            }

          PRINT (out,
                 "</host>");

          PRINT (out,
                 "<host_start><host>%s</host>%s</host_start>",
                 host_iterator_host (&hosts),
                 host_iterator_start_time (&hosts));
        }
      cleanup_iterator (&hosts);

      init_host_iterator (&hosts, report, NULL, 0);
      while (next (&hosts))
        PRINT (out,
                 "<host_end><host>%s</host>%s</host_end>",
                 host_iterator_host (&hosts),
                 host_iterator_end_time (&hosts)
                  ? host_iterator_end_time (&hosts)
                  : "");
      cleanup_iterator (&hosts);
    }
  end_time = scan_end_time (report);
  PRINT (out,
           "<scan_end>%s</scan_end>",
           end_time);
  free (end_time);

  if (delta == 0 && print_report_errors_xml (report, out))
    {
      tz_revert (zone, tz);
      if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
      return -1;
    }

  PRINT (out, "</report>");

  g_free (sort_field);
  g_free (levels);
  g_free (search_phrase);
  g_free (min_cvss_base);
  g_free (min_qod);
  g_free (delta_states);

  if (host_summary && host_summary_buffer)
    *host_summary = g_string_free (host_summary_buffer, FALSE);

  if (fclose (out))
    {
      g_warning ("%s: fclose failed: %s\n",
                 __FUNCTION__,
                 strerror (errno));
      return -1;
    }

  return 0;
}

/**
 * @brief Generate a report.
 *
 * @param[in]  report             Report.
 * @param[in]  report_format      Report format.
 * @param[in]  filt_id            ID of filter or NULL, overrides other args.
 * @param[in]  sort_order         Whether to sort ascending or descending.
 * @param[in]  sort_field         Field to sort on, or NULL for "type".
 * @param[in]  result_hosts_only  Whether to show only hosts with results.
 * @param[in]  min_cvss_base      Minimum CVSS base of included results.  All
 *                                results if NULL.
 * @param[in]  min_qod            Minimum QoD of included results.  All
 *                                results if NULL.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in] delta_states       Delta states.  Allows caller to specify the
 *                               value for the delta_states param in the filter
 *                               string.
 * @param[in]  apply_overrides    Whether to apply overrides.
 * @param[in]  search_phrase      Phrase that results must include.  All results
 *                                if NULL or "".
 * @param[in]  autofp             Whether to apply auto FP filter.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  first_result       The result to start from.  The results are 0
 *                                indexed.
 * @param[in]  max_results        The maximum number of results returned.
 * @param[in]  type               Type of report: NULL or "scan".
 * @param[out] output_length      NULL or location for length of return.
 * @param[out] extension          NULL or location for report format extension.
 * @param[out] content_type       NULL or location for report format content
 *                                type.
 * @param[in]  zone               Timezone.  NULL or "" for user's timezone.
 * @param[out] filter_term_return  Filter term used in report.
 * @param[out] zone_return         Actual zone used in report.
 * @param[out] host_summary    Summary of results per host.
 *
 * @return Contents of report on success, NULL on error.
 */
gchar *
manage_report (report_t report, report_format_t report_format,
               const char *filt_id, int sort_order,
               const char* sort_field, int result_hosts_only,
               const char *min_cvss_base, const char *min_qod,
               const char *levels,
               const char *delta_states, int apply_overrides,
               const char *search_phrase, int autofp,
               int notes, int notes_details, int overrides,
               int overrides_details, int first_result,
               int max_results, const char *type, gsize *output_length,
               gchar **extension, gchar **content_type, const char *zone,
               gchar **filter_term_return, gchar **zone_return,
               gchar **host_summary)
{
  task_t task;
  gchar *xml_file;
  char xml_dir[] = "/tmp/openvasmd_XXXXXX";
  int ret;
  get_data_t get;

  memset (&get, 0, sizeof (get));

  if (type && strcmp (type, "scan"))
    return NULL;

  /* Print the report as XML to a file. */

  if (((report_format_predefined (report_format) == 0)
       && (report_format_trust (report_format) != TRUST_YES))
      || (report_task (report, &task)))
    {
      return NULL;
    }

  if (mkdtemp (xml_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
      return NULL;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);
  get.filt_id = filt_id ? g_strdup (filt_id) : NULL;
  get.details = 1;
  ret = print_report_xml (report, 0, task, xml_file, &get,
                          sort_order, sort_field,
                          result_hosts_only, min_cvss_base, min_qod,
                          report_format,
                          levels, delta_states, apply_overrides, search_phrase,
                          autofp, notes, notes_details, overrides,
                          overrides_details, first_result, max_results, type,
                          NULL, 0, NULL, NULL, 0, 0, 0, zone,
                          filter_term_return, zone_return, host_summary);
  g_free (get.filt_id);
  if (ret)
    {
      g_free (xml_file);
      return NULL;
    }

  /* Pass the file to the report format generate script, sending the output
   * to a file. */

  {
    iterator_t formats;
    const char *uuid_format;
    gchar *script, *script_dir;
    get_data_t report_format_get;

    /* Setup file names. */

    memset (&report_format_get, '\0', sizeof (report_format_get));
    report_format_get.id = report_format_uuid (report_format);

    init_report_format_iterator (&formats, &report_format_get);
    if (next (&formats) == FALSE)
      {
        g_free (xml_file);
        cleanup_iterator (&formats);
        return NULL;
      }

    /* Set convenience return parameters. */
    assert (report_format_iterator_extension (&formats));
    assert (report_format_iterator_content_type (&formats));
    if (extension)
      *extension = g_strdup (report_format_iterator_extension (&formats));
    if (content_type)
      *content_type = g_strdup (report_format_iterator_content_type (&formats));

    uuid_format = get_iterator_uuid (&formats);
    if (report_format_global (report_format))
      script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                     "openvasmd",
                                     "global_report_formats",
                                     uuid_format,
                                     NULL);
    else
      {
        gchar *owner;
        owner = sql_string ("SELECT uuid FROM users"
                            " WHERE id = (SELECT owner FROM"
                            "             report_formats WHERE id = %llu);",
                            report_format);
        script_dir = g_build_filename (OPENVAS_STATE_DIR,
                                       "openvasmd",
                                       "report_formats",
                                       owner,
                                       uuid_format,
                                       NULL);
        g_free (owner);
      }

    cleanup_iterator (&formats);

    script = g_build_filename (script_dir, "generate", NULL);

    if (!g_file_test (script, G_FILE_TEST_EXISTS))
      {
        g_free (script);
        g_free (script_dir);
        if (extension) g_free (*extension);
        if (content_type) g_free (*content_type);
        g_free (xml_file);
        return NULL;
      }

    {
      gchar *output_file, *command;
      char *previous_dir;
      int ret;

      /* Change into the script directory. */

      /** @todo NULL arg is glibc extension. */
      previous_dir = getcwd (NULL, 0);
      if (previous_dir == NULL)
        {
          g_warning ("%s: Failed to getcwd: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          g_free (xml_file);
          if (extension) g_free (*extension);
          if (content_type) g_free (*content_type);
          return NULL;
        }

      if (chdir (script_dir))
        {
          g_warning ("%s: Failed to chdir: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          g_free (xml_file);
          if (extension) g_free (*extension);
          if (content_type) g_free (*content_type);
          return NULL;
        }
      g_free (script_dir);

      output_file = g_strdup_printf ("%s/report.out", xml_dir);

      /* Call the script. */

      command = g_strdup_printf ("%s %s > %s"
                                 " 2> /dev/null",
                                 script,
                                 xml_file,
                                 output_file);
      g_free (script);

      g_debug ("   command: %s\n", command);

      if (getuid () == 0)
        {
          pid_t pid;
          struct passwd *nobody;

          /* Run the command with lower privileges in a fork. */

          nobody = getpwnam ("nobody");
          if ((nobody == NULL)
              || chown (xml_dir, nobody->pw_uid, nobody->pw_gid)
              || chown (xml_file, nobody->pw_uid, nobody->pw_gid))
            {
              g_warning ("%s: Failed to set dir permissions: %s\n",
                         __FUNCTION__,
                         strerror (errno));
              g_free (previous_dir);
              g_free (output_file);
              g_free (xml_file);
              if (extension) g_free (*extension);
              if (content_type) g_free (*content_type);
              return NULL;
            }
          g_free (xml_file);

          pid = fork ();
          switch (pid)
            {
              case 0:
                {
                  /* Child.  Drop privileges, run command, exit. */

                  cleanup_manage_process (FALSE);

                  if (setgroups (0,NULL))
                    {
                      g_warning ("%s (child): setgroups: %s\n",
                                 __FUNCTION__, strerror (errno));
                      exit (EXIT_FAILURE);
                    }
                  if (setgid (nobody->pw_gid))
                    {
                      g_warning ("%s (child): setgid: %s\n",
                                 __FUNCTION__,
                                 strerror (errno));
                      exit (EXIT_FAILURE);
                    }
                  if (setuid (nobody->pw_uid))
                    {
                      g_warning ("%s (child): setuid: %s\n",
                                 __FUNCTION__,
                                 strerror (errno));
                      exit (EXIT_FAILURE);
                    }

                  /* RATS: ignore, command is defined above. */
                  ret = system (command);
                  /* Ignore the shell command exit status, because we've not
                   * specified what it must be in the past. */
                  if (ret == -1)
                    {
                      g_warning ("%s (child):"
                                 " system failed with ret %i, %i, %s\n",
                                 __FUNCTION__,
                                 ret,
                                 WEXITSTATUS (ret),
                                 command);
                      exit (EXIT_FAILURE);
                    }

                  exit (EXIT_SUCCESS);
                }

              case -1:
                /* Parent when error. */

                g_warning ("%s: Failed to fork: %s\n",
                           __FUNCTION__,
                           strerror (errno));
                if (chdir (previous_dir))
                  g_warning ("%s: and chdir failed\n",
                             __FUNCTION__);
                g_free (previous_dir);
                g_free (output_file);
                g_free (command);
                if (extension) g_free (*extension);
                if (content_type) g_free (*content_type);
                return NULL;
                break;

              default:
                {
                  int status;

                  /* Parent on success.  Wait for child, and check result. */

                  g_free (command);

                  while (waitpid (pid, &status, 0) < 0)
                    {
                      if (errno == ECHILD)
                        {
                          g_warning ("%s: Failed to get child exit status",
                                     __FUNCTION__);
                          if (chdir (previous_dir))
                            g_warning ("%s: and chdir failed\n",
                                       __FUNCTION__);
                          g_free (previous_dir);
                          g_free (output_file);
                          if (extension) g_free (*extension);
                          if (content_type) g_free (*content_type);
                          return NULL;
                        }
                      if (errno == EINTR)
                        continue;
                      g_warning ("%s: wait: %s",
                                 __FUNCTION__,
                                 strerror (errno));
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      g_free (output_file);
                      if (extension) g_free (*extension);
                      if (content_type) g_free (*content_type);
                      return NULL;
                    }
                  if (WIFEXITED (status))
                    switch (WEXITSTATUS (status))
                      {
                        case EXIT_SUCCESS:
                          break;
                        case EXIT_FAILURE:
                        default:
                          g_warning ("%s: child failed, %s\n",
                                     __FUNCTION__,
                                     command);
                          if (chdir (previous_dir))
                            g_warning ("%s: and chdir failed\n",
                                       __FUNCTION__);
                          g_free (previous_dir);
                          g_free (output_file);
                          if (extension) g_free (*extension);
                          if (content_type) g_free (*content_type);
                          return NULL;
                      }
                  else
                    {
                      g_warning ("%s: child failed, %s\n",
                                 __FUNCTION__,
                                 command);
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      g_free (output_file);
                      if (extension) g_free (*extension);
                      if (content_type) g_free (*content_type);
                      return NULL;
                    }

                  /* Child succeeded, continue to process result. */

                  break;
                }
            }
        }
      else
        {
          /* Just run the command as the current user. */

          /* RATS: ignore, command is defined above. */
          ret = system (command);
          if (ret == -1)
            {
              g_warning ("%s: system failed with ret %i, %i, %s\n",
                         __FUNCTION__,
                         ret,
                         WEXITSTATUS (ret),
                         command);
              if (chdir (previous_dir))
                g_warning ("%s: and chdir failed\n",
                           __FUNCTION__);
              g_free (previous_dir);
              g_free (output_file);
              g_free (command);
              if (extension) g_free (*extension);
              if (content_type) g_free (*content_type);
              return NULL;
            }

          g_free (command);
        }

      {
        GError *get_error;
        gchar *output;
        gsize output_len;

        /* Change back to the previous directory. */

        if (chdir (previous_dir))
          {
            g_warning ("%s: Failed to chdir back: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            g_free (previous_dir);
            g_free (output_file);
            if (extension) g_free (*extension);
            if (content_type) g_free (*content_type);
            return NULL;
          }
        g_free (previous_dir);

        /* Read the script output from file. */

        get_error = NULL;
        g_file_get_contents (output_file,
                             &output,
                             &output_len,
                             &get_error);
        g_free (output_file);
        if (get_error)
          {
            g_warning ("%s: Failed to get output: %s\n",
                       __FUNCTION__,
                       get_error->message);
            g_error_free (get_error);
            if (extension) g_free (*extension);
            if (content_type) g_free (*content_type);
            return NULL;
          }

        /* Remove the directory. */

        openvas_file_remove_recurse (xml_dir);

        /* Return the output. */

        if (output_length) *output_length = output_len;

        return output;
      }
    }
  }
}

/**
 * @brief Size of base64 chunk in manage_send_report.
 */
#define MANAGE_SEND_REPORT_CHUNK64_SIZE 262144

/**
 * @brief Size of file chunk in manage_send_report.
 */
#define MANAGE_SEND_REPORT_CHUNK_SIZE (MANAGE_SEND_REPORT_CHUNK64_SIZE * 3 / 4)

/**
 * @brief Generate a report.
 *
 * @param[in]  report             Report.
 * @param[in]  delta_report       Report to compare with.
 * @param[in]  report_format      Report format.
 * @param[in]  get                GET command data.
 * @param[in]  sort_order         Whether to sort ascending or descending.
 * @param[in]  sort_field         Field to sort on, or NULL for "type".
 * @param[in]  result_hosts_only  Whether to show only hosts with results.
 * @param[in]  min_cvss_base      Minimum CVSS base of included results.  All
 *                                results if NULL.
 * @param[in]  min_qod        Minimum QoD of included results.  All
 *                            results if NULL.
 * @param[in]  levels         String describing threat levels (message types)
 *                            to include in count (for example, "hmlgd" for
 *                            High, Medium, Low, loG and Debug).  All levels if
 *                            NULL.
 * @param[in]  delta_states   String describing delta states to include in count
 *                            (for example, "sngc" Same, New, Gone and Changed).
 *                            All levels if NULL.
 * @param[in]  apply_overrides    Whether to apply overrides.
 * @param[in]  search_phrase      Phrase that results must include.  All results
 *                                if NULL or "".
 * @param[in]  autofp             Whether to apply auto FP filter.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  first_result       The result to start from.  The results are 0
 *                                indexed.
 * @param[in]  max_results        The maximum number of results returned.
 * @param[in]  ignore_pagination  Whether to ignore pagination.
 * @param[in]  base64             Whether to base64 encode the report.
 * @param[in]  send               Function to write to client.
 * @param[in]  send_data_1        Second argument to \p send.
 * @param[in]  send_data_2        Third argument to \p send.
 * @param[in]  alert_id       ID of alert to escalate report with,
 *                                instead of getting report.  NULL to get
 *                                report.
 * @param[in]  type               Type of report: NULL, "scan" or "assets".
 * @param[in]  host               Host or NULL, when type "assets".
 * @param[in]  pos                Position of report from end, when host.  1 for
 *                                last.
 * @param[in]  host_search_phrase  Phrase that results must include.  All results
 *                                 if NULL or "".  For hosts.
 * @param[in]  host_levels         String describing threat levels (message types)
 *                                 to include in count (for example, "hmlgd" for
 *                                 High, Medium, Low, loG and Debug).  All levels if
 *                                 NULL.
 * @param[in]  host_first_result   The host result to start from.  The results
 *                                 are 0 indexed.
 * @param[in]  host_max_results    The host maximum number of results returned.
 * @param[in]  prefix              Text to send to client before the report.
 * @param[in]  zone                Timezone.  NULL or "" for user's default.
 *
 * @return 0 success, -1 error, -2 failed to find alert report format, -3 error
 *         during alert, -4 failed to find alert filter, 1 failed to find alert,
 *         2 failed to find filter (before anything sent to client).
 */
int
manage_send_report (report_t report, report_t delta_report,
                    report_format_t report_format, const get_data_t *get,
                    int sort_order, const char* sort_field,
                    int result_hosts_only, const char *min_cvss_base,
                    const char *min_qod,
                    const char *levels, const char *delta_states,
                    int apply_overrides, const char *search_phrase,
                    int autofp, int notes, int notes_details, int overrides,
                    int overrides_details, int first_result,
                    int max_results, int ignore_pagination, int base64,
                    gboolean (*send) (const char *,
                                      int (*) (const char *, void*),
                                      void*),
                    int (*send_data_1) (const char *, void*), void *send_data_2,
                    const char *alert_id, const char *type,
                    const char *host, int pos, const char *host_search_phrase,
                    const char *host_levels, int host_first_result,
                    int host_max_results, const gchar* prefix,
                    const char *zone)
{
  task_t task;
  gchar *xml_file;
  char xml_dir[] = "/tmp/openvasmd_XXXXXX";
  int ret;

  if (type && (strcmp (type, "assets") == 0))
    task = 0;
  else if (type && (strcmp (type, "prognostic") == 0))
    task = 0;
  else if (type && (strcmp (type, "scan")))
    return -1;
  else if (report_task (report, &task))
    return -1;

  /* Escalate instead, if requested. */

  if (alert_id)
    {
      alert_t alert = 0;
      alert_condition_t condition;
      alert_method_t method;

      if (find_alert_with_permission (alert_id, &alert, "get_alerts"))
        return -1;

      if (alert == 0)
        return 1;

      condition = alert_condition (alert);
      method = alert_method (alert);

      ret = escalate_2 (alert, task, report, EVENT_TASK_RUN_STATUS_CHANGED,
                        (void*) TASK_STATUS_DONE, method, condition,
                        /* Report filtering. */
                        sort_order, sort_field, result_hosts_only,
                        min_cvss_base, min_qod, levels, delta_states,
                        apply_overrides, search_phrase, autofp,
                        notes, notes_details, overrides,
                        overrides_details, first_result, max_results,
                        zone);
      if (ret == -3)
        return -4;
      if (ret == -1)
        return -3;
      if (ret)
        return -2;
      return 0;
    }

  /* Print the report as XML to a file. */

  if ((report_format_predefined (report_format) == 0)
      && (report_format_trust (report_format) != TRUST_YES))
    return -1;

  if (mkdtemp (xml_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
      return -1;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);
  ret = print_report_xml (report, delta_report, task, xml_file, get, sort_order,
                          sort_field, result_hosts_only, min_cvss_base, min_qod,
                          report_format, levels, delta_states, apply_overrides,
                          search_phrase, autofp, notes, notes_details,
                          overrides, overrides_details, first_result,
                          max_results, type, host, pos, host_search_phrase,
                          host_levels, host_first_result, host_max_results,
                          ignore_pagination, zone, NULL, NULL, NULL);
  if (ret)
    {
      g_free (xml_file);
      if (ret == 2)
        return 2;
      return -1;
    }

  /* Pass the file to the report format generate script, sending the output
   * to a file. */

  {
    iterator_t formats;
    const char *uuid_format;
    gchar *script, *script_dir;
    get_data_t report_format_get;

    /* Setup file names. */

    memset (&report_format_get, '\0', sizeof (report_format_get));
    report_format_get.id = report_format_uuid (report_format);

    init_report_format_iterator (&formats, &report_format_get);
    if (next (&formats) == FALSE)
      {
        g_free (xml_file);
        cleanup_iterator (&formats);
        return -1;
      }

    uuid_format = get_iterator_uuid (&formats);
    if (report_format_global (report_format))
      script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                     "openvasmd",
                                     "global_report_formats",
                                     uuid_format,
                                     NULL);
    else
      {
        gchar *owner;
        owner = sql_string ("SELECT uuid FROM users"
                            " WHERE id = (SELECT owner FROM"
                            "             report_formats WHERE id = %llu);",
                            report_format);
        script_dir = g_build_filename (OPENVAS_STATE_DIR,
                                       "openvasmd",
                                       "report_formats",
                                       owner,
                                       uuid_format,
                                       NULL);
        g_free (owner);
      }

    cleanup_iterator (&formats);

    script = g_build_filename (script_dir, "generate", NULL);

    if (!g_file_test (script, G_FILE_TEST_EXISTS))
      {
        g_free (script);
        g_free (script_dir);
        g_free (xml_file);
        return -1;
      }

    {
      gchar *output_file, *command;
      char *previous_dir;
      int ret;

      /* Change into the script directory. */

      /** @todo NULL arg is glibc extension. */
      previous_dir = getcwd (NULL, 0);
      if (previous_dir == NULL)
        {
          g_warning ("%s: Failed to getcwd: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          g_free (xml_file);
          return -1;
        }

      if (chdir (script_dir))
        {
          g_warning ("%s: Failed to chdir: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          g_free (xml_file);
          return -1;
        }
      g_free (script_dir);

      output_file = g_strdup_printf ("%s/report.out", xml_dir);

      /* Call the script. */

      command = g_strdup_printf ("%s %s > %s"
                                 " 2> /dev/null",
                                 script,
                                 xml_file,
                                 output_file);
      g_free (script);

      g_debug ("   command: %s\n", command);

      if (getuid () == 0)
        {
          pid_t pid;
          struct passwd *nobody;

          /* Run the command with lower privileges in a fork. */

          nobody = getpwnam ("nobody");
          if ((nobody == NULL)
              || chown (xml_dir, nobody->pw_uid, nobody->pw_gid)
              || chown (xml_file, nobody->pw_uid, nobody->pw_gid))
            {
              g_warning ("%s: Failed to set dir permissions: %s\n",
                         __FUNCTION__,
                         strerror (errno));
              g_free (previous_dir);
              g_free (xml_file);
              g_free (output_file);
              return -1;
            }

          g_free (xml_file);

          pid = fork ();
          switch (pid)
            {
              case 0:
                {
                  /* Child.  Drop privileges, run command, exit. */

                  cleanup_manage_process (FALSE);

                  if (setgroups (0,NULL))
                    {
                      g_warning ("%s (child): setgroups: %s\n",
                                 __FUNCTION__, strerror (errno));
                      exit (EXIT_FAILURE);
                    }
                  if (setgid (nobody->pw_gid))
                    {
                      g_warning ("%s (child): setgid: %s\n",
                                 __FUNCTION__,
                                 strerror (errno));
                      exit (EXIT_FAILURE);
                    }
                  if (setuid (nobody->pw_uid))
                    {
                      g_warning ("%s (child): setuid: %s\n",
                                 __FUNCTION__,
                                 strerror (errno));
                      exit (EXIT_FAILURE);
                    }

                  /* RATS: ignore, command is defined above. */
                  ret = system (command);
                  /* Ignore the shell command exit status, because we've not
                   * specified what it must be in the past. */
                  if (ret == -1)
                    {
                      g_warning ("%s (child):"
                                 " system failed with ret %i, %i, %s\n",
                                 __FUNCTION__,
                                 ret,
                                 WEXITSTATUS (ret),
                                 command);
                      exit (EXIT_FAILURE);
                    }

                  exit (EXIT_SUCCESS);
                }

              case -1:
                /* Parent when error. */

                g_warning ("%s: Failed to fork: %s\n",
                           __FUNCTION__,
                           strerror (errno));
                if (chdir (previous_dir))
                  g_warning ("%s: and chdir failed\n",
                             __FUNCTION__);
                g_free (previous_dir);
                g_free (output_file);
                g_free (command);
                return -1;
                break;

              default:
                {
                  int status;

                  /* Parent on success.  Wait for child, and check result. */

                  g_free (command);

                  while (waitpid (pid, &status, 0) < 0)
                    {
                      if (errno == ECHILD)
                        {
                          g_warning ("%s: Failed to get child exit status",
                                     __FUNCTION__);
                          if (chdir (previous_dir))
                            g_warning ("%s: and chdir failed\n",
                                       __FUNCTION__);
                          g_free (previous_dir);
                          g_free (output_file);
                          return -1;
                        }
                      if (errno == EINTR)
                        continue;
                      g_warning ("%s: wait: %s",
                                 __FUNCTION__,
                                 strerror (errno));
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      g_free (output_file);
                      return -1;
                    }
                  if (WIFEXITED (status))
                    switch (WEXITSTATUS (status))
                      {
                        case EXIT_SUCCESS:
                          break;
                        case EXIT_FAILURE:
                        default:
                          g_warning ("%s: child failed, %s\n",
                                     __FUNCTION__,
                                     command);
                          if (chdir (previous_dir))
                            g_warning ("%s: and chdir failed\n",
                                       __FUNCTION__);
                          g_free (previous_dir);
                          g_free (output_file);
                          return -1;
                      }
                  else
                    {
                      g_warning ("%s: child failed, %s\n",
                                 __FUNCTION__,
                                 command);
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed\n",
                                   __FUNCTION__);
                      g_free (previous_dir);
                      g_free (output_file);
                      return -1;
                    }

                  /* Child succeeded, continue to process result. */

                  break;
                }
            }
        }
      else
        {
          /* Just run the command as the current user. */

          g_free (xml_file);

          /* RATS: ignore, command is defined above. */
          ret = system (command);
          /* Ignore the shell command exit status, because we've not
           * specified what it must be in the past. */
          if (ret == -1)
            {
              g_warning ("%s: system failed with ret %i, %i, %s\n",
                         __FUNCTION__,
                         ret,
                         WEXITSTATUS (ret),
                         command);
              if (chdir (previous_dir))
                g_warning ("%s: and chdir failed\n",
                           __FUNCTION__);
              g_free (previous_dir);
              g_free (command);
              g_free (output_file);
              return -1;
            }

          g_free (command);
        }

      {
        char chunk[MANAGE_SEND_REPORT_CHUNK_SIZE + 1];
        FILE *stream;

        /* Change back to the previous directory. */

        if (chdir (previous_dir))
          {
            g_warning ("%s: Failed to chdir back: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            g_free (previous_dir);
            g_free (output_file);
            return -1;
          }
        g_free (previous_dir);

        /* Read the script output from file in chunks, sending to client. */

        stream = fopen (output_file, "r");
        g_free (output_file);
        if (stream == NULL)
          {
            g_warning ("%s: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            return -1;
          }

        if (prefix && send (prefix, send_data_1, send_data_2))
          {
            fclose (stream);
            g_warning ("%s: send prefix error\n", __FUNCTION__);
            return -1;
          }

        while (1)
          {
            int left;
            char *dest;

            /* Read a chunk. */

            left = MANAGE_SEND_REPORT_CHUNK_SIZE;
            dest = chunk;
            while (1)
              {
                int ret = fread (dest, 1, left, stream);
                if (ferror (stream))
                  {
                    fclose (stream);
                    g_warning ("%s: error after fread\n", __FUNCTION__);
                    return -1;
                  }
                left -= ret;
                if (left == 0)
                  break;
                if (feof (stream))
                  break;
                dest += ret;
              }

            /* Send the chunk. */

            if (left < MANAGE_SEND_REPORT_CHUNK_SIZE)
              {
                if (base64)
                  {
                    gchar *chunk64;
                    chunk64 = g_base64_encode ((guchar*) chunk,
                                               MANAGE_SEND_REPORT_CHUNK_SIZE
                                                - left);
                    if (send (chunk64, send_data_1, send_data_2))
                      {
                        g_free (chunk64);
                        fclose (stream);
                        g_warning ("%s: send error\n", __FUNCTION__);
                        return -1;
                      }
                    g_free (chunk64);
                  }
                else
                  {
                    chunk[MANAGE_SEND_REPORT_CHUNK_SIZE - left] = '\0';
                    if (send (chunk, send_data_1, send_data_2))
                      {
                        fclose (stream);
                        g_warning ("%s: send error\n", __FUNCTION__);
                        return -1;
                      }
                  }
              }

            /* Check if there's more. */

            if (feof (stream))
              break;
          }

        fclose (stream);

        /* Remove the directory. */

        openvas_file_remove_recurse (xml_dir);

        /* Return the output. */

        return 0;
      }
    }
  }
}


/* More task stuff. */

/** @todo Should be on tasks page above. */

/**
 * @brief Return the number of finished reports associated with a task.
 *
 * @param[in]  task  Task.
 *
 * @return Number of reports.
 */
unsigned int
task_finished_report_count (task_t task)
{
  return (unsigned int) sql_int ("SELECT count(*) FROM reports"
                                 " WHERE task = %llu"
                                 " AND scan_run_status = %u;",
                                 task,
                                 TASK_STATUS_DONE);
}

/**
 * @brief Return the trend of a task, given counts.
 *
 * @param[in]  holes_a   Number of holes on earlier report.
 * @param[in]  warns_a   Number of warnings on earlier report.
 * @param[in]  infos_a   Number of infos on earlier report.
 * @param[in]  severity_a Severity of earlier report.
 * @param[in]  holes_b   Number of holes on later report.
 * @param[in]  warns_b   Number of warnings on later report.
 * @param[in]  infos_b   Number of infos on later report.
 * @param[in]  severity_b Severity of later report.
 *
 * @return "up", "down", "more", "less", "same" or if too few reports "".
 */
static const char *
task_trend_calc (int holes_a, int warns_a, int infos_a, double severity_a,
                 int holes_b, int warns_b, int infos_b, double severity_b)
{
  int threat_a, threat_b;

  /* Check if the severity score changed. */

  if (severity_a > severity_b)
    return "up";

  if (severity_a < severity_b)
    return "down";

  /* Calculate trend. */

  if (holes_a > 0)
    threat_a = 4;
  else if (warns_a > 0)
    threat_a = 3;
  else if (infos_a > 0)
    threat_a = 2;
  else
    threat_a = 1;

  if (holes_b > 0)
    threat_b = 4;
  else if (warns_b > 0)
    threat_b = 3;
  else if (infos_b > 0)
    threat_b = 2;
  else
    threat_b = 1;

  /* Check if the threat level changed. */

  if (threat_a > threat_b)
    return "up";

  if (threat_a < threat_b)
    return "down";

  /* Check if the threat count changed in the highest level. */

  if (holes_a)
    {
      if (holes_a > holes_b)
        return "more";
      if (holes_a < holes_b)
        return "less";
      return "same";
    }

  if (warns_a)
    {
      if (warns_a > warns_b)
        return "more";
      if (warns_a < warns_b)
        return "less";
      return "same";
    }

  if (infos_a)
    {
      if (infos_a > infos_b)
        return "more";
      if (infos_a < infos_b)
        return "less";
      return "same";
    }

  return "same";
}

/**
 * @brief Return the trend of a task, given counts.
 *
 * @param[in]  task      Task.
 * @param[in]  holes_a   Number of holes on earlier report.
 * @param[in]  warns_a   Number of warnings on earlier report.
 * @param[in]  infos_a   Number of infos on earlier report.
 * @param[in]  severity_a Severity score of earlier report.
 * @param[in]  holes_b   Number of holes on later report.
 * @param[in]  warns_b   Number of warnings on later report.
 * @param[in]  infos_b   Number of infos on later report.
 * @param[in]  severity_b  Severity score of later report.
 *
 * @return "up", "down", "more", "less", "same" or if too few reports "".
 */
const char *
task_iterator_trend_counts (iterator_t *iterator, int holes_a, int warns_a,
                            int infos_a, double severity_a, int holes_b,
                            int warns_b, int infos_b, double severity_b)
{
  /* Ensure there are enough reports. */
  if (task_iterator_finished_reports (iterator) <= 1)
    return "";

  /* Skip running tasks. */

  if (task_iterator_run_status (iterator) == TASK_STATUS_RUNNING)
    return "";

  return task_trend_calc (holes_a, warns_a, infos_a, severity_a,
                          holes_b, warns_b, infos_b, severity_b);
}

/**
 * @brief Return the trend of a task.
 *
 * @param[in]  task      Task.
 * @param[in]  override  Whether to override the threat.
 * @param[in]  min_qod   The minimum QoD of results to count.
 *
 * @return "up", "down", "more", "less", "same" or if too few reports "".
 */
const char *
task_trend (task_t task, int override, int min_qod)
{
  report_t last_report, second_last_report;
  int holes_a, warns_a, infos_a, logs_a, false_positives_a;
  int holes_b, warns_b, infos_b, logs_b, false_positives_b;
  double severity_a, severity_b;
  gchar *min_qod_str;

  /* Ensure there are enough reports. */

  if (task_finished_report_count (task) <= 1)
    return "";

  /* Get trend only for authenticated users to avoid
     caching result counts for dummy or NULL users   */
  if (current_credentials.uuid == NULL
      || strcmp (current_credentials.uuid, "") == 0)
    return "";

  /* Skip running and container tasks. */

  if (task_run_status (task) == TASK_STATUS_RUNNING)
    return "";

  if (task_target (task) == 0)
    return NULL;

  /* Get details of last report. */

  task_last_report (task, &last_report);
  if (last_report == 0)
    return "";

  min_qod_str = g_strdup_printf ("%d", min_qod);

  /* Count the logs and false positives too, as report_counts_id is faster
   * with all five. */
  if (report_counts_id (last_report, NULL, &holes_a, &infos_a, &logs_a, &warns_a,
                        &false_positives_a, &severity_a, override, NULL, 0,
                        min_qod_str))
    /** @todo Either fail better or abort at SQL level. */
    abort ();

  /* Get details of second last report. */

  task_second_last_report (task, &second_last_report);
  if (second_last_report == 0)
    {
      g_free (min_qod_str);
      return "";
    }

  /* Count the logs and false positives too, as report_counts_id is faster
   * with all five. */
  if (report_counts_id (second_last_report, NULL, &holes_b, &infos_b, &logs_b,
                        &warns_b, &false_positives_b, &severity_b, override,
                        NULL, 0, min_qod_str))
                        //TODO: Make param
    /** @todo Either fail better or abort at SQL level. */
    abort ();

  g_free (min_qod_str);

  return task_trend_calc (holes_a, warns_a, infos_a, severity_a,
                          holes_b, warns_b, infos_b, severity_b);
}

/**
 * @brief Dummy function.
 */
void
free_tasks ()
{
  /* Empty. */
}

/**
 * @brief Make a task.
 *
 * The char* parameters name and comment are used directly and freed
 * when the task is freed.
 *
 * @param[in]  name     The name of the task.
 * @param[in]  comment  A comment associated the task.
 *
 * @return A pointer to the new task.
 */
task_t
make_task (char* name, char* comment)
{
  task_t task;
  char* uuid = openvas_uuid_make ();
  gchar *quoted_name, *quoted_comment;
  if (uuid == NULL) abort ();
  quoted_name = name ? sql_quote ((gchar*) name) : NULL;
  quoted_comment = comment ? sql_quote ((gchar*) comment) : NULL;
  sql ("INSERT into tasks"
       " (owner, uuid, name, hidden, comment, schedule,"
       "  schedule_next_time, slave, config_location, target_location,"
       "  schedule_location, slave_location, alterable, creation_time,"
       "  modification_time)"
       " VALUES ((SELECT id FROM users WHERE users.uuid = '%s'),"
       "         '%s', '%s', 0, '%s', 0, 0, 0, 0, 0, 0, 0, 0, m_now (),"
       "         m_now ());",
       current_credentials.uuid,
       uuid,
       quoted_name ? quoted_name : "",
       quoted_comment ? quoted_comment : "");
  task = sql_last_insert_id ();
  set_task_run_status (task, TASK_STATUS_NEW);
  sql ("INSERT INTO task_preferences (task, name, value)"
       " VALUES (%llu, 'in_assets', 'yes')",
       task);
  free (uuid);
  free (name);
  free (comment);
  g_free (quoted_name);
  g_free (quoted_comment);
  return task;
}

/**
 * @brief Complete the creation of a task.
 *
 * @param[in]  uuid     The UUID of the task.
 */
void
make_task_complete (const char *uuid)
{
  task_t task;

  if (find_task (uuid, &task))
    return;

  if (task == 0)
    return;

  event (task, EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_NEW);
}

#ifdef S_SPLINT_S
typedef /*@only@*/ struct dirent * only_dirent_pointer;
#endif

/**
 * @brief Dummy function.
 *
 * @return 0.
 */
int
load_tasks ()
{
  return 0;
}

/**
 * @brief Dummy function.
 *
 * @return 0.
 */
int
save_tasks ()
{
  return 0;
}

/**
 * @brief Set a task parameter.
 *
 * The "value" parameter is used directly and freed either immediately or
 * when the task is freed.
 *
 * @param[in]  task       A pointer to a task.
 * @param[in]  parameter  The name of the parameter (in any case): NAME
 *                        or COMMENT.
 * @param[in]  value      The value of the parameter.
 *
 * @return 0 on success, -2 if parameter name error, -3 value error (NULL).
 */
int
set_task_parameter (task_t task, const char* parameter, /*@only@*/ char* value)
{
  /** @todo Free value consistently. */

  tracef ("   set_task_parameter %u %s\n",
          task_id (task),
          parameter ? parameter : "(null)");
  if (value == NULL) return -3;
  if (parameter == NULL)
    {
      free (value);
      return -2;
    }
  else if (strcasecmp ("NAME", parameter) == 0)
    {
      gchar* quoted_value = sql_quote (value ?: "");
      sql ("UPDATE tasks SET name = '%s', modification_time = m_now ()"
           " WHERE id = %llu;", quoted_value, task);
      g_free (quoted_value);
    }
  else if (strcasecmp ("COMMENT", parameter) == 0)
    {
      gchar* quote = sql_nquote (value, strlen (value));
      sql ("UPDATE tasks SET comment = '%s', modification_time = m_now ()"
           " WHERE id = %llu;",
           quote,
           task);
      g_free (quote);
    }
  else
    {
      free (value);
      return -2;
    }
  return 0;
}

/**
 * @brief Create a task from an existing task.
 *
 * @param[in]  name        Name of new task.  NULL to copy from existing.
 * @param[in]  comment     Comment on new task.  NULL to copy from existing.
 * @param[in]  task_id     UUID of existing task.
 * @param[in]  alterable   Whether the new task will be alterable.
 * @param[out] new_task    New task.
 *
 * @return 0 success, 2 failed to find existing task, 99 permission denied,
 *         -1 error.
 */
int
copy_task (const char* name, const char* comment, const char *task_id,
           int alterable, task_t* new_task)
{
  task_t new, old;
  int ret;

  assert (current_credentials.uuid);

  if (task_id == NULL)
    return -1;

  sql_begin_immediate ();

  // FIX task names are allowed to clash
  ret = copy_resource_lock ("task", name, comment, task_id,
                            "config, target, schedule,"
                            " schedule_next_time, slave, config_location,"
                            " target_location, schedule_location,"
                            " slave_location, hosts_ordering, scanner",
                            1, &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("UPDATE tasks SET alterable = %i, hidden = 0 WHERE id = %llu;",
       alterable,
       new);

  set_task_run_status (new, TASK_STATUS_NEW);
  sql ("INSERT INTO task_preferences (task, name, value)"
       " SELECT %llu, name, value FROM task_preferences"
       " WHERE task = %llu;",
       new,
       old);

  sql ("INSERT INTO task_alerts (task, alert, alert_location)"
       " SELECT %llu, alert, alert_location FROM task_alerts"
       " WHERE task = %llu;",
       new,
       old);

  // FIX do this for all types, or none
  sql ("INSERT INTO permissions"
       " (uuid, owner, name, comment, resource_type, resource, resource_uuid,"
       "  resource_location, subject_type, subject, subject_location,"
       "  creation_time, modification_time)"
       " SELECT make_uuid (), (SELECT owner FROM tasks WHERE id = %llu),"
       "        name, comment, resource_type, %llu,"
       "        (SELECT uuid FROM tasks WHERE id = %llu),"
       "        resource_location, subject_type, subject, subject_location,"
       "        m_now (), m_now ()"
       " FROM permissions"
       " WHERE owner = (SELECT owner FROM tasks WHERE id = %llu)"
       " AND resource_type = 'task'"
       " AND resource_location = " G_STRINGIFY (LOCATION_TABLE)
       " AND resource = %llu;",
       new,
       new,
       new,
       old,
       old);

  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("COMMIT;");
  if (new_task) *new_task = new;
  return 0;
}

/**
 * @brief Request deletion of a task.
 *
 * Stop the task beforehand with \ref stop_task_internal, if it is running.
 *
 * Used only for CREATE_TASK in omp.c.  Always ultimate.
 *
 * @param[in]  task_pointer  A pointer to the task.
 *
 * @return 0 if deleted, 1 if delete requested, 2 if task is hidden,
 *         -1 if error, -5 if scanner is down.
 */
int
request_delete_task (task_t* task_pointer)
{
  task_t task = *task_pointer;
  int hidden;

  tracef ("   request delete task %u\n", task_id (task));

  hidden = sql_int ("SELECT hidden from tasks WHERE id = %llu;",
                    *task_pointer);

  if (hidden == 1)
    return 2;

  /* Technically the task could be in the trashcan, if someone gets the UUID
   * with GET_TASKS before the CREATE_TASK finishes, and removes the task.
   * Pretend it was deleted.  There'll be half a task in the trashcan. */
  if (hidden == 2)
    return 0;

  if (current_credentials.uuid == NULL) return -1;

  switch (stop_task_internal (task))
    {
      case 0:    /* Stopped. */
        return delete_task_lock (task, 1);
      case 1:    /* Stop requested. */
        set_task_run_status (task, TASK_STATUS_DELETE_ULTIMATE_REQUESTED);
        return 1;
      default:   /* Programming error. */
        assert (0);
      case -1:   /* Error. */
        return -1;
        break;
      case -5:   /* Scanner down. */
        return -5;
        break;
    }

  return 0;
}

static gboolean
find_trash_task (const char*, task_t*);

/**
 * @brief Request deletion of a task.
 *
 * Stop the task beforehand with \ref stop_task_internal, if it is running.
 *
 * @param[in]  task_id   UUID of task.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 if deleted, 1 if delete requested, 2 if task is hidden, 3 failed
 *         to find task, 99 permission denied, -1 if error, -5 if scanner is
 *         down.
 */
int
request_delete_task_uuid (const char *task_id, int ultimate)
{
  task_t task = 0;

  /* Tasks have special handling for the trashcan.  Other resources have trash
   * tables, like targets_trash.  Tasks are marked as trash in the tasks table
   * by giving the "hidden" field a value of 2.  This means that the results can
   * stay in the results table and will still refer to the correct task.  This
   * should all work because there is already handling of the hidden flag
   * everywhere else. */

  tracef ("   request delete task %s\n", task_id);

  sql_begin_immediate ();

  if (user_may ("delete_task") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_task_with_permission (task_id, &task, "delete_task"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (task == 0)
    {
      if (find_trash_task (task_id, &task))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (task == 0)
        {
          sql ("ROLLBACK;");
          return 3;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      if (delete_reports (task))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      permissions_set_orphans ("task", task, LOCATION_TRASH);
      tags_set_orphans ("task", task, LOCATION_TRASH);

      sql ("DELETE FROM results WHERE task = %llu;", task);
      sql ("DELETE FROM task_alerts WHERE task = %llu;", task);
      sql ("DELETE FROM task_files WHERE task = %llu;", task);
      sql ("DELETE FROM task_preferences WHERE task = %llu;", task);
      sql ("DELETE FROM tasks WHERE id = %llu;", task);
      sql ("COMMIT;");
      return 0;
    }

  if (sql_int ("SELECT hidden from tasks WHERE id = %llu;",
               task)
      == 1)
    {
      sql ("ROLLBACK;");
      return 2;
    }

  if (current_credentials.uuid == NULL)
    {
      sql ("ROLLBACK;");
      return -1;
    }

  switch (stop_task_internal (task))
    {
      case 0:    /* Stopped. */
        {
          int ret;
          ret = delete_task (task, ultimate);
          if (ret)
            sql ("ROLLBACK;");
          else
            sql ("COMMIT;");
          return ret;
        }
      case 1:    /* Stop requested. */
        if (ultimate)
          set_task_run_status (task,
                               TASK_STATUS_DELETE_ULTIMATE_REQUESTED);
        else
          set_task_run_status (task,
                               TASK_STATUS_DELETE_REQUESTED);
        sql ("COMMIT;");
        return 1;
      default:   /* Programming error. */
        assert (0);
      case -1:   /* Error. */
        sql ("ROLLBACK;");
        return -1;
        break;
      case -5:   /* Scanner down. */
        sql ("ROLLBACK;");
        return -5;
        break;
    }

  /*@notreached@*/
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Complete deletion of a task.
 *
 * The caller must do the locking, and must do the hidden check.
 *
 * The caller must handle the case where the task is already in the trashcan.
 *
 * @param[in]  task      The task.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 on success, 1 if task is hidden, -1 on error.
 */
int
delete_task (task_t task, int ultimate)
{
  tracef ("   delete task %llu\n", task);

  /** @todo Many other places just assert this. */
  if (current_credentials.uuid == NULL)
    return -1;

  if (ultimate)
    {
      if (delete_reports (task))
        return -1;

      permissions_set_orphans ("task", task,
                               task_in_trash (task)
                                ? LOCATION_TRASH
                                : LOCATION_TABLE);
      tags_set_orphans ("task", task,
                        task_in_trash (task)
                          ? LOCATION_TRASH
                          : LOCATION_TABLE);

      sql ("DELETE FROM results WHERE task = %llu;", task);
      sql ("DELETE FROM task_alerts WHERE task = %llu;", task);
      sql ("DELETE FROM task_files WHERE task = %llu;", task);
      sql ("DELETE FROM task_preferences WHERE task = %llu;", task);
      sql ("DELETE FROM tasks WHERE id = %llu;", task);
    }
  else
    {
      permissions_set_locations ("task", task, task, LOCATION_TRASH);
      tags_set_locations ("task", task, task, LOCATION_TRASH);
      sql ("UPDATE tags SET resource_location = "
           G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'report'"
           " AND resource IN (SELECT id FROM reports"
           "                  WHERE reports.task = %llu);",
           task);
      sql ("UPDATE tags SET resource_location = "
           G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT results.id FROM results"
           "                  WHERE results.task = %llu);",
           task);

      sql ("UPDATE tags_trash SET resource_location = "
           G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'report'"
           " AND resource IN (SELECT id FROM reports"
           "                  WHERE reports.task = %llu);",
           task);
      sql ("UPDATE tags_trash SET resource_location = "
           G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT results.id FROM results"
           "                  WHERE results.task = %llu);",
           task);

      sql ("UPDATE tasks SET hidden = 2 WHERE id = %llu;", task);
    }

  return 0;
}

/**
 * @brief Complete deletion of a task.
 *
 * This sets up a transaction around the delete.
 *
 * @param[in]  task      The task.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 on success, 1 if task is hidden, -1 on error.
 */
int
delete_task_lock (task_t task, int ultimate)
{
  int ret;

  tracef ("   delete task %llu\n", task);

  sql_begin_exclusive ();

  if (sql_int ("SELECT hidden FROM tasks WHERE id = %llu;", task))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  /** @todo Many other places just assert this. */
  if (current_credentials.uuid == NULL)
    {
      sql ("ROLLBACK;");
      return -1;
    }

  ret = delete_task (task, ultimate);
  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");
  return ret;
}

/**
 * @brief Delete all trash tasks.
 *
 * The caller must do the transaction.
 *
 * @return 0 on success, -1 on error.
 */
static int
delete_trash_tasks ()
{
  iterator_t tasks;

  init_user_task_iterator (&tasks, 1);
  while (next (&tasks))
    {
      task_t task;

      task = get_iterator_resource (&tasks);

      if (delete_reports (task))
        {
          cleanup_iterator (&tasks);
          return -1;
        }

      sql ("DELETE FROM results WHERE task = %llu;", task);
      sql ("DELETE FROM task_alerts WHERE task = %llu;", task);
      sql ("DELETE FROM task_files WHERE task = %llu;", task);
      sql ("DELETE FROM task_preferences WHERE task = %llu;", task);
      sql ("DELETE FROM tasks WHERE id = %llu;", task);
    }
  cleanup_iterator (&tasks);

  return 0;
}

/**
 * @brief Append text to the comment associated with a task.
 *
 * @param[in]  task    A pointer to the task.
 * @param[in]  text    The text to append.
 * @param[in]  length  Length of the text.
 */
void
append_to_task_comment (task_t task, const char* text, /*@unused@*/ int length)
{
  append_to_task_string (task, "comment", text);
}

/**
 * @brief Append text to the name associated with a task.
 *
 * @param[in]  task    A pointer to the task.
 * @param[in]  text    The text to append.
 * @param[in]  length  Length of the text.
 */
void
append_to_task_name (task_t task, const char* text, /*@unused@*/ int length)
{
  append_to_task_string (task, "name", text);
}

/**
 * @brief Set the ports for a particular host in a scan.
 *
 * @param[in]  report   Report associated with scan.
 * @param[in]  host     Host.
 * @param[in]  current  New value for port currently being scanned.
 * @param[in]  max      New value for last port to be scanned.
 */
void
set_scan_ports (report_t report, const char* host, unsigned int current,
                unsigned int max)
{
  sql ("UPDATE report_hosts SET current_port = %i, max_port = %i"
       " WHERE host = '%s' AND report = %llu;",
       current, max, host, report);
}

/**
 * @brief Find a task given an identifier.
 *
 * @param[in]   uuid  A task identifier.
 * @param[out]  task  Task return, 0 if succesfully failed to find task.
 *
 * @return FALSE on success (including if failed to find task), TRUE on error.
 */
gboolean
find_task (const char* uuid, task_t* task)
{
  if (user_owns_uuid ("task", uuid, 0) == 0)
    {
      *task = 0;
      return FALSE;
    }
  switch (sql_int64 (task,
                     "SELECT id FROM tasks WHERE uuid = '%s'"
                     " AND hidden != 2;",
                     uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *task = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return TRUE;
        break;
    }

  return FALSE;
}

/**
 * @brief Find a task for a specific permission, given a UUID.
 *
 * @param[in]   uuid      UUID of task.
 * @param[out]  task      Task return, 0 if succesfully failed to find task.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find task), TRUE on error.
 */
gboolean
find_task_with_permission (const char* uuid, task_t* task,
                           const char *permission)
{
  return find_resource_with_permission ("task", uuid, task, permission, 0);
}

/**
 * @brief Find a task in the trashcan for a specific permission, given a UUID.
 *
 * @param[in]   uuid      UUID of task.
 * @param[out]  task      Task return, 0 if succesfully failed to find task.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find task), TRUE on error.
 */
gboolean
find_trash_task_with_permission (const char* uuid, task_t* task,
                                 const char *permission)
{
  return find_resource_with_permission ("task", uuid, task, permission, 1);
}

/**
 * @brief Find a task in the trashcan, given an identifier.
 *
 * @param[in]   uuid  A task identifier.
 * @param[out]  task  Task return, 0 if succesfully failed to find task.
 *
 * @return FALSE on success (including if failed to find task), TRUE on error.
 */
static gboolean
find_trash_task (const char* uuid, task_t* task)
{
  if (user_owns_uuid ("task", uuid, 1) == 0)
    {
      *task = 0;
      return FALSE;
    }
  switch (sql_int64 (task,
                     "SELECT id FROM tasks WHERE uuid = '%s'"
                     " AND hidden = 2;",
                     uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *task = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return TRUE;
        break;
    }

  return FALSE;
}

/**
 * @brief Find a report for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of report.
 * @param[out]  report      Report return, 0 if succesfully failed to find
 *                          report.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find report), TRUE on error.
 */
gboolean
find_report_with_permission (const char* uuid, report_t* report,
                             const char *permission)
{
  return find_resource_with_permission ("report", uuid, report, permission, 0);
}

/**
 * @brief Reset all running information for a task.
 *
 * @param[in]  task  Task.
 */
void
reset_task (task_t task)
{
  sql ("UPDATE tasks SET"
       " start_time = 0,"
       " end_time = 0"
       " WHERE id = %llu;",
       task);
}

/**
 * @brief Add a file to a task, or update the file on the task.
 *
 * @param[in]  task     Task.
 * @param[in]  name     Name of file.
 * @param[in]  content  Content for file in base64 encoding.
 */
void
manage_task_update_file (task_t task, const char *name,
                         const void *content)
{
  gchar* quoted_name = sql_quote (name);
  gchar* quoted_content = sql_quote (content);

  /** @todo Probably better to save ASCII instead of base64. */

  if (sql_int ("SELECT count(*) FROM task_files"
               " WHERE task = %llu AND name = '%s';",
               task,
               quoted_name))
    {
      /* Update the existing file. */

      sql ("UPDATE task_files SET content = '%s'"
           " WHERE task = %llu AND name = '%s';",
           quoted_content,
           task,
           quoted_name);
    }
  else
    {
      /* Insert the file. */

      sql ("INSERT INTO task_files (task, name, content)"
           " VALUES (%llu, '%s', '%s');",
           task,
           quoted_name,
           quoted_content);
    }

  sql ("UPDATE tasks SET modification_time = m_now () WHERE id = %llu;",
       task);

  g_free (quoted_name);
  g_free (quoted_content);
}

/**
 * @brief Remove a file on a task.
 *
 * @param[in]  task     Task.
 * @param[in]  name     Name of file.
 *
 * @return 0 success, -1 error.
 */
int
manage_task_remove_file (task_t task, const char *name)
{
  if (sql_int ("SELECT count(*) FROM task_files"
               " WHERE task = %llu AND name = '%s';",
               task))
    {
      gchar* quoted_name = sql_quote (name);
      sql ("DELETE FROM task_files WHERE task = %llu AND name = '%s';",
           task,
           quoted_name);
      sql ("UPDATE tasks SET modification_time = m_now () WHERE id = %llu;",
           task);
      g_free (quoted_name);
      return 0;
    }
  return -1;
}


/**
 * @brief Initialise a task file iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 * @param[in]  file      File name, NULL for all files.
 */
void
init_task_file_iterator (iterator_t* iterator, task_t task, const char* file)
{
  gchar* sql;
  if (file)
    {
      gchar *quoted_file = sql_nquote (file, strlen (file));
      sql = g_strdup_printf ("SELECT name, content, length(content)"
                             " FROM task_files"
                             " WHERE task = %llu"
                             " AND name = '%s';",
                             task, quoted_file);
      g_free (quoted_file);
    }
  else
    sql = g_strdup_printf ("SELECT name, content, length(content)"
                           " FROM task_files"
                           " WHERE task = %llu;",
                           task);
  init_iterator (iterator, sql);
  g_free (sql);
}

/**
 * @brief Get the name of the file from a task file iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of the file or NULL if iteration is complete.
 */
DEF_ACCESS (task_file_iterator_name, 0);

/**
 * @brief Get the content of the file from a task file iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Content of the file or NULL if iteration is complete.
 */
DEF_ACCESS (task_file_iterator_content, 1);

/* Targets. */

/**
 * @brief Get the maximum allowed number of hosts per target.
 *
 * @return Maximum.
 */
int
manage_max_hosts ()
{
  return max_hosts;
}

/**
 * @brief Set the maximum allowed number of hosts per target.
 *
 * @param[in]   new_max   New max_hosts value.
 */
void
manage_set_max_hosts (int new_max)
{
  max_hosts = new_max;
}

/**
 * @brief Find a target for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of target.
 * @param[out]  target      Target return, 0 if succesfully failed to find target.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find target), TRUE on error.
 */
gboolean
find_target_with_permission (const char* uuid, target_t* target,
                             const char *permission)
{
  return find_resource_with_permission ("target", uuid, target, permission, 0);
}

/**
 * @brief Return number of hosts described by a hosts string.
 *
 * @param[in]  given_hosts      String describing hosts.
 * @param[in]  exclude_hosts    String describing hosts excluded from given set.
 *
 * @return Number of hosts, or -1 on error.
 */
int
manage_count_hosts (const char *given_hosts, const char *exclude_hosts)
{
  int count;
  openvas_hosts_t *hosts;

  hosts = openvas_hosts_new_with_max (given_hosts, manage_max_hosts ());
  if (hosts == NULL)
    return -1;

  if (exclude_hosts)
    /* Don't resolve hostnames in excluded hosts. */
    openvas_hosts_exclude (hosts, exclude_hosts, 0);

  count = openvas_hosts_count (hosts);
  openvas_hosts_free (hosts);

  return count;
}

/**
 * @brief Trim leading and trailing space from a hosts string.
 *
 * @param[in]  string  String.  May be modified.
 *
 * @return Either string or some address within string.
 */
static gchar *
trim_hosts (gchar *string)
{
  gchar *host, *end;

  /* Trim leading and trailing space. */
  host = string;
  while ((*host == ' ') || (*host == '\t'))
    host++;
  end = host;
  while (*end)
    {
      if ((*end == ' ') || (*end == '\t'))
        {
          *end = '\0';
          break;
        }
      end++;
    }
  return host;
}

/**
 * @brief Clean a hosts string.
 *
 * @param[in]  given_hosts  String describing hosts.
 * @param[out] max          Max number of hosts, adjusted for duplicates.
 *
 * @return Freshly allocated new hosts string, or NULL on error.
 */
gchar*
clean_hosts (const char *given_hosts, int *max)
{
  array_t *clean_array;
  GString *clean;
  gchar **split, **point, *hosts, *hosts_start, *host;
  guint index;

  /* Treat newlines like commas. */
  hosts = hosts_start = g_strdup (given_hosts);
  while (*hosts)
    {
      if (*hosts == '\n') *hosts = ',';
      hosts++;
    }

  split = g_strsplit (hosts_start, ",", 0);
  g_free (hosts_start);
  point = split;

  if ((point == NULL) || (*point == NULL))
    {
      g_strfreev (split);
      return g_strdup ("");
    }

  clean_array = make_array ();
  while (*point)
    {
      host = trim_hosts (*point);

      if (*host)
        {
          /* Prevent simple duplicates. */
          if (array_find_string (clean_array, host) == NULL)
            array_add (clean_array, host);
          else if (max)
            (*max)--;
        }

      point += 1;
    }

  clean = g_string_new ("");

  host = (gchar*) g_ptr_array_index (clean_array, 0);
  if (host)
    g_string_append_printf (clean, "%s", host);

  for (index = 1; index < clean_array->len; index++)
    {
      host = (gchar*) g_ptr_array_index (clean_array, index);
      if (host)
        g_string_append_printf (clean, ", %s", host);
    }

  return g_string_free (clean, FALSE);
}

/**
 * @brief Start a new IMMEDIATE transaction.
 */
void
manage_transaction_start ()
{
  if (!in_transaction)
    {
      sql_begin_immediate ();
      in_transaction = TRUE;
    }
  gettimeofday (&last_msg, NULL);
}

/**
 * @brief Commit the current transaction, if any.
 *
 * The algorithm is extremely naive (time elapsed since the last message
 * was received) but delivers good enough performances when facing
 * bursts of messages.
 *
 * @param[in] force_commit  Force committing the pending transaction.
 */
void manage_transaction_stop (gboolean force_commit)
{
  struct timeval now;

  if (!in_transaction)
    return;

  gettimeofday (&now, NULL);
  if (force_commit || TIMEVAL_SUBTRACT_MS (now, last_msg) >= 500)
    {
      sql ("COMMIT;");
      in_transaction = FALSE;
    }
}

/**
 * @brief Validate a single port.
 *
 * @param[in]   port      A port.
 *
 * @return 0 success, 1 failed.
 */
static int
validate_port (const char *port)
{
  const char *first;

  while (*port && isblank (*port)) port++;
  if (*port == '\0')
    return 1;

  first = port;
  while (*first && isdigit (*first)) first++;
  if (first == port)
    return 1;

  while (*first && isblank (*first)) first++;
  if (*first == '\0')
    {
      long int number;
      number = strtol (port, NULL, 10);
      if (number <= 0)
        return 1;
      if (number > 65535)
        return 1;
      return 0;
    }
  return 1;
}

/**
 * @brief Validate a single port.
 * @brief May come in values such as 100/foo and 100/foo (IANA: bar).
 * @brief Will also validate values such as: general/tcp.
 *
 * @param[in]   port      A port.
 *
 * @return 0 success, 1 failed.
 */
static int
validate_results_port (const char *port)
{
  int num;
  char *buff;

  if (!port)
    return 1;

  if (strncmp ("general/", port, 8) == 0)
    return 0;

  num = atoi (port);
  if (num > 0 && num < 65535)
    return 0;

  buff = g_newa (char, strlen (port));
  sscanf (port, "%s (%i/%s)", buff, &num, buff);
  if (num > 0 && num < 65535)
    return 0;

  return 1;
}

/**
 * @brief Convert alive test name to alive test bitfield.
 *
 * @param[in]  alive_tests  Name of alive test.
 *
 * @return Alive test, or -1 on error.
 */
static int
alive_test_from_string (const char* alive_tests)
{
  alive_test_t alive_test;
  if (alive_tests == NULL
      || strcmp (alive_tests, "") == 0
      || strcmp (alive_tests, "Scan Config Default") == 0)
    alive_test = 0;
  else if (strcmp (alive_tests, "ICMP, TCP-ACK Service & ARP Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ICMP | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "TCP-ACK Service & ARP Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "ICMP & ARP Ping") == 0)
    alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "ICMP & TCP-ACK Service Ping") == 0)
    alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_TCP_ACK_SERVICE;
  else if (strcmp (alive_tests, "ARP Ping") == 0)
    alive_test = ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "TCP-ACK Service Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE;
  else if (strcmp (alive_tests, "TCP-SYN Service Ping") == 0)
    alive_test = ALIVE_TEST_TCP_SYN_SERVICE;
  else if (strcmp (alive_tests, "ICMP Ping") == 0)
    alive_test = ALIVE_TEST_ICMP;
  else if (strcmp (alive_tests, "Consider Alive") == 0)
    alive_test = ALIVE_TEST_CONSIDER_ALIVE;
  else
    return -1;
  return alive_test;
}

/**
 * @brief Create a target.
 *
 * @param[in]   name            Name of target.
 * @param[in]   hosts           Host list of target.
 * @param[in]   exclude_hosts   List of hosts to exclude from \p hosts.
 * @param[in]   comment         Comment on target.
 * @param[in]   port_list_id    Port list of target (overrides \p port_range).
 * @param[in]   port_range      Port range of target.
 * @param[in]   ssh_lsc_credential  SSH LSC credential.
 * @param[in]   ssh_port        Port for SSH LSC login.
 * @param[in]   smb_lsc_credential  SMB LSC credential.
 * @param[in]   esxi_lsc_credential ESXi LSC credential.
 * @param[in]   reverse_lookup_only   Scanner preference reverse_lookup_only.
 * @param[in]   reverse_lookup_unify  Scanner preference reverse_lookup_unify.
 * @param[in]   alive_tests     Alive tests.
 * @param[in]   make_name_unique  Whether to make name unique.
 * @param[out]  target          Created target.
 *
 * @return 0 success, 1 target exists already, 2 error in host specification,
 *         3 too many hosts, 4 error in port range, 5 error in SSH port,
 *         6 failed to find port list, 7 error in alive tests,
 *         99 permission denied, -1 error.
 */
int
create_target (const char* name, const char* hosts, const char* exclude_hosts,
               const char* comment, const char* port_list_id,
               const char* port_range, lsc_credential_t ssh_lsc_credential,
               const char* ssh_port, lsc_credential_t smb_lsc_credential,
               lsc_credential_t esxi_lsc_credential,
               const char *reverse_lookup_only,
               const char *reverse_lookup_unify, const char *alive_tests,
               int make_name_unique, target_t* target)
{
  gchar *quoted_name, *quoted_hosts, *quoted_exclude_hosts, *quoted_comment;
  gchar *port_list_comment, *quoted_ssh_port, *clean;
  port_list_t port_list;
  int ret, alive_test, max;

  assert (current_credentials.uuid);

  if (port_range && validate_port_range (port_range))
    return 4;

  if (ssh_port && validate_port (ssh_port))
    return 5;

  alive_test = alive_test_from_string (alive_tests);
  if (alive_test <= -1)
    return 7;

  sql_begin_immediate ();

  if (user_may ("create_target") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (make_name_unique)
    {
      int suffix;
      /* Ensure the name is unique. */
      quoted_name = sql_quote (name);
      suffix = 1;
      while (resource_with_name_exists (quoted_name, "target", 0))
        {
          gchar *new_name;
          g_free (quoted_name);
          new_name = g_strdup_printf ("%s %i", name, suffix++);
          quoted_name = sql_quote (new_name);
          g_free (new_name);
        }
    }
  else
    {
      if (resource_with_name_exists (name, "target", 0))
        {
          sql ("ROLLBACK;");
          return 1;
        }
    }
  quoted_name = sql_quote (name);
  quoted_exclude_hosts = exclude_hosts ? sql_quote (exclude_hosts)
                                       : g_strdup ("");

  max = manage_count_hosts (hosts, quoted_exclude_hosts);
  if (max <= 0)
    {
      g_free (quoted_exclude_hosts);
      g_free (quoted_name);
      sql ("ROLLBACK;");
      return 2;
    }
  clean = clean_hosts (hosts, &max);
  if (max > max_hosts)
    {
      g_free (quoted_exclude_hosts);
      g_free (quoted_name);
      sql ("ROLLBACK;");
      return 3;
    }
  quoted_hosts = sql_quote (clean);
  g_free (clean);

  if (port_list_id)
    {
      if (find_port_list_with_permission (port_list_id, &port_list,
                                          "get_port_lists")
          || (port_list == 0))
        {
          g_free (quoted_name);
          return 6;
        }
    }
  else
    {
      port_list_comment = g_strdup_printf ("Autogenerated for target %s.", name);
      ret = create_port_list_unique (name, port_list_comment, port_range,
                                     &port_list);
      g_free (port_list_comment);
      if (ret)
        {
          g_free (quoted_name);
          sql ("ROLLBACK;");
          return ret;
        }
    }

  if (ssh_lsc_credential)
    quoted_ssh_port = sql_insert (ssh_port ? ssh_port : "22");
  else
    quoted_ssh_port = g_strdup ("NULL");

  if (reverse_lookup_only == NULL || strcmp (reverse_lookup_only, "0") == 0)
    reverse_lookup_only = "0";
  else
    reverse_lookup_only = "1";
  if (reverse_lookup_unify == NULL || strcmp (reverse_lookup_unify, "0") == 0)
    reverse_lookup_unify = "0";
  else
    reverse_lookup_unify = "1";

  if (comment)
    {
      quoted_comment = sql_nquote (comment, strlen (comment));
      sql ("INSERT INTO targets"
           " (uuid, name, owner, hosts, exclude_hosts, comment, lsc_credential,"
           "  ssh_port, smb_lsc_credential, esxi_lsc_credential,"
           "  port_range, reverse_lookup_only,"
           "  reverse_lookup_unify, alive_test, creation_time,"
           "  modification_time)"
           " VALUES (make_uuid (), '%s',"
           " (SELECT id FROM users WHERE users.uuid = '%s'), '%s',"
           " '%s', '%s', %llu, %s, %llu, %llu, %llu, '%s', '%s', %i,"
           " m_now (), m_now ());",
           quoted_name, current_credentials.uuid, quoted_hosts,
           quoted_exclude_hosts, quoted_comment, ssh_lsc_credential,
           quoted_ssh_port, smb_lsc_credential, esxi_lsc_credential,
           port_list, reverse_lookup_only, reverse_lookup_unify, alive_test);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO targets"
         " (uuid, name, owner, hosts, exclude_hosts, comment, lsc_credential,"
         "  ssh_port, smb_lsc_credential, esxi_lsc_credential,"
         "  port_range, reverse_lookup_only,"
         "  reverse_lookup_unify, alive_test, creation_time, modification_time)"
         " VALUES (make_uuid (), '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '%s', '%s', '', %llu, %s, %llu, %llu, %llu, '%s', '%s', %i,"
         " m_now (), m_now ());",
         quoted_name, current_credentials.uuid, quoted_hosts,
         quoted_exclude_hosts, quoted_exclude_hosts, ssh_lsc_credential,
         quoted_ssh_port, smb_lsc_credential, esxi_lsc_credential,
         port_list, reverse_lookup_only, reverse_lookup_unify, alive_test);

  if (target)
    *target = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_hosts);
  g_free (quoted_exclude_hosts);
  g_free (quoted_ssh_port);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a target from an existing target.
 *
 * @param[in]  name        Name of new target.  NULL to copy from existing.
 * @param[in]  comment     Comment on new target.  NULL to copy from existing.
 * @param[in]  target_id   UUID of existing target.
 * @param[out] new_target  New target.
 *
 * @return 0 success, 1 target exists already, 2 failed to find existing
 *         target, 99 permission denied, -1 error.
 */
int
copy_target (const char* name, const char* comment, const char *target_id,
             target_t* new_target)
{
  return copy_resource ("target", name, comment, target_id,
                        "hosts, exclude_hosts, lsc_credential, ssh_port,"
                        " smb_lsc_credential, esxi_lsc_credential, port_range,"
                        " reverse_lookup_only, reverse_lookup_unify",
                        1, new_target);
}

/**
 * @brief Delete a target.
 *
 * @param[in]  target_id  UUID of target.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the target, 2 failed
 *         to find target, 3 predefined target, 99 permission denied, -1 error.
 */
int
delete_target (const char *target_id, int ultimate)
{
  target_t target = 0;

  if (strcmp (target_id, TARGET_UUID_LOCALHOST) == 0)
    return 3;

  sql_begin_immediate ();

  if (user_may ("delete_target") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_target_with_permission (target_id, &target, "delete_target"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (target == 0)
    {
      if (find_trash ("target", target_id, &target))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (target == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE target = %llu"
                   " AND target_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   target))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("target", target, LOCATION_TRASH);
      tags_set_orphans ("target", target, LOCATION_TRASH);

      sql ("DELETE FROM targets_trash WHERE id = %llu;", target);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE target = %llu"
                   " AND target_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (hidden = 0 OR hidden = 1);",
                   target))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO targets_trash"
           " (uuid, owner, name, hosts, exclude_hosts, comment, lsc_credential,"
           "  ssh_port, smb_lsc_credential, port_range, ssh_location,"
           "  smb_location, port_list_location, reverse_lookup_only,"
           "  reverse_lookup_unify, alive_test, creation_time,"
           "  modification_time, esxi_lsc_credential, esxi_location)"
           " SELECT uuid, owner, name, hosts, exclude_hosts, comment,"
           "        lsc_credential, ssh_port, smb_lsc_credential, port_range,"
           "      " G_STRINGIFY (LOCATION_TABLE) ","
           "      " G_STRINGIFY (LOCATION_TABLE) ","
           "      " G_STRINGIFY (LOCATION_TABLE) ","
           "        reverse_lookup_only, reverse_lookup_unify, alive_test,"
           "        creation_time, modification_time,"
           "        esxi_lsc_credential, " G_STRINGIFY (LOCATION_TABLE)
           " FROM targets WHERE id = %llu;",
           target);

      /* Update the location of the target in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET target = %llu,"
           "     target_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE target = %llu"
           " AND target_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           target);

      permissions_set_locations ("target", target,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("target", target,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else if (sql_int ("SELECT count(*) FROM tasks"
                    " WHERE target = %llu"
                    " AND target_location = " G_STRINGIFY (LOCATION_TABLE),
                    target))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  else
    {
      permissions_set_orphans ("target", target, LOCATION_TABLE);
      tags_set_orphans ("target", target, LOCATION_TABLE);
    }

  sql ("DELETE FROM targets WHERE id = %llu;", target);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Modify a target.
 *
 * @param[in]   target_id       UUID of target.
 * @param[in]   name            Name of target.
 * @param[in]   hosts           Host list of target.
 * @param[in]   exclude_hosts   List of hosts to exclude from \p hosts.
 * @param[in]   comment         Comment on target.
 * @param[in]   port_list_id    Port list of target (overrides \p port_range).
 * @param[in]   ssh_lsc_credential_id  SSH LSC credential.
 * @param[in]   ssh_port        Port for SSH LSC login.
 * @param[in]   smb_lsc_credential_id  SMB LSC credential.
 * @param[in]   esxi_lsc_credential_id  ESXi LSC credential.
 * @param[in]   reverse_lookup_only   Scanner preference reverse_lookup_only.
 * @param[in]   reverse_lookup_unify  Scanner preference reverse_lookup_unify.
 * @param[in]   alive_tests     Alive tests.
 *
 * @return 0 success, 1 target exists already, 2 error in host specification,
 *         3 too many hosts, 4 error in port range, 5 error in SSH port,
 *         6 failed to find port list, 7 failed to find SSH cred, 8 failed to
 *         find SMB cred, 9 failed to find target, 10 error in alive tests,
 *         11 zero length name, 12 exclude hosts requires hosts
 *         13 hosts requires exclude hosts,
 *         14 hosts must be at least one character, 15 target is in use,
 *         16 failed to find ESXi cred,
 *         99 permission denied, -1 error.
 */
int
modify_target (const char *target_id, const char *name, const char *hosts,
               const char *exclude_hosts, const char *comment,
               const char *port_list_id, const char *ssh_lsc_credential_id,
               const char *ssh_port, const char *smb_lsc_credential_id,
               const char *esxi_lsc_credential_id,
               const char *reverse_lookup_only,
               const char *reverse_lookup_unify, const char *alive_tests)
{
  target_t target;

  assert (target_id);

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_target") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (hosts && (exclude_hosts == NULL))
    {
      sql ("ROLLBACK;");
      return 13;
    }

  target = 0;
  if (find_target_with_permission (target_id, &target, "modify_target"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (target == 0)
    {
      sql ("ROLLBACK;");
      return 9;
    }

  if (name)
    {
      gchar *quoted_name;

      if (strlen (name) == 0)
        {
          sql ("ROLLBACK;");
          return 11;
        }
      if (resource_with_name_exists (name, "target", target))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      quoted_name = sql_quote (name);
      sql ("UPDATE targets SET"
           " name = '%s',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           quoted_name,
           target);

      g_free (quoted_name);
    }

  if (comment)
    {
      gchar *quoted_comment;
      quoted_comment = sql_quote (comment);
      sql ("UPDATE targets SET"
           " comment = '%s',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           quoted_comment,
           target);
      g_free (quoted_comment);
    }

  if (alive_tests)
    {
      int alive_test;

      alive_test = alive_test_from_string (alive_tests);
      if (alive_test <= -1)
        {
          sql ("ROLLBACK;");
          return 10;
        }
      sql ("UPDATE targets SET"
           " alive_test = '%i',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           alive_test,
           target);
    }

  if (port_list_id)
    {
      port_list_t port_list;

      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      port_list = 0;
      if (find_port_list_with_permission (port_list_id, &port_list,
                                          "get_port_lists"))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (port_list == 0)
        {
          sql ("ROLLBACK;");
          return 6;
        }

      sql ("UPDATE targets SET"
           " port_range = %llu,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           port_list,
           target);
    }

  if (ssh_lsc_credential_id)
    {
      lsc_credential_t ssh_lsc_credential;
      gchar *quoted_ssh_port;

      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      ssh_lsc_credential = 0;
      if (strcmp (ssh_lsc_credential_id, "0"))
        {
          if (find_lsc_credential_with_permission (ssh_lsc_credential_id,
                                                   &ssh_lsc_credential,
                                                   "get_lsc_credentials"))
            {
              sql ("ROLLBACK;");
              return -1;
            }

          if (ssh_lsc_credential == 0)
            {
              sql ("ROLLBACK;");
              return 7;
            }

           if (ssh_port && validate_port (ssh_port))
            {
              sql ("ROLLBACK;");
              return 5;
            }

          quoted_ssh_port = sql_insert (ssh_port ? ssh_port : "22");
        }
      else
        quoted_ssh_port = g_strdup ("NULL");

      sql ("UPDATE targets SET"
           " lsc_credential = %llu,"
           " ssh_port = %s,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           ssh_lsc_credential,
           quoted_ssh_port,
           target);

       g_free (quoted_ssh_port);
    }

  if (smb_lsc_credential_id)
    {
      lsc_credential_t smb_lsc_credential;

      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      smb_lsc_credential = 0;
      if (strcmp (smb_lsc_credential_id, "0"))
        {
          if (find_lsc_credential_with_permission (smb_lsc_credential_id,
                                                   &smb_lsc_credential,
                                                   "get_lsc_credentials"))
            {
              sql ("ROLLBACK;");
              return -1;
            }

          if (smb_lsc_credential == 0)
            {
              sql ("ROLLBACK;");
              return 7;
            }
        }

      sql ("UPDATE targets SET"
           " smb_lsc_credential = %llu,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           smb_lsc_credential,
           target);
    }

  if (esxi_lsc_credential_id)
    {
      lsc_credential_t esxi_lsc_credential;

      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      esxi_lsc_credential = 0;
      if (strcmp (esxi_lsc_credential_id, "0"))
        {
          if (find_lsc_credential_with_permission (esxi_lsc_credential_id,
                                                   &esxi_lsc_credential,
                                                   "get_lsc_credentials"))
            {
              sql ("ROLLBACK;");
              return -1;
            }

          if (esxi_lsc_credential == 0)
            {
              sql ("ROLLBACK;");
              return 16;
            }
        }

      sql ("UPDATE targets SET"
           " esxi_lsc_credential = %llu,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           esxi_lsc_credential,
           target);
    }

  if (exclude_hosts)
    {
      gchar *quoted_exclude_hosts, *quoted_hosts, *clean;
      int max;

      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      quoted_exclude_hosts = sql_quote (exclude_hosts);

      if (hosts == NULL)
        {
          g_free (quoted_exclude_hosts);
          sql ("ROLLBACK;");
          return 12;
        }

      if (strlen (hosts) == 0)
        {
          g_free (quoted_exclude_hosts);
          sql ("ROLLBACK;");
          return 14;
        }

      max = manage_count_hosts (hosts, quoted_exclude_hosts);
      if (max <= 0)
        {
          g_free (quoted_exclude_hosts);
          sql ("ROLLBACK;");
          return 2;
        }
      clean = clean_hosts (hosts, &max);
      if (max > max_hosts)
        {
          g_free (quoted_exclude_hosts);
          sql ("ROLLBACK;");
          return 3;
        }
      quoted_hosts = sql_quote (clean);
      g_free (clean);

      sql ("UPDATE targets SET"
           " hosts = '%s',"
           " exclude_hosts = '%s',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           quoted_hosts,
           quoted_exclude_hosts,
           target);

      g_free (quoted_hosts);
      g_free (quoted_exclude_hosts);
    }

  if (reverse_lookup_only)
    {
      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      sql ("UPDATE targets SET"
           " reverse_lookup_only = '%i',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           strcmp (reverse_lookup_only, "0") ? 1 : 0,
           target);
    }

  if (reverse_lookup_unify)
    {
      if (target_in_use (target))
        {
          sql ("ROLLBACK;");
          return 15;
        }

      sql ("UPDATE targets SET"
           " reverse_lookup_unify = '%i',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           strcmp (reverse_lookup_unify, "0") ? 1 : 0,
           target);
    }

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Filter columns for target iterator.
 */
#define TARGET_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, "hosts", "exclude_hosts", "ips", "port_list",  \
   "ssh_credential", "smb_credential", "esxi_credential", NULL }

/**
 * @brief Target iterator columns.
 */
#define TARGET_ITERATOR_COLUMNS                             \
 {                                                          \
   GET_ITERATOR_COLUMNS (targets),                          \
   { "hosts", NULL },                                       \
   { "lsc_credential", NULL },                              \
   { "ssh_port", NULL },                                    \
   { "smb_lsc_credential", NULL },                          \
   { "port_range", NULL },                                  \
   { "0", NULL },                                           \
   { "0", NULL },                                           \
   {                                                        \
     "(SELECT uuid FROM port_lists"                         \
     " WHERE port_lists.id = port_range)",                  \
     NULL                                                   \
   },                                                       \
   {                                                        \
     "(SELECT name FROM port_lists"                         \
     " WHERE port_lists.id = port_range)",                  \
     "port_list"                                            \
   },                                                       \
   { "0", NULL },                                           \
   { "exclude_hosts", NULL },                               \
   { "reverse_lookup_only", NULL },                         \
   { "reverse_lookup_unify", NULL },                        \
   { "alive_test", NULL },                                  \
   { "esxi_lsc_credential", NULL },                         \
   { "0", NULL },                                           \
   {                                                        \
     "(SELECT name FROM lsc_credentials"                    \
     " WHERE lsc_credentials.id = lsc_credential)",         \
     "ssh_credential"                                       \
   },                                                       \
   {                                                        \
     "(SELECT name FROM lsc_credentials"                    \
     " WHERE lsc_credentials.id = smb_lsc_credential)",     \
     "smb_credential"                                       \
   },                                                       \
   {                                                        \
     "(SELECT name FROM lsc_credentials"                    \
     " WHERE lsc_credentials.id = esxi_lsc_credential)",    \
     "esxi_credential"                                      \
   },                                                       \
   { "hosts", NULL },                                       \
   { "max_hosts (hosts, exclude_hosts)", "ips" },           \
   { NULL, NULL }                                           \
 }

/**
 * @brief Target iterator columns for trash case.
 */
#define TARGET_ITERATOR_TRASH_COLUMNS                               \
 {                                                                  \
   GET_ITERATOR_COLUMNS (targets_trash),                            \
   { "hosts", NULL },                                               \
   { "lsc_credential", NULL },                                      \
   { "ssh_port", NULL },                                            \
   { "smb_lsc_credential", NULL },                                  \
   { "port_range", NULL },                                          \
   { "ssh_location", NULL },                                        \
   { "smb_location", NULL },                                        \
   {                                                                \
     "(CASE"                                                        \
     " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH)     \
     " THEN (SELECT uuid FROM port_lists_trash"                     \
     "       WHERE port_lists_trash.id = port_range)"               \
     " ELSE (SELECT uuid FROM port_lists"                           \
     "       WHERE port_lists.id = port_range)"                     \
     " END)",                                                       \
     NULL                                                           \
   },                                                               \
   {                                                                \
     "(CASE"                                                        \
     " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH)     \
     " THEN (SELECT name FROM port_lists_trash"                     \
     "       WHERE port_lists_trash.id = port_range)"               \
     " ELSE (SELECT name FROM port_lists"                           \
     "       WHERE port_lists.id = port_range)"                     \
     " END)",                                                       \
     NULL                                                           \
   },                                                               \
   { "port_list_location = " G_STRINGIFY (LOCATION_TRASH), NULL },  \
   { "exclude_hosts", NULL },                                       \
   { "reverse_lookup_only", NULL },                                 \
   { "reverse_lookup_unify", NULL },                                \
   { "alive_test", NULL },                                          \
   { "esxi_lsc_credential", NULL },                                 \
   { "esxi_location", NULL },                                       \
   { NULL, NULL }                                                   \
 }

/**
 * @brief Count number of targets.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of targets in filtered set.
 */
int
target_count (const get_data_t *get)
{
  static const char *extra_columns[] = TARGET_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TARGET_ITERATOR_COLUMNS;
  static column_t trash_columns[] = TARGET_ITERATOR_TRASH_COLUMNS;
  return count ("target", get, columns, trash_columns, extra_columns, 0, 0, 0,
                TRUE);
}

/**
 * @brief Initialise a target iterator, limited to the current user's targets.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  target      Target to limit iteration to.
 */
void
init_user_target_iterator (iterator_t* iterator, target_t target)
{
  static column_t select_columns[] = TARGET_ITERATOR_COLUMNS;
  gchar *columns;

  assert (target);

  columns = columns_build_select (select_columns);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM targets"
                 " WHERE id = %llu"
                 " AND ((owner IS NULL)"
                 "      OR (owner = (SELECT id FROM users"
                 "                   WHERE users.uuid = '%s')));",
                 columns,
                 target,
                 current_credentials.uuid);

  g_free (columns);
}

/**
 * @brief Initialise a target iterator, including observed targets.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_target_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = TARGET_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TARGET_ITERATOR_COLUMNS;
  static column_t trash_columns[] = TARGET_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "target",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the hosts of the target from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts of the target or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_hosts, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the SSH LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SSH LSC credential.
 */
int
target_iterator_ssh_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  return ret;
}

/**
 * @brief Get the SSH LSC port of the target from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SSH LSC port of the target or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_ssh_port, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the SMB LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SMB LSC credential.
 */
int
target_iterator_smb_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
  return ret;
}

/**
 * @brief Get the location of the SSH LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 0 in table, 1 in trash
 */
int
target_iterator_ssh_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
  return ret;
}

/**
 * @brief Get the location of the SMB LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 0 in table, 1 in trash
 */
int
target_iterator_smb_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
  return ret;
}

/**
 * @brief Get the port list uuid of the target from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID of the target port list or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_port_list_uuid, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Get the port list name of the target from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of the target port list or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_port_list_name, GET_ITERATOR_COLUMN_COUNT + 8);

/**
 * @brief Get the location of the port list from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 0 in table, 1 in trash.
 */
int
target_iterator_port_list_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
  return ret;
}

/**
 * @brief Get the excluded hosts of the target from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Excluded hosts of the target or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_exclude_hosts, GET_ITERATOR_COLUMN_COUNT + 10);

/**
 * @brief Get the reverse lookup only value from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Reverse lookup only of the target or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_reverse_lookup_only,
            GET_ITERATOR_COLUMN_COUNT + 11);

/**
 * @brief Get the reverse lookup unify value from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Reverse lookup unify of the target or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_reverse_lookup_unify,
            GET_ITERATOR_COLUMN_COUNT + 12);

/**
 * @brief Get the alive test description from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Reverse lookup unify of the target or NULL if iteration is complete.
 */
const char*
target_iterator_alive_tests (iterator_t* iterator)
{
  int tests;
  if (iterator->done) return "";
  tests = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 13);
  if ((tests & ALIVE_TEST_TCP_ACK_SERVICE)
      && (tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_ARP))
    return "ICMP, TCP-ACK Service & ARP Ping";
  if ((tests & ALIVE_TEST_TCP_ACK_SERVICE)
      && (tests & ALIVE_TEST_ARP))
    return "TCP-ACK Service & ARP Ping";
  if ((tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_ARP))
    return "ICMP & ARP Ping";
  if ((tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_TCP_ACK_SERVICE))
    return "ICMP & TCP-ACK Service Ping";
  if (tests & ALIVE_TEST_ARP)
    return "ARP Ping";
  if (tests & ALIVE_TEST_TCP_ACK_SERVICE)
    return "TCP-ACK Service Ping";
  if (tests & ALIVE_TEST_TCP_SYN_SERVICE)
    return "TCP-SYN Service Ping";
  if (tests & ALIVE_TEST_ICMP)
    return "ICMP Ping";
  if (tests & ALIVE_TEST_CONSIDER_ALIVE)
    return "Consider Alive";
  return "Scan Config Default";
}

/**
 * @brief Get the ESXi LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_esxi_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 14);
  return ret;
}

/**
 * @brief Get the ESXi LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_esxi_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 15);
  return ret;
}

/**
 * @brief Return the UUID of a tag.
 *
 * @param[in]  tag  Tag.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
tag_uuid (tag_t tag)
{
  return sql_string ("SELECT uuid FROM tags WHERE id = %llu;",
                     tag);
}

/**
 * @brief Return the UUID of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
target_uuid (target_t target)
{
  return sql_string ("SELECT uuid FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the UUID of a trashcan target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
trash_target_uuid (target_t target)
{
  return sql_string ("SELECT uuid FROM targets_trash WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the name of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
target_name (target_t target)
{
  return sql_string ("SELECT name FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the name of a trashcan target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
trash_target_name (target_t target)
{
  return sql_string ("SELECT name FROM targets_trash WHERE id = %llu;",
                     target);
}

/**
 * @brief Return whether a trashcan target is readable.
 *
 * @param[in]  target  Target.
 *
 * @return 1 if readable, else 0.
 */
int
trash_target_readable (target_t target)
{
  char *uuid;
  target_t found;

  if (target == 0)
    return 0;
  uuid = target_uuid (target);
  if (find_trash ("target", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Return the hosts associated with a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated comma separated list of hosts if available,
 *         else NULL.
 */
char*
target_hosts (target_t target)
{
  return sql_string ("SELECT hosts FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the excluded hosts associated with a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated comma separated list of excluded hosts if available,
 *         else NULL.
 */
char*
target_exclude_hosts (target_t target)
{
  return sql_string ("SELECT exclude_hosts FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the reverse_lookup_only value of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Reverse lookup only value if available, else NULL.
 */
char*
target_reverse_lookup_only (target_t target)
{
  return sql_string ("SELECT reverse_lookup_only FROM targets"
                     " WHERE id = %llu;", target);
}

/**
 * @brief Return the reverse_lookup_unify value of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Reverse lookup unify value if available, else NULL.
 */
char*
target_reverse_lookup_unify (target_t target)
{
  return sql_string ("SELECT reverse_lookup_unify FROM targets"
                     " WHERE id = %llu;", target);
}

/**
 * @brief Return the hosts associated with a trashcan target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated comma separated list of hosts if available,
 *         else NULL.
 */
char*
trash_target_hosts (target_t target)
{
  return sql_string ("SELECT hosts FROM targets_trash WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the excluded hosts associated with a trashcan target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated comma separated list of excluded hosts if available,
 *         else NULL.
 */
char*
trash_target_exclude_hosts (target_t target)
{
  return sql_string ("SELECT exclude_hosts FROM targets_trash"
                     " WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the SSH LSC port of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated port if available, else NULL.
 */
char*
target_ssh_port (target_t target)
{
  return sql_string ("SELECT ssh_port FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the SSH credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return SSH credential if any, else 0.
 */
lsc_credential_t
target_ssh_lsc_credential (target_t target)
{
  lsc_credential_t lsc_credential;

  switch (sql_int64 (&lsc_credential,
                     "SELECT lsc_credential FROM targets"
                     " WHERE id = %llu;",
                     target))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        /** @todo Move return to arg; return -1. */
        return 0;
        break;
    }
  return lsc_credential;
}

/**
 * @brief Return the SMB credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return SMB credential if any, else 0.
 */
lsc_credential_t
target_smb_lsc_credential (target_t target)
{
  lsc_credential_t lsc_credential;

  switch (sql_int64 (&lsc_credential,
                     "SELECT smb_lsc_credential FROM targets"
                     " WHERE id = %llu;",
                     target))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        /** @todo Move return to arg; return -1. */
        return 0;
        break;
    }
  return lsc_credential;
}

/**
 * @brief Return the ESXi credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return ESXi credential if any, else 0.
 */
lsc_credential_t
target_esxi_lsc_credential (target_t target)
{
  lsc_credential_t lsc_credential;

  switch (sql_int64 (&lsc_credential,
                     "SELECT esxi_lsc_credential FROM targets"
                     " WHERE id = %llu;",
                     target))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        /** @todo Move return to arg; return -1. */
        return 0;
        break;
    }
  return lsc_credential;
}

/**
 * @brief Return the port list associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return Port list
 */
port_list_t
target_port_list (target_t target)
{
  port_list_t port_list;

  switch (sql_int64 (&port_list,
                     "SELECT port_range FROM targets"
                     " WHERE id = %llu;",
                     target))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        /** @todo Move return to arg; return -1. */
        return 0;
        break;
    }
  return port_list;
}

/**
 * @brief Return the port range of a target, in OTP format.
 *
 * For "OpenVAS Default", return the explicit port ranges instead of "default".
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated port range if available, else NULL.
 */
char*
target_port_range (target_t target)
{
  GString *range;
  iterator_t ranges;
  range = g_string_new ("");
  init_port_range_iterator (&ranges, target_port_list (target), 0, 1,
                            "type, CAST (start AS INTEGER)");
  if (next (&ranges))
    {
      const char *start, *end;
      int type;

      start = port_range_iterator_start (&ranges);
      end = port_range_iterator_end (&ranges);
      type = port_range_iterator_type_int (&ranges);

      /* Scanner can only handle: T:1-3,5-6,9,U:1-2 */

      if (end && strcmp (end, "0") && strcmp (end, start))
        g_string_append_printf (range, "%s%s-%s",
                                (type == PORT_PROTOCOL_UDP ? "U:" : "T:"),
                                start, end);
      else
        g_string_append_printf (range, "%s%s",
                                (type == PORT_PROTOCOL_UDP ? "U:" : "T:"),
                                start);
      while (next (&ranges))
        {
          int tcp;

          start = port_range_iterator_start (&ranges);
          end = port_range_iterator_end (&ranges);
          tcp = (type == PORT_PROTOCOL_TCP);
          type = port_range_iterator_type_int (&ranges);

          if (end && strcmp (end, "0") && strcmp (end, start))
            g_string_append_printf (range, ",%s%s-%s",
                                    (tcp && type == PORT_PROTOCOL_UDP ? "U:" : ""),
                                    start, end);
          else
            g_string_append_printf (range, ",%s%s",
                                    (tcp && type == PORT_PROTOCOL_UDP ? "U:" : ""),
                                    start);
        }
    }
  cleanup_iterator (&ranges);
  return g_string_free (range, FALSE);
}

/**
 * @brief Return a target's alive tests.
 *
 * @param[in]  target  Target.
 *
 * @return Alive test bitfield.
 */
alive_test_t
target_alive_tests (target_t target)
{
  return sql_int ("SELECT alive_test FROM targets WHERE id = %llu;",
                  target);
}

/**
 * @brief Return whether a target is in use by a task.
 *
 * @param[in]  target  Target.
 *
 * @return 1 if in use, else 0.
 */
int
target_in_use (target_t target)
{
  return !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE target = %llu"
                    " AND target_location = " G_STRINGIFY (LOCATION_TABLE)
                    " AND (hidden = 0 OR hidden = 1);",
                    target);
}

/**
 * @brief Return whether a trashcan target is referenced by a task.
 *
 * @param[in]  target  Target.
 *
 * @return 1 if in use, else 0.
 */
int
trash_target_in_use (target_t target)
{
  return !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE target = %llu"
                    " AND target_location = " G_STRINGIFY (LOCATION_TRASH),
                    target);
}

/**
 * @brief Return whether a target is writable.
 *
 * @param[in]  target  Target.
 *
 * @return 1 if writable, else 0.
 */
int
target_writable (target_t target)
{
  return sql_int ("SELECT count(*) FROM targets"
                  " WHERE id = %llu"
                  " AND uuid = '" TARGET_UUID_LOCALHOST "'",
                  target)
         == 0;
}

/**
 * @brief Return whether a trashcan target is writable.
 *
 * @param[in]  target  Target.
 *
 * @return 1 if writable, else 0.
 */
int
trash_target_writable (target_t target)
{
  return (sql_int ("SELECT count(*) FROM targets_trash"
                   " WHERE id = %llu"
                   " AND uuid = '" TARGET_UUID_LOCALHOST "'",
                   target)
          || trash_target_in_use (target))
         == 0;
}

/**
 * @brief Initialise a target task iterator.
 *
 * Iterates over all tasks that use the target.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  target     Target.
 */
void
init_target_task_iterator (iterator_t* iterator, target_t target)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (target);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT name, uuid, %s FROM tasks"
                 " WHERE target = %llu"
                 " AND hidden = 0"
                 " ORDER BY name ASC;",
                 available,
                 target);

  g_free (available);
}

/**
 * @brief Get the name from a target_task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (target_task_iterator_name, 0);

/**
 * @brief Get the uuid from a target_task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The uuid of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (target_task_iterator_uuid, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
target_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}


/* Configs. */

/**
 * @brief Find a config given a UUID.
 *
 * @param[in]   uuid    Config UUID.
 * @param[out]  config  Config return, 0 if succesfully failed to find config.
 *
 * @return FALSE on success (including if failed to find config), TRUE on error.
 */
gboolean
find_config (const char* uuid, config_t* config)
{
  gchar *quoted_uuid = sql_quote (uuid);
  if (user_owns_uuid ("config", quoted_uuid, 0) == 0)
    {
      g_free (quoted_uuid);
      *config = 0;
      return FALSE;
    }
  switch (sql_int64 (config,
                     "SELECT id FROM configs WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *config = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }
  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Find a config for a set of permissions, given a UUID.
 *
 * @param[in]   uuid        UUID of config.
 * @param[out]  config      Config return, 0 if succesfully failed to find
 *                          config.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find config), TRUE on error.
 */
gboolean
find_config_with_permission (const char* uuid, config_t* config,
                             const char *permission)
{
  return find_resource_with_permission ("config", uuid, config, permission, 0);
}

/**
 * @brief Insert preferences into a config.
 *
 * @param[in]  config       Config.
 * @param[in]  preferences  Preferences.
 *
 * @return 0 success, -1 error, -4 input error.
 */
static int
config_insert_preferences (config_t config,
                           const array_t* preferences /* preference_t. */)
{
  int index = 0;
  const preference_t *preference;
  if (preferences == NULL) return -4;
  while ((preference = (preference_t*) g_ptr_array_index (preferences, index++)))
    /* Simply skip the preference if the value is NULL, for exports
     * where sensitive information is left out. */
    if (preference->value)
      {
        GString *value;
        int alt_index = 0;
        const gchar *alt;
        gchar *quoted_value;

        if (preference->name == NULL) return -4;
        if (strcmp (preference->name, "Timeout") == 0)
          {
            gchar *quoted_nvt_oid;

            /* Special Timeout preference. */

            if (preference->nvt_oid == NULL) return -4;

            quoted_nvt_oid = sql_quote (preference->nvt_oid);
            quoted_value = sql_quote (preference->value);

            sql ("INSERT into config_preferences (config, type, name, value)"
                 " VALUES (%llu, 'SERVER_PREFS', 'timeout.%s', '%s');",
                 config,
                 quoted_nvt_oid,
                 quoted_value);

            g_free (quoted_nvt_oid);
            g_free (quoted_value);
          }
        else if (preference->type)
          {
            gchar *quoted_type, *quoted_nvt_name, *quoted_preference_name;

            /* Presume NVT preference. */

            if (preference->nvt_name == NULL) return -4;

            value = g_string_new (preference->value);
            while ((alt = (gchar*) g_ptr_array_index (preference->alts, alt_index++)))
              g_string_append_printf (value, ";%s", alt);

            quoted_nvt_name = sql_quote (preference->nvt_name);
            quoted_preference_name = sql_quote (preference->name);
            quoted_type = sql_quote (preference->type);
            quoted_value = sql_quote (value->str);
            g_string_free (value, TRUE);
            /* LDAPsearch[entry]:Timeout value */
            sql ("INSERT into config_preferences (config, type, name, value)"
                 " VALUES (%llu, 'PLUGINS_PREFS', '%s[%s]:%s', '%s');",
                 config,
                 quoted_nvt_name,
                 quoted_type,
                 quoted_preference_name,
                 quoted_value);
            g_free (quoted_nvt_name);
            g_free (quoted_preference_name);
            g_free (quoted_type);
            g_free (quoted_value);
          }
        else
          {
            gchar *quoted_name;

            /* Presume scanner preference. */

            quoted_name = sql_quote (preference->name);
            quoted_value = sql_quote (preference->value);
            sql ("INSERT into config_preferences (config, type, name, value)"
                 " VALUES (%llu, 'SERVER_PREFS', '%s', '%s');",
                 config,
                 quoted_name,
                 quoted_value);
            g_free (quoted_name);
            g_free (quoted_value);
          }
      }
  return 0;
}

/**
 * @brief Create a config.
 *
 * If a config with the same name exists already then add a unique integer
 * suffix onto the name.
 *
 * @param[in]   proposed_name  Proposed name of config.
 * @param[in]   comment        Comment on config.
 * @param[in]   selectors      NVT selectors.
 * @param[in]   preferences    Preferences.
 * @param[out]  config         On success the config.
 * @param[out]  name           On success the name of the config.
 *
 * @return 0 success, 1 config exists already, 99 permission denied, -1 error,
 *         -2 name empty, -3 input error in selectors, -4 input error in
 *         preferences.
 */
int
create_config (const char* proposed_name, const char* comment,
               const array_t* selectors /* nvt_selector_t. */,
               const array_t* preferences /* preference_t. */,
               config_t *config, char **name)
{
  int ret;
  gchar *quoted_comment, *candidate_name, *quoted_candidate_name;
  char *selector_uuid;
  unsigned int num = 1;

  assert (current_credentials.uuid);

  if (proposed_name == NULL || strlen (proposed_name) == 0) return -2;

  selector_uuid = openvas_uuid_make ();
  if (selector_uuid == NULL)
    return -1;

  sql_begin_immediate ();

  if (user_may ("create_config") == 0)
    {
      sql ("ROLLBACK;");
      free (selector_uuid);
      return 99;
    }

  candidate_name = g_strdup (proposed_name);
  quoted_candidate_name = sql_quote (candidate_name);

  while (1)
    {
      if (!resource_with_name_exists (quoted_candidate_name, "config", 0))
        break;
      g_free (candidate_name);
      g_free (quoted_candidate_name);
      candidate_name = g_strdup_printf ("%s %u", proposed_name, ++num);
      quoted_candidate_name = sql_quote (candidate_name);
    }

  if (comment)
    {
      quoted_comment = sql_nquote (comment, strlen (comment));
      sql ("INSERT INTO configs (uuid, name, owner, nvt_selector, comment,"
           " type, creation_time, modification_time)"
           " VALUES (make_uuid (), '%s',"
           " (SELECT id FROM users WHERE users.uuid = '%s'),"
           " '%s', '%s', 0, m_now (), m_now ());",
           quoted_candidate_name,
           current_credentials.uuid,
           selector_uuid,
           quoted_comment);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO configs (uuid, name, owner, nvt_selector, comment,"
         " type, creation_time, modification_time)"
         " VALUES (make_uuid (), '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '%s', '', 0, m_now (), m_now ());",
         quoted_candidate_name,
         current_credentials.uuid,
         selector_uuid);
  g_free (quoted_candidate_name);

  /* Insert the selectors into the nvt_selectors table. */

  *config = sql_last_insert_id ();
  if ((ret = insert_nvt_selectors (selector_uuid, selectors)))
    {
      sql ("ROLLBACK;");
      free (selector_uuid);
      return ret;
    }
  free (selector_uuid);

  /* Insert the preferences into the config_preferences table. */

  if ((ret = config_insert_preferences (*config, preferences)))
    {
      sql ("ROLLBACK;");
      return ret;
    }

  /* Update family and NVT count caches. */

  update_config_caches (*config);

  sql ("COMMIT;");
  *name = candidate_name;
  return 0;
}

/**
 * @brief Get list of OSP Scanner parameters.
 *
 * @param[in]   scanner    Scanner.
 *
 * @return List of scanner parameters, NULL if error.
 */
static GSList *
get_scanner_params (scanner_t scanner)
{
  GSList *list = NULL;
  osp_connection_t *connection;

  connection = osp_scanner_connect (scanner);
  if (!connection)
    return NULL;

  osp_get_scanner_details (connection, NULL, &list);
  osp_connection_close (connection);
  return list;
}

/**
 * @brief Create a config from an OSP scanner.
 *
 * @param[in]   scanner_id  UUID of scanner to create config from.
 *
 * @return 0 success, 1 couldn't find scanner, 2 scanner not of OSP type,
 *         3 config name exists already, 4 couldn't get params from scanner,
 *         99 permission denied, -1 error.
 */
int
create_config_from_scanner (const char *scanner_id, const char *name,
                            const char *comment, char **uuid)
{
  scanner_t scanner;
  config_t config;
  GSList *params, *element;
  char *quoted_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (scanner_id);
  sql_begin_immediate ();

  if (user_may ("create_config") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }
  if (find_scanner_with_permission (scanner_id, &scanner, "create_config"))
    {
      sql ("ROLLBACK;");
      return -1;
    }
  if (scanner == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }
  if (scanner_type (scanner) != SCANNER_TYPE_OSP)
    {
      sql ("ROLLBACK;");
      return 2;
    }
  if (resource_with_name_exists (name, "config", 0))
    {
      sql ("ROLLBACK;");
      return 3;
    }

  params = get_scanner_params (scanner);
  if (!params)
    {
      sql ("ROLLBACK;");
      return 4;
    }
  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");
  /* Create new OSP config. */
  sql ("INSERT INTO configs (uuid, name, owner, nvt_selector, comment,"
       " type, creation_time, modification_time)"
       " VALUES (make_uuid (), '%s',"
       " (SELECT id FROM users WHERE users.uuid = '%s'),"
       " '', '%s', 1, m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment);
  g_free (quoted_name);
  g_free (quoted_comment);
  config = sql_last_insert_id ();
  *uuid = config_uuid (config);

  element = params;
  while (element)
    {
      char *param_id;

      osp_param_t *param = element->data;
      param_id = sql_quote (osp_param_id (param));
      /* XXX: Ignoring username, password and port parameters as these are taken
       * from the task's LSC Credentials / Target. */
      if (strcmp (param_id, "username") && strcmp (param_id, "password")
          && strcmp (param_id, "port"))
        {
          char *param_type, *param_def, *param_value = NULL;

          param_type = sql_quote (osp_param_type_str (param));
          param_def = sql_quote (osp_param_default (param));
          if (!strcmp (param_type, "selection"))
            {
              param_value = g_strdup (param_def);
              param_value = strtok (param_value, "|");
            }
          sql ("INSERT INTO config_preferences (config, name, type, value,"
               " default_value) VALUES (%llu, '%s', '%s', '%s', '%s')",
               config , param_id, param_type, param_value ?: param_def,
               param_def);
          g_free (param_type);
          g_free (param_def);
          g_free (param_value);
        }
      g_free (param_id);
      osp_param_free (element->data);
      element = element->next;
    }
  g_slist_free (params);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return the UUID of a config.
 *
 * @param[in]   config  Config.
 * @param[out]  id      Pointer to a newly allocated string.
 *
 * @return Newly allocated config uuid pointer.
 */
char *
config_uuid (config_t config)
{
  return sql_string ("SELECT uuid FROM configs WHERE id = %llu;", config);
}

/**
 * @brief Return the type of a config.
 *
 * @param[in]  config  Config.
 *
 * @return Config type, -1 if not found.
 */
int
config_type (config_t config)
{
  int type;
  char *str;
  str = sql_string ("SELECT type FROM configs WHERE id = %llu;", config);
  if (!str)
    return -1;
  type = atoi (str);
  g_free (str);
  return type;
}

/**
 * @brief Get the timeout value for an NVT in a config.
 *
 * @param[in]  config  Config.
 * @param[in]  oid     ID of NVT.
 *
 * @return Newly allocated timeout if set for the NVT, else NULL.
 */
char *
config_nvt_timeout (config_t config, const char *oid)
{
  return sql_string ("SELECT value FROM config_preferences"
                     " WHERE config = %llu"
                     " AND type = 'SERVER_PREFS'"
                     " AND name = 'timeout.%s';",
                     config,
                     oid);
}

/**
 * @brief Check scanner and config values match for a task.
 *
 * @param[in]  config       Scan Config.
 * @param[in]  scanner      Scanner.
 *
 * @return 1 if config and scanner types match, 0 otherwise.
 */
int
create_task_check_config_scanner (config_t config, scanner_t scanner)
{
  int ctype, stype;

  assert (config);
  assert (scanner);

  ctype = config_type (config);
  stype = scanner_type (scanner);

  if (ctype == 0 && stype == SCANNER_TYPE_OPENVAS)
    return 1;
  if (ctype == 1 && stype == SCANNER_TYPE_OSP)
    return 1;

  return 0;
}

/**
 * @brief Check scanner and slave values match for a task.
 *
 * @param[in]  task         Task.
 * @param[in]  slave_id     ID of slave. "0" to use task's config.
 * @param[in]  scanner_id   ID of scanner.
 *
 * @return 1 if slave and scanner values match, 0 otherwise.
 */
int
modify_task_check_slave_scanner (task_t task, const char *slave_id,
                                 const char *scanner_id)
{
  gchar *existing_scanner_id;

  /* If slave ID is NULL caller will leave the slave as it is. */

  if (slave_id == NULL)
    {
      /* If scanner ID is NULL or 0, caller will leave the scanner alone
       * too.  They should already match, so that's fine. */
      if ((scanner_id == NULL) || (strcmp (scanner_id, "0") == 0))
        return 1;

      if (task_slave (task))
        {
          /* Scanner will change, so ensure the new one accepts slaves. */
          if (strcmp (scanner_id, SCANNER_UUID_DEFAULT))
            return 0;
        }
      return 1;
    }

  /* If slave ID is 0 caller will clear the slave, which is always fine. */

  if (slave_id && (strcmp (slave_id, "0") == 0))
    return 1;

  /* Otherwise, caller will set an existing slave on the task. */

  if ((scanner_id == NULL) || (strcmp (scanner_id, "0") == 0))
    /* Scanner ID is NULL or 0, caller will leave the scanner as it is. */
    existing_scanner_id = scanner_uuid (task_scanner (task));
  else
    /* Caller will set a new scanner on the task. */
    existing_scanner_id = NULL;

  /* Either way, ensure that the scanner accepts slaves. */

  if (strcmp (existing_scanner_id ? existing_scanner_id : scanner_id,
              SCANNER_UUID_DEFAULT))
    {
      g_free (existing_scanner_id);
      return 0;
    }
  g_free (existing_scanner_id);
  return 1;
}

/**
 * @brief Check scanner and config values match for a task.
 *
 * @param[in]  task         Task.
 * @param[in]  config_id    ID of config. "0" to use task's config.
 * @param[in]  scanner_id   ID of scanner.
 *
 * @return 1 if config and scanner types match, 0 otherwise.
 */
int
modify_task_check_config_scanner (task_t task, const char *config_id,
                                  const char *scanner_id)
{
  config_t config = 0;
  scanner_t scanner = 0;
  int ctype, stype;

  if (config_id == NULL)
    config_id = "0";
  if (scanner_id == NULL)
    scanner_id = "0";

  if (!strcmp (config_id, "0") && !strcmp (scanner_id, "0"))
    return 1;

  if (strcmp (config_id, "0"))
    find_config_with_permission (config_id, &config, "get_configs");
  else
    config = task_config (task);

  find_scanner_with_permission (scanner_id, &scanner, "get_scanners");

  ctype = config_type (config);
  stype = scanner_type (scanner);
  /* OSP Scanner with OSP config. */
  if (stype == SCANNER_TYPE_OSP && ctype == 1)
    return 1;

  /* OpenVAS Scanner with OpenVAS config. */
  if (stype == SCANNER_TYPE_OPENVAS && ctype == 0)
    return 1;

  /* Default Scanner with OpenVAS Config. */
  if (scanner == 0 && ctype == 0)
    return 1;

  return 0;
}

/**
 * @brief Create a config from an existing config.
 *
 * @param[in]  name        Name of new config and NVT selector.
 * @param[in]  comment     Comment on new config.
 * @param[in]  config_id   UUID of existing config.
 * @param[out] new_config  New config.
 *
 * @return 0 success, 1 config exists already, 2 failed to find existing
 *         config, 99 permission denied, -1 error.
 */
int
copy_config (const char* name, const char* comment, const char *config_id,
             config_t* new_config)
{
  int ret, type;
  char *config_selector;
  gchar *quoted_config_selector;
  config_t new, old;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  /* Copy the existing config. */

  ret = copy_resource_lock ("config", name, comment, config_id,
                            " family_count, nvt_count, families_growing,"
                            " nvts_growing, type",
                            1, &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("INSERT INTO config_preferences (config, type, name, value,"
       "                                default_value)"
       " SELECT %llu, type, name, value, default_value FROM config_preferences"
       " WHERE config = %llu;", new, old);

  type = config_type (new);
  if (type > 0)
    {
      /* Don't create nvt_selector etc,. for non-standard configs
       * (eg. OSP config.) Only config preferences are copied.
       */
      sql ("COMMIT;");
      if (new_config) *new_config = new;
      return 0;
    }

  sql ("UPDATE configs SET nvt_selector = make_uuid () WHERE id = %llu;",
       new);

  config_selector = config_nvt_selector (old);
  if (config_selector == NULL)
    {
      sql ("ROLLBACK;");
      return -1;
    }
  quoted_config_selector = sql_quote (config_selector);
  free (config_selector);

  sql ("INSERT INTO nvt_selectors (name, exclude, type, family_or_nvt, family)"
       " SELECT (SELECT nvt_selector FROM configs WHERE id = %llu),"
       "        exclude, type, family_or_nvt, family"
       " FROM nvt_selectors"
       " WHERE name = '%s';",
       new,
       quoted_config_selector);
  g_free (quoted_config_selector);

  sql ("COMMIT;");
  if (new_config) *new_config = new;
  return 0;
}

/**
 * @brief Delete a config.
 *
 * @param[in]  config_id  UUID of config.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the config, 2 failed to
 *         find config, 99 permission denied, -1 error.
 */
int
delete_config (const char *config_id, int ultimate)
{
  config_t config = 0;

  if ((strcmp (config_id, CONFIG_UUID_FULL_AND_FAST) == 0)
      || (strcmp (config_id, CONFIG_UUID_FULL_AND_FAST_ULTIMATE) == 0)
      || (strcmp (config_id, CONFIG_UUID_FULL_AND_VERY_DEEP) == 0)
      || (strcmp (config_id, CONFIG_UUID_FULL_AND_VERY_DEEP_ULTIMATE) == 0)
      || (strcmp (config_id, CONFIG_UUID_DISCOVERY) == 0)
      || (strcmp (config_id, CONFIG_UUID_HOST_DISCOVERY) == 0)
      || (strcmp (config_id, CONFIG_UUID_SYSTEM_DISCOVERY) == 0)
      || (strcmp (config_id, CONFIG_UUID_EMPTY) == 0))
    return 1;

  sql_begin_immediate ();

  if (user_may ("delete_config") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_config_with_permission (config_id, &config, "delete_config"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (config == 0)
    {
      if (find_trash ("config", config_id, &config))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (config == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE config = %llu"
                   " AND config_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   config))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("config", config, LOCATION_TRASH);
      tags_set_orphans ("config", config, LOCATION_TRASH);

      sql ("DELETE FROM nvt_selectors WHERE name ="
           " (SELECT nvt_selector FROM configs_trash WHERE id = %llu);",
           config);
      sql ("DELETE FROM config_preferences_trash WHERE config = %llu;",
           config);
      sql ("DELETE FROM configs_trash WHERE id = %llu;",
           config);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate)
    {
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE config = %llu"
                   " AND config_location = " G_STRINGIFY (LOCATION_TABLE),
                   config))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("DELETE FROM nvt_selectors WHERE name ="
           " (SELECT nvt_selector FROM configs_trash WHERE id = %llu);",
           config);

      permissions_set_orphans ("config", config, LOCATION_TABLE);
      tags_set_orphans ("config", config, LOCATION_TABLE);
    }
  else
    {
      config_t trash_config;

      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE config = %llu"
                   " AND config_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (hidden = 0 OR hidden = 1);",
                   config))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO configs_trash"
           " (uuid, owner, name, nvt_selector, comment, family_count, nvt_count,"
           "  families_growing, nvts_growing, type,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, nvt_selector, comment, family_count,"
           "        nvt_count, families_growing, nvts_growing,"
           "        type, creation_time, modification_time"
           " FROM configs WHERE id = %llu;",
           config);

      trash_config = sql_last_insert_id ();

      sql ("INSERT INTO config_preferences_trash"
           " (config, type, name, value, default_value)"
           " SELECT %llu, type, name, value, default_value"
           " FROM config_preferences WHERE config = %llu;",
           trash_config,
           config);

      /* Update the location of the config in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET config = %llu,"
           "     config_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE config = %llu"
           " AND config_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_config,
           config);

      permissions_set_locations ("config", config, trash_config,
                                 LOCATION_TRASH);
      tags_set_locations ("config", config, trash_config,
                          LOCATION_TRASH);
    }

  sql ("DELETE FROM config_preferences WHERE config = %llu;", config);
  sql ("DELETE FROM configs WHERE id = %llu;", config);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Filter columns for scan configs iterator.
 */
#define CONFIG_ITERATOR_FILTER_COLUMNS                                        \
 { GET_ITERATOR_FILTER_COLUMNS, "nvt_selector", "families_total",             \
   "nvts_total", "families_trend", "nvts_trend", NULL }

/**
 * @brief Scan config iterator columns.
 */
#define CONFIG_ITERATOR_COLUMNS                                               \
 {                                                                            \
   GET_ITERATOR_COLUMNS (configs),                                            \
   { "nvt_selector", NULL },                                                  \
   { "family_count", "families_total" },                                      \
   { "nvt_count", "nvts_total"},                                              \
   { "families_growing", "families_trend"},                                   \
   { "nvts_growing", "nvts_trend"},                                           \
   { "type", NULL },                                                          \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Scan config iterator columns for trash case.
 */
#define CONFIG_ITERATOR_TRASH_COLUMNS                                         \
 {                                                                            \
   GET_ITERATOR_COLUMNS (configs_trash),                                      \
   { "nvt_selector", NULL },                                                  \
   { "family_count", "families_total" },                                      \
   { "nvt_count", "nvts_total"},                                              \
   { "families_growing", "families_trend"},                                   \
   { "nvts_growing", "nvts_trend"},                                           \
   { "type", NULL },                                                          \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Count the number of scan configs.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of scan configs filtered set.
 */
int
config_count (const get_data_t *get)
{
  static const char *filter_columns[] = CONFIG_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CONFIG_ITERATOR_COLUMNS;
  static column_t trash_columns[] = CONFIG_ITERATOR_TRASH_COLUMNS;
  return count ("config", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a config iterator, limited to user's configs.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  config      Config.  0 for all.
 * @param[in]  trash       Whether to iterate over trashcan configs.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_user_config_iterator (iterator_t* iterator, config_t config, int trash,
                           int ascending, const char* sort_field)
{
  static column_t select_columns[] = CONFIG_ITERATOR_COLUMNS;
  gchar *columns;
  gchar *sql;

  assert (current_credentials.uuid);

  columns = columns_build_select (select_columns);
  if (config)
    sql = g_strdup_printf ("SELECT %s"
                           " FROM configs%s"
                           " WHERE id = %llu"
                           " AND ((owner IS NULL) OR (owner ="
                           " (SELECT id FROM users"
                           "  WHERE users.uuid = '%s')))"
                           " ORDER BY %s %s;",
                           columns,
                           trash ? "_trash" : "",
                           config,
                           current_credentials.uuid,
                           sort_field ? sort_field : "id",
                           ascending ? "ASC" : "DESC");
  else
    sql = g_strdup_printf ("SELECT %s"
                           " FROM configs%s"
                           " WHERE ((owner IS NULL) OR (owner ="
                           " (SELECT id FROM users"
                           "  WHERE users.uuid = '%s')))"
                           " ORDER BY %s %s;",
                           columns,
                           trash ? "_trash" : "",
                           current_credentials.uuid,
                           sort_field ? sort_field : "id",
                           ascending ? "ASC" : "DESC");
  g_free (columns);
  init_iterator (iterator, sql);
  g_free (sql);
}

/**
 * @brief Initialise a scan config iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find scan config, failed to find filter,
 *         -1 error.
 */
int
init_config_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = CONFIG_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CONFIG_ITERATOR_COLUMNS;
  static column_t trash_columns[] = CONFIG_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "config",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the nvt_selector from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The nvt_selector of the config, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (config_iterator_nvt_selector, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the family count from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Family count if known, -1 else.
 */
int
config_iterator_family_count (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  return ret;
}

/**
 * @brief Get the nvt count from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Nvt count if known, -1 else.
 */
int
config_iterator_nvt_count (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
  return ret;
}

/**
 * @brief Get the families growing state from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Families growing flag.
 */
int
config_iterator_families_growing (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT +3);
  return ret;
}

/**
 * @brief Get the NVTs growing state from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVTs growing flag.
 */
int
config_iterator_nvts_growing (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
  return ret;
}

/**
 * @brief Get the type from a config iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Config type.
 */
int
config_iterator_type (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
  return ret;
}

/**
 * @brief Return whether a config is referenced by a task.
 *
 * The predefined configs are always in use.
 *
 * @param[in]  config  Config.
 *
 * @return 1 if in use, else 0.
 */
int
config_in_use (config_t config)
{
  if (sql_int ("SELECT count(*) FROM configs"
               " WHERE id = %i"
               " AND (uuid = '" CONFIG_UUID_FULL_AND_FAST "'"
               "      OR uuid = '" CONFIG_UUID_FULL_AND_FAST_ULTIMATE "'"
               "      OR uuid = '" CONFIG_UUID_FULL_AND_VERY_DEEP "'"
               "      OR uuid = '" CONFIG_UUID_FULL_AND_VERY_DEEP_ULTIMATE "'"
               "      OR uuid = '" CONFIG_UUID_EMPTY "'"
               "      OR uuid = '" CONFIG_UUID_DISCOVERY "'"
               "      OR uuid = '" CONFIG_UUID_HOST_DISCOVERY "'"
               "      OR uuid = '" CONFIG_UUID_SYSTEM_DISCOVERY "');",
               config))
    return 1;

  return sql_int ("SELECT count(*) FROM tasks"
                  " WHERE config = %llu"
                  " AND config_location = " G_STRINGIFY (LOCATION_TABLE)
                  " AND (hidden = 0 OR hidden = 1);",
                  config);
}

/**
 * @brief Return whether a config is referenced by a task outside the trashcan.
 *
 * @param[in]  config  Config.
 *
 * @return 1 if in use, else 0.
 */
int
config_writable (config_t config)
{
  return !config_in_use (config);
}

/**
 * @brief Return whether a trashcan config is referenced by a task.
 *
 * @param[in]  config  Config.
 *
 * @return 1 if in use, else 0.
 */
int
trash_config_in_use (config_t config)
{
  return sql_int ("SELECT count(*) FROM tasks"
                  " WHERE config = %llu"
                  " AND config_location = " G_STRINGIFY (LOCATION_TRASH),
                  config);
}

/**
 * @brief Return whether a trashcan config is writable.
 *
 * @param[in]  config  Config.
 *
 * @return 1 if in use, else 0.
 */
int
trash_config_writable (config_t config)
{
  return !trash_config_in_use (config);
}

/**
 * @brief Return whether a trashcan config is readable.
 *
 * @param[in]  config_id  Config UUID.
 *
 * @return 1 if readable, else 0.
 */
int
trash_config_readable_uuid (const gchar *config_id)
{
  config_t found;

  found = 0;
  if (find_trash ("config", config_id, &found))
    return 0;
  return found > 0;
}

/**
 * @brief Initialise a preference iterator.
 *
 * Assume the caller has permission to access the config.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  config    Config.
 * @param[in]  section   Preference section, NULL for general preferences.
 */
void
init_preference_iterator (iterator_t* iterator, config_t config)
{
  gchar* sql;

  sql = g_strdup_printf ("SELECT name, value, type, default_value FROM"
                         " config_preferences WHERE config = %llu;", config);
  init_iterator (iterator, sql);
  g_free (sql);
}

/**
 * @brief Get the name from a preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the preference iterator, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (preference_iterator_name, 0);

/**
 * @brief Get the value from a preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the preference iterator, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (preference_iterator_value, 1);

/**
 * @brief Get the type from a preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the preference iterator, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (preference_iterator_type, 2);

/**
 * @brief Get the default from a preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The default of the preference iterator, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (preference_iterator_default, 3);

/**
 * @brief Initialise an "OTP" preference iterator.
 *
 * Assume the caller has permission to access the config.
 *
 * This version substitutes the scanner preference when the NVT preference
 * is missing.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  config    Config containing preferences.
 * @param[in]  section   Preference section, NULL for general preferences.
 */
void
init_otp_pref_iterator (iterator_t* iterator,
                        config_t config,
                        const char* section)
{
  gchar *quoted_section;

  assert (config);
  assert (section);
  assert ((strcmp (section, "PLUGINS_PREFS") == 0)
          || (strcmp (section, "SERVER_PREFS") == 0));

  quoted_section = sql_quote (section);

  init_iterator (iterator,
                 "SELECT config_preferences.name, config_preferences.value"
                 " FROM config_preferences, nvt_preferences"
                 " WHERE config_preferences.config = %llu"
                 " AND config_preferences.type = '%s'"
                 " AND (config_preferences.name = nvt_preferences.name"
                 "      OR config_preferences.name LIKE 'timeout.%')"
                 " AND config_preferences.name != 'max_checks'"
                 " AND config_preferences.name != 'max_hosts'"
                 " UNION"
                 " SELECT nvt_preferences.name, nvt_preferences.value"
                 " FROM nvt_preferences"
                 " WHERE nvt_preferences.name %s"
                 " AND (SELECT COUNT(*) FROM config_preferences"
                 "      WHERE config = %llu"
                 "      AND config_preferences.name = nvt_preferences.name) = 0;",
                 config,
                 quoted_section,
                 strcmp (quoted_section, "SERVER_PREFS") == 0
                  ? "NOT LIKE '%[%]%'" : "LIKE '%[%]%'",
                 config);
  g_free (quoted_section);
}

/**
 * @brief Get the NAME from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NAME, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (otp_pref_iterator_name, 0);

/**
 * @brief Get the value from a otp_pref iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (otp_pref_iterator_value, 1);

/**
 * @brief Return the NVT selector associated with a config.
 *
 * @param[in]  config  Config.
 *
 * @return Name of NVT selector if config exists and NVT selector is set, else
 *         NULL.
 */
char*
config_nvt_selector (config_t config)
{
  return sql_string ("SELECT nvt_selector FROM configs WHERE id = %llu;",
                     config);
}

/**
 * @brief Set a preference of a config.
 *
 * @param[in]  config    Config.
 * @param[in]  nvt       UUID of NVT.  NULL for scanner preference.
 * @param[in]  name      Preference name, including NVT name and preference
 *                       type.
 * @param[in]  value_64  Preference value in base64.  NULL for an NVT
 *                       preference removes the preference from the config.
 *
 * @return 0 success, 1 config in use, 2 empty radio value, -1 error.
 */
int
manage_set_config_preference (config_t config, const char* nvt, const char* name,
                              const char* value_64)
{
  gchar *quoted_name, *quoted_value, *value;
  int type_start = -1, type_end = -1, count;

  if (value_64 == NULL)
    {
      int end = -1;

      sql_begin_immediate ();

      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE config = %llu AND (hidden = 0 OR hidden = 1);",
                   config))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      quoted_name = sql_quote (name);

      /* scanner[scanner]:Timeout */
      count = sscanf (name, "%*[^[][scanner]:%n", &end);
      if (count == 0 && end > 0)
        {
          /* A scanner preference.  Remove type decoration from name. */
          g_free (quoted_name);
          quoted_name = sql_quote (name + end);
        }

      sql ("DELETE FROM config_preferences"
           " WHERE config = %llu"
           " AND name = '%s';",
           config,
           quoted_name);

      sql ("COMMIT;");

      g_free (quoted_name);
      return 0;
    }

  sql_begin_immediate ();

  if (sql_int ("SELECT count(*) FROM tasks"
               " WHERE config = %llu AND (hidden = 0 OR hidden = 1);",
               config))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_name = sql_quote (name);

  if (strlen (value_64))
    {
      gsize value_len;
      value = (gchar*) g_base64_decode (value_64, &value_len);
    }
  else
    value = g_strdup ("");

  /* LDAPsearch[entry]:Timeout value */
  count = sscanf (name, "%*[^[][%n%*[^]]%n]:", &type_start, &type_end);
  if (count == 0 && type_start > 0 && type_end > 0)
    {
      if (strncmp (name + type_start, "radio", type_end - type_start) == 0)
        {
          char *old_value;
          gchar **split, **point;
          GString *string;

          if (strlen (value) == 0)
            {
              g_free (quoted_name);
              g_free (value);
              sql ("ROLLBACK;");
              return 2;
            }

          /* A radio.  Put the new value on the front of the list of options. */

          old_value = sql_string ("SELECT value FROM config_preferences"
                                  " WHERE config = %llu"
                                  " AND type %s"
                                  " AND name = '%s'",
                                  config,
                                  nvt ? "= 'PLUGINS_PREFS'" : "is NULL",
                                  quoted_name);
          if (old_value == NULL)
            old_value = sql_string ("SELECT value FROM nvt_preferences"
                                    " WHERE name = '%s'",
                                    quoted_name);
          if (old_value)
            {
              string = g_string_new (value);
              split = g_strsplit (old_value, ";", 0);
              free (old_value);
              point = split;
              while (*point)
                {
                  if (strlen (*point) == 0)
                    {
                      g_free (quoted_name);
                      g_strfreev (split);
                      g_free (value);
                      g_string_free (string, TRUE);
                      sql ("ROLLBACK;");
                      return -1;
                    }

                  if (strcmp (*point, value))
                    {
                      g_string_append_c (string, ';');
                      g_string_append (string, *point);
                    }
                  point++;
                }
              g_strfreev (split);
              g_free (value);
              value = g_string_free (string, FALSE);
            }
        }
      else if (strncmp (name + type_start, "scanner", type_end - type_start)
               == 0)
        {
          /* A scanner preference.  Remove type decoration from name. */

          g_free (quoted_name);
          quoted_name = sql_quote (name + type_end + 2);
        }
    }

  quoted_value = sql_quote ((gchar*) value);
  g_free (value);

  if (config_type (config) > 0)
    sql ("UPDATE config_preferences SET value = '%s'"
         " WHERE config = %llu AND name = '%s';",
         quoted_value, config, quoted_name);
  else
    {
      /* nvt prefs are not present on first modification. */
      sql ("DELETE FROM config_preferences"
           " WHERE config = %llu AND type %s AND name = '%s'",
           config,
           nvt ? "= 'PLUGINS_PREFS'" : "= 'SERVER_PREFS'",
           quoted_name);
      sql ("INSERT INTO config_preferences"
           " (config, type, name, value) VALUES (%llu, %s, '%s', '%s');",
           config, nvt ? "'PLUGINS_PREFS'" : "'SERVER_PREFS'", quoted_name,
           quoted_value);
    }

  sql ("COMMIT;");

  g_free (quoted_name);
  g_free (quoted_value);
  return 0;
}

/**
 * @brief Set the comment of a config.
 *
 * @param[in]  config   Config.
 * @param[in]  comment  New comment.
 *
 * @return 0 success, -1 error.
 */
int
manage_set_config_comment (config_t config, const char* comment)
{
  gchar *quoted_comment;
  quoted_comment = sql_quote (comment);
  sql ("UPDATE configs SET comment = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_comment, config);
  g_free (quoted_comment);
  return 0;
}

/**
 * @brief Set the name of a config.
 *
 * @param[in]  config  Config.
 * @param[in]  name    New name.
 *
 * @return 0 success, 1 config with new name exists already, -1 error.
 */
int
manage_set_config_name (config_t config, const char* name)
{
  gchar *quoted_name;
  assert (current_credentials.uuid);
  sql_begin_immediate ();
  if (resource_with_name_exists (name, "config", config))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  quoted_name = sql_quote (name);
  sql ("UPDATE configs SET name = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name, config);
  g_free (quoted_name);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Set the name of a config.
 *
 * @param[in]  config   Config.
 * @param[in]  name     New name.
 * @param[in]  comment  New comment.
 *
 * @return 0 success, 1 config with new name exists already, -1 error.
 */
int
manage_set_config_name_comment (config_t config, const char* name,
                                const char* comment)
{
  gchar *quoted_name, *quoted_comment;
  assert (current_credentials.uuid);
  sql_begin_immediate ();
  if (resource_with_name_exists (name, "config", config))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  quoted_name = sql_quote (name);
  quoted_comment = sql_quote (comment);
  sql ("UPDATE configs SET name = '%s', comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name, quoted_comment, config);
  g_free (quoted_name);
  g_free (quoted_comment);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Set the NVT's selected for a single family of a config.
 *
 * @param[in]  config         Config.
 * @param[in]  family         Family name.
 * @param[in]  selected_nvts  NVT's.
 *
 * @return 0 success, 1 config in use, -1 error.
 */
int
manage_set_config_nvts (config_t config, const char* family,
                        GPtrArray* selected_nvts)
{
  char *selector;
  gchar *quoted_family, *quoted_selector;
  int new_nvt_count = 0, old_nvt_count;

  sql_begin_exclusive ();

  if (sql_int ("SELECT count(*) FROM tasks"
               " WHERE config = %llu AND (hidden = 0 OR hidden = 1);",
               config))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_family = sql_quote (family);

  selector = config_nvt_selector (config);
  if (selector == NULL)
    {
      /* The config should always have a selector. */
      g_free (quoted_family);
      return -1;
    }

  quoted_selector = sql_quote (selector);

  /* If the family is growing, then exclude all no's, otherwise the family
   * is static, so include all yes's. */

  if (nvt_selector_family_growing (selector,
                                   family,
                                   config_families_growing (config)))
    {
      iterator_t nvts;

      old_nvt_count = nvt_selector_nvt_count (selector, family, 1);

      free (selector);

      /* Clear any NVT selectors for this family from the config. */

      sql ("DELETE FROM nvt_selectors"
           " WHERE name = '%s'"
           " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
           " AND family = '%s';",
           quoted_selector,
           quoted_family);

      /* Exclude all no's. */

      new_nvt_count = family_nvt_count (family);

      init_nvt_iterator (&nvts, (nvt_t) 0, config, family, NULL, 1, NULL);
      while (next (&nvts))
        {
          const char *oid = nvt_iterator_oid (&nvts);
          gchar *quoted_oid;

          if (member (selected_nvts, oid)) continue;

          quoted_oid = sql_quote (oid);
          sql ("INSERT INTO nvt_selectors"
               " (name, exclude, type, family_or_nvt, family)"
               " VALUES ('%s', 1, "
               G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
               ", '%s', '%s');",
               quoted_selector,
               quoted_oid,
               quoted_family);
          g_free (quoted_oid);

          new_nvt_count--;
        }
      cleanup_iterator (&nvts);
    }
  else
    {
      old_nvt_count = nvt_selector_nvt_count (selector, family, 0);

      free (selector);

      /* Clear any NVT selectors for this family from the config. */

      sql ("DELETE FROM nvt_selectors"
           " WHERE name = '%s'"
           " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
           " AND family = '%s';",
           quoted_selector,
           quoted_family);

      /* Include all yes's. */

      if (selected_nvts)
        {
          gchar *nvt;
          new_nvt_count = 0;

          while ((nvt = (gchar*) g_ptr_array_index (selected_nvts,
                                                    new_nvt_count)))
            {
              gchar *quoted_nvt = sql_quote (nvt);
              sql ("INSERT INTO nvt_selectors"
                   " (name, exclude, type, family_or_nvt, family)"
                   " VALUES ('%s', 0, "
                   G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                   ", '%s', '%s');",
                   quoted_selector,
                   quoted_nvt,
                   quoted_family);
              g_free (quoted_nvt);
              new_nvt_count++;
            }
        }
    }

  /* Update the cached config info. */

  sql ("UPDATE configs SET family_count = family_count + %i,"
       " nvt_count = nvt_count - %i + %i,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       old_nvt_count == 0
        ? (new_nvt_count == 0 ? 0 : 1)
        : (new_nvt_count == 0 ? -1 : 0),
       old_nvt_count,
       MAX (new_nvt_count, 0),
       config);

  sql ("COMMIT;");

  g_free (quoted_family);
  g_free (quoted_selector);
  return 0;
}

/**
 * @brief Switch between constraining and generating representation.
 *
 * It's up to the caller to start and end a transaction.
 *
 * @param[in]  config        Config name.
 * @param[in]  constraining  1 families currently growing, 0 families currently
 *                           static.
 *
 * @return 0 success, -1 error.
 */
static int
switch_representation (config_t config, int constraining)
{
  char* selector;
  gchar *quoted_selector;

  selector = config_nvt_selector (config);
  if (selector == NULL)
    return -1;
  quoted_selector = sql_quote (selector);

  if (constraining)
    {
      iterator_t families;

      /* Currently constraining the universe. */

      /* Remove the all selector. */

      nvt_selector_remove_selector (quoted_selector,
                                    NULL,
                                    NVT_SELECTOR_TYPE_ALL);

      /* Convert each family. */

      init_family_iterator (&families, 0, NULL, 1);
      while (next (&families))
        {
          const char *family = family_iterator_name (&families);
          if (family)
            {
              gchar *quoted_family = sql_quote (family);
              if (nvt_selector_family_growing (selector, family, 1))
                /* Add a family include. */
                nvt_selector_add (quoted_selector,
                                  quoted_family,
                                  NULL,
                                  0);
              else
                /* Remove the family exclude. */
                nvt_selector_remove_selector (quoted_selector,
                                              quoted_family,
                                              NVT_SELECTOR_TYPE_FAMILY);
              g_free (quoted_family);
            }
        }
      cleanup_iterator (&families);

      /* Update the cached config info. */

      sql ("UPDATE configs SET families_growing = 0 WHERE id = %llu;",
           config);
    }
  else
    {
      iterator_t families;

      /* Currently generating from empty. */

      /* Add the all selector. */

      sql ("INSERT INTO nvt_selectors"
           " (name, exclude, type, family_or_nvt)"
           " VALUES ('%s', 0, 0, 0);",
           quoted_selector);

      /* Convert each family. */

      init_family_iterator (&families, 0, NULL, 1);
      while (next (&families))
        {
          const char *family = family_iterator_name (&families);
          if (family)
            {
              gchar *quoted_family = sql_quote (family);
              if (nvt_selector_family_growing (selector, family, 0))
                /* Remove the family include. */
                nvt_selector_remove_selector (quoted_selector,
                                              quoted_family,
                                              NVT_SELECTOR_TYPE_FAMILY);
              else
                /* Add a family exclude. */
                nvt_selector_add (quoted_selector,
                                  quoted_family,
                                  NULL,
                                  1);
              g_free (quoted_family);
            }
        }
      cleanup_iterator (&families);

      /* Update the cached config info. */

      sql ("UPDATE configs SET families_growing = 1 WHERE id = %llu;",
           config);
    }

  free (selector);
  g_free (quoted_selector);
  return 0;
}

/**
 * @brief Initialise a config task iterator.
 *
 * Iterate over all tasks that use the config.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  config     Config.
 * @param[in]  ascending  Whether to sort ascending or descending.
 */
void
init_config_task_iterator (iterator_t* iterator, config_t config,
                           int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (config);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT name, uuid, %s FROM tasks"
                 " WHERE config = %llu"
                 " AND hidden = 0"
                 " ORDER BY name %s;",
                 available,
                 config,
                 ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the name from a config_task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (config_task_iterator_name, 0);

/**
 * @brief Get the UUID from a config_task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (config_task_iterator_uuid, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
config_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}

/**
 * @brief Initialise a config timeout iterator.
 *
 * Iterate over all timeout preferences of NVTs that have timeouts.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  config     Config.
 */
void
init_config_timeout_iterator (iterator_t* iterator, config_t config)
{
  init_iterator (iterator,
                 "SELECT name, substr (name, 9),"
                 "       (SELECT name FROM nvts"
                 "        WHERE oid = substr (config_preferences.name, 9)),"
                 "       value"
                 " FROM config_preferences"
                 " WHERE config = %llu"
                 " AND substr (name, 1, 8) = 'timeout.'"
                 /* Ensure that the NVT pref comes first, in case an
                  * error in the GSA added the NVT pref as a Scanner
                  * pref. */
                 " ORDER BY type",
                 config);
}

/**
 * @brief Get the NVT OID from a config timeout iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT OID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (config_timeout_iterator_oid, 1);

/**
 * @brief Get the NVT OID from a config timeout iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT OID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (config_timeout_iterator_nvt_name, 2);

/**
 * @brief Get the value from a config timeout iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Timeout value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (config_timeout_iterator_value, 3);


/* NVT's. */

/**
 * @brief Get the name of an NVT.
 *
 * @param[in]  nvt  NVT.
 *
 * @return Freshly allocated name of NVT if possible, else NULL.
 */
char *
manage_nvt_name (nvt_t nvt)
{
  return sql_string ("SELECT name FROM nvts WHERE id = %llu;", nvt);
}

/**
 * @brief Guess the OID of an NVT given a name.
 *
 * @param[in]  name  Name of NVT.
 *
 * @return OID of NVT if possible, else NULL.
 */
char *
nvt_oid (const char *name)
{
  gchar *quoted_name = sql_quote (name);
  char *ret = sql_string ("SELECT oid FROM nvts WHERE name = '%s' LIMIT 1;",
                          quoted_name);
  g_free (quoted_name);
  return ret;
}

/**
 * @brief Return feed version of the plugins in the plugin cache.
 *
 * @return Number of plugins if the plugins are cached, else NULL.
 */
char*
nvts_feed_version ()
{
  return sql_string ("SELECT value FROM %s.meta"
                     " WHERE name = 'nvts_feed_version';",
                     sql_schema ());
}

/**
 * @brief Set the feed version of the plugins in the plugin cache.
 *
 * @param[in]  feed_version  New feed version.
 *
 * Also queue an update to the nvti cache.
 */
void
set_nvts_feed_version (const char *feed_version)
{
  gchar* quoted = sql_quote (feed_version);
  sql ("DELETE FROM %s.meta WHERE name = 'nvts_feed_version';",
       sql_schema ());
  sql ("INSERT INTO %s.meta (name, value)"
       " VALUES ('nvts_feed_version', '%s');",
       sql_schema (),
       quoted);
  g_free (quoted);

  sql ("UPDATE %s.meta SET value = 1 WHERE name = 'update_nvti_cache';",
       sql_schema ());
}

/**
 * @brief Find an NVT given an identifier.
 *
 * @param[in]   oid  An NVT identifier.
 * @param[out]  nvt  NVT return, 0 if succesfully failed to find task.
 *
 * @return FALSE on success (including if failed to find NVT), TRUE on error.
 */
gboolean
find_nvt (const char* oid, nvt_t* nvt)
{
  switch (sql_int64 (nvt,
                     "SELECT id FROM nvts WHERE oid = '%s';",
                     oid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *nvt = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return TRUE;
        break;
    }

  return FALSE;
}

/**
 * @brief Make an nvt from an nvti.
 *
 * @param[in]  nvti    NVTI.
 * @param[in]  remove  Whether to remove the NVT from the cache first.
 *
 * @return An NVT.
 */
nvt_t
make_nvt_from_nvti (const nvti_t *nvti, int remove)
{
  gchar *qod_str, *qod_type;
  /** @todo Freeing string literals. */
  gchar *quoted_version, *quoted_name, *quoted_summary;
  gchar *quoted_copyright, *quoted_cve, *quoted_bid, *quoted_xref, *quoted_tag;
  gchar *quoted_cvss_base, *quoted_qod_type, *quoted_family, *value;
  gchar *quoted_solution_type;

  int creation_time, modification_time, qod;

  if (remove)
    {
      sql_begin_exclusive ();
      sql ("DELETE FROM nvts WHERE oid = '%s';", nvti_oid (nvti));
    }

  quoted_version = sql_quote (nvti_version (nvti));
  quoted_name = sql_quote (nvti_name (nvti) ? nvti_name (nvti) : "");
  quoted_summary = sql_quote (nvti_summary (nvti) ? nvti_summary (nvti) : "");
  quoted_copyright = sql_quote (nvti_copyright (nvti)
                                ? nvti_copyright (nvti)
                                : "");
  quoted_cve = sql_quote (nvti_cve (nvti) ? nvti_cve (nvti) : "");
  quoted_bid = sql_quote (nvti_bid (nvti) ? nvti_bid (nvti) : "");
  quoted_xref = sql_quote (nvti_xref (nvti) ? nvti_xref (nvti) : "");
  if (nvti_tag (nvti))
    {
      const char *tags;
      gchar **split, **point;
      GString *tag;

      tags = nvti_tag (nvti);

      /* creation_date=2009-04-09 14:18:58 +0200 (Thu, 09 Apr 2009)|... */

      split = g_strsplit (tags, "|", 0);
      point = split;

      while (*point)
        {
          if (((strlen (*point) > strlen ("creation_date"))
               && (strncmp (*point, "creation_date", strlen ("creation_date"))
                   == 0)
               && ((*point)[strlen ("creation_date")] == '='))
              || ((strlen (*point) > strlen ("last_modification"))
                  && (strncmp (*point, "last_modification",
                               strlen ("last_modification"))
                      == 0)
                  && ((*point)[strlen ("last_modification")] == '=')))
            {
              gchar **move;
              move = point;
              g_free (*point);
              while (*move)
                {
                  move[0] = move[1];
                  move++;
                }
            }
          else
            point++;
        }

      point = split;
      tag = g_string_new ("");
      while (*point)
        {
          if (point[1])
            g_string_append_printf (tag, "%s|", *point);
          else
            g_string_append_printf (tag, "%s", *point);
          point++;
        }
      g_strfreev (split);

      quoted_tag = sql_quote (tag->str);
      g_string_free (tag, TRUE);
    }
  else
    quoted_tag = g_strdup ("");
  quoted_cvss_base = sql_quote (nvti_cvss_base (nvti)
                                 ? nvti_cvss_base (nvti)
                                 : "");

  qod_str = tag_value (nvti_tag (nvti), "qod");
  qod_type = tag_value (nvti_tag (nvti), "qod_type");

  if (qod_str == NULL || sscanf (qod_str, "%d", &qod) != 1)
    qod = qod_from_type (qod_type);

  quoted_qod_type = sql_quote (qod_type ? qod_type : "");

  g_free (qod_str);
  g_free (qod_type);

  quoted_family = sql_quote (nvti_family (nvti) ? nvti_family (nvti) : "");

  value = tag_value (nvti_tag (nvti), "creation_date");
  switch (parse_time (value, &creation_time))
    {
      case -1:
        g_warning ("%s: Failed to parse creation time of %s: %s",
                   __FUNCTION__, nvti_oid (nvti), value);
        creation_time = 0;
        break;
      case -2:
        g_warning ("%s: Failed to make time: %s", __FUNCTION__, value);
        creation_time = 0;
        break;
      case -3:
        g_warning ("%s: Failed to parse timezone offset: %s",
                   __FUNCTION__,
                   value);
        creation_time = 0;
        break;
    }
  g_free (value);

  value = tag_value (nvti_tag (nvti), "last_modification");
  switch (parse_time (value, &modification_time))
    {
      case -1:
        g_warning ("%s: Failed to parse last_modification time of %s: %s",
                   __FUNCTION__, nvti_oid (nvti), value);
        modification_time = 0;
        break;
      case -2:
        g_warning ("%s: Failed to make time: %s", __FUNCTION__, value);
        modification_time = 0;
        break;
      case -3:
        g_warning ("%s: Failed to parse timezone offset: %s",
                   __FUNCTION__,
                   value);
        modification_time = 0;
        break;
    }
  g_free (value);

  value = tag_value (nvti_tag (nvti), "solution_type");
  if (value)
    {
      quoted_solution_type = sql_quote (value);
      g_free (value);
    }
  else
    quoted_solution_type = g_strdup ("");

  sql ("DELETE FROM nvts WHERE oid = '%s';", nvti_oid (nvti));
  sql ("INSERT into nvts (oid, version, name, summary, copyright,"
       " cve, bid, xref, tag, category, family, cvss_base,"
       " creation_time, modification_time, uuid, solution_type,"
       " qod, qod_type)"
       " VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s',"
       " '%s', %i, '%s', '%s', %i, %i, '%s', '%s', %d, '%s');",
       nvti_oid (nvti), quoted_version, quoted_name, quoted_summary,
       quoted_copyright, quoted_cve, quoted_bid, quoted_xref, quoted_tag,
       nvti_category (nvti), quoted_family, quoted_cvss_base, creation_time,
       modification_time, nvti_oid (nvti), quoted_solution_type,
       qod, quoted_qod_type);

  if (remove)
    sql ("COMMIT;");

  g_free (quoted_version);
  g_free (quoted_name);
  g_free (quoted_summary);
  g_free (quoted_copyright);
  g_free (quoted_cve);
  g_free (quoted_bid);
  g_free (quoted_xref);
  g_free (quoted_tag);
  g_free (quoted_cvss_base);
  g_free (quoted_family);
  g_free (quoted_solution_type);
  g_free (quoted_qod_type);

  return sql_last_insert_id ();
}

/**
 * @brief Filter columns for NVT info iterator.
 */
#define NVT_INFO_ITERATOR_FILTER_COLUMNS                                    \
 { GET_ITERATOR_FILTER_COLUMNS, "version", "summary", "cve", "bid", "xref", \
   "family", "cvss_base", "severity", "cvss", "script_tags", "qod",         \
   "qod_type", "solution_type", NULL }

/**
 * @brief NVT iterator columns.
 */
#define NVT_ITERATOR_COLUMNS                                                \
 {                                                                          \
   GET_ITERATOR_COLUMNS_PREFIX (""),                                        \
   { "''", "_owner" },                                                      \
   { "0", NULL },                                                           \
   { "oid", NULL },                                                         \
   { "version", NULL },                                                     \
   { "name", NULL },                                                        \
   { "summary", NULL },                                                     \
   { "copyright", NULL },                                                   \
   { "cve", NULL },                                                         \
   { "bid", NULL },                                                         \
   { "xref", NULL },                                                        \
   { "tag", NULL },                                                         \
   { "category", NULL },                                                    \
   { "family", NULL },                                                      \
   { "cvss_base", NULL },                                                   \
   { "cvss_base", "severity" },                                             \
   { "cvss_base", "cvss" },                                                 \
   { "qod", NULL },                                                         \
   { "qod_type", NULL },                                                    \
   { "solution_type" , NULL },                                              \
   { "tag", "script_tags" },                                                \
   { NULL, NULL }                                                           \
 }

/**
 * @brief NVT iterator columns.
 */
#define NVT_ITERATOR_COLUMNS_NVTS                                           \
 {                                                                          \
   GET_ITERATOR_COLUMNS_PREFIX ("nvts."),                                   \
   { "''", "_owner" },                                                      \
   { "0", NULL },                                                           \
   { "oid", NULL },                                                         \
   { "version", NULL },                                                     \
   { "nvts.name", NULL },                                                   \
   { "summary", NULL },                                                     \
   { "copyright", NULL },                                                   \
   { "cve", NULL },                                                         \
   { "bid", NULL },                                                         \
   { "xref", NULL },                                                        \
   { "tag", NULL },                                                         \
   { "category", NULL },                                                    \
   { "nvts.family", NULL },                                                 \
   { "cvss_base", NULL },                                                   \
   { "cvss_base", "severity" },                                             \
   { "cvss_base", "cvss" },                                                 \
   { "qod", NULL },                                                         \
   { "qod_type", NULL },                                                    \
   { "solution_type" , NULL },                                              \
   { "tag", "script_tags" },                                                \
   { NULL, NULL }                                                           \
 }

/**
 * @brief Initialise an NVT iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 * @param[in]  name        Name of the info
 *
 * @return 0 success, 1 failed to find NVT, 2 failed to find filter,
 *         -1 error.
 */
int
init_nvt_info_iterator (iterator_t* iterator, get_data_t *get, const char *name)
{
  static const char *filter_columns[] = NVT_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = NVT_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      // FIX what for anyway?
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }

  ret = init_get_iterator (iterator,
                           "nvt",
                           get,
                           /* Columns. */
                           columns,
                           /* Columns for trashcan. */
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           0);

  g_free (clause);
  return ret;
}

/**
 * @brief Get NVT iterator SELECT columns.
 *
 * @return SELECT columns
 */
static gchar *
nvt_iterator_columns ()
{
  static column_t select_columns[] = NVT_ITERATOR_COLUMNS;
  static gchar *columns = NULL;
  if (columns == NULL)
    columns = columns_build_select (select_columns);
  return columns;
}

/**
 * @brief Get NVT iterator SELECT columns.
 *
 * @return SELECT columns
 */
static gchar *
nvt_iterator_columns_nvts ()
{
  static column_t select_columns[] = NVT_ITERATOR_COLUMNS_NVTS;
  static gchar *columns = NULL;
  if (columns == NULL)
    columns = columns_build_select (select_columns);
  return columns;
}

/**
 * @brief Count number of nvt.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of cpes in filtered set.
 */
int
nvt_info_count (const get_data_t *get)
{
  static const char *extra_columns[] = NVT_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = NVT_ITERATOR_COLUMNS;
  return count ("nvt", get, columns, NULL, extra_columns, 0, 0, 0,
                FALSE);
}

/**
 * @brief Initialise an NVT iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  nvt         NVT to iterate over, all if 0.
 * @param[in]  config      Config to limit selection to.  NULL for all NVTs.
 *                         Overridden by \arg nvt.
 * @param[in]  category    Category to limit selection to.  NULL for all.
 * @param[in]  family      Family to limit selection to.  NULL for all NVTs.
 *                         Overridden by \arg config.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_nvt_iterator (iterator_t* iterator, nvt_t nvt, config_t config,
                   const char* family, const char *category, int ascending,
                   const char* sort_field)
{
  assert ((nvt && family) == 0);

  if (nvt)
    {
      gchar* sql;
      sql = g_strdup_printf ("SELECT %s"
                             " FROM nvts WHERE id = %llu;",
                             nvt_iterator_columns (),
                             nvt);
      init_iterator (iterator, sql);
      g_free (sql);
    }
  else if (config)
    {
      gchar* sql;
      if (family == NULL) abort ();
      sql = select_config_nvts (config, family, ascending, sort_field);
      if (sql)
        {
          init_iterator (iterator, sql);
          g_free (sql);
        }
      else
        init_iterator (iterator,
                       "SELECT %s"
                       " FROM nvts LIMIT 0;",
                       nvt_iterator_columns ());
    }
  else if (family)
    {
      gchar *quoted_family = sql_quote (family);
      init_iterator (iterator,
                     "SELECT %s"
                     " FROM nvts"
                     " WHERE family = '%s'"
                     " ORDER BY %s %s;",
                     nvt_iterator_columns (),
                     quoted_family,
                     sort_field ? sort_field : "name",
                     ascending ? "ASC" : "DESC");
      g_free (quoted_family);
    }
  else if (category)
    {
      gchar *quoted_category;
      quoted_category = sql_quote (category);
      init_iterator (iterator,
                     "SELECT %s"
                     " FROM nvts"
                     " WHERE category = '%s'"
                     " ORDER BY %s %s;",
                     nvt_iterator_columns (),
                     quoted_category,
                     sort_field ? sort_field : "name",
                     ascending ? "ASC" : "DESC");
      g_free (quoted_category);
    }
  else
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM nvts"
                   " ORDER BY %s %s;",
                   nvt_iterator_columns (),
                   sort_field ? sort_field : "name",
                   ascending ? "ASC" : "DESC");
}

/**
 * @brief Initialise an NVT iterator, for NVTs of a certain CVE.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  cve         CVE name.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_cve_nvt_iterator (iterator_t* iterator, const char *cve, int ascending,
                       const char* sort_field)
{
  init_iterator (iterator,
                 "SELECT %s"
                 " FROM nvts"
                 " WHERE cve LIKE '%%%s%%'"
                 " ORDER BY %s %s;",
                 nvt_iterator_columns (),
                 cve ? cve : "",
                 sort_field ? sort_field : "name",
                 ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the OID from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return OID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_oid, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the version from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Version, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_version, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the name from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_name, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the summary from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Summary, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_summary, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the copyright from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Copyright, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_copyright, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the cve from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Cve, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_cve, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the bid from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Bid, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_bid, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the xref from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Xref, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_xref, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Get the tag from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Tag, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_tag, GET_ITERATOR_COLUMN_COUNT + 8);

/**
 * @brief Get the category from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Category.
 */
int
nvt_iterator_category (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
  return ret;
}

/**
 * @brief Get the family from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Family, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_family, GET_ITERATOR_COLUMN_COUNT + 10);

/**
 * @brief Get the cvss_base from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Cvss_base, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_cvss_base, GET_ITERATOR_COLUMN_COUNT + 11);

/**
 * @brief Get the qod from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return QoD, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_qod, GET_ITERATOR_COLUMN_COUNT + 14);

/**
 * @brief Get the qod_type from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return QoD type, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_qod_type, GET_ITERATOR_COLUMN_COUNT + 15);

/**
 * @brief Get the solution_type from an NVT iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Solution type, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_iterator_solution_type, GET_ITERATOR_COLUMN_COUNT + 16);

/**
 * @brief Get the number of NVTs in one or all families.
 *
 * @param[in]  family  Family name.  NULL for all families.
 *
 * @return Number of NVTs in family, or total number of nvts.
 */
int
family_nvt_count (const char *family)
{
  gchar *quoted_family;

  if (family == NULL)
    {
      static int nvt_count = -1;
      if (nvt_count == -1)
        nvt_count = sql_int ("SELECT COUNT(*) FROM nvts"
                             " WHERE family != 'Credentials';");
      return nvt_count;
    }

  quoted_family = sql_quote (family);
  int ret = sql_int ("SELECT COUNT(*) FROM nvts WHERE family = '%s';",
                     quoted_family);
  g_free (quoted_family);
  return ret;
}

/**
 * @brief Get the number of families.
 *
 * @return Total number of families.
 */
int
family_count ()
{
  return sql_int ("SELECT COUNT(distinct family) FROM nvts"
                  " WHERE family != 'Credentials';");
}

/**
 * @brief Update the cached count and growing information in a config.
 *
 * It's up to the caller to organise a transaction.
 *
 * @param[in]  configs  Config to update.
 */
static void
update_config_cache (iterator_t *configs)
{
  const char *selector;
  gchar *quoted_selector, *quoted_name;
  int families_growing;

  if (config_iterator_type (configs) > 0)
    return;

  quoted_name = sql_quote (get_iterator_name (configs));
  selector = config_iterator_nvt_selector (configs);
  families_growing = nvt_selector_families_growing (selector);
  quoted_selector = sql_quote (selector);

  sql ("UPDATE configs"
       " SET family_count = %i, nvt_count = %i,"
       " families_growing = %i, nvts_growing = %i"
       " WHERE name = '%s';",
       nvt_selector_family_count (quoted_selector, families_growing),
       nvt_selector_nvt_count (quoted_selector, NULL, families_growing),
       families_growing,
       nvt_selector_nvts_growing_2 (quoted_selector, families_growing),
       quoted_name);

  g_free (quoted_name);
  g_free (quoted_selector);
}

/**
 * @brief Update the cached count and growing information in every config.
 *
 * Only consider configs for the current user.
 *
 * It's up to the caller to organise a transaction.
 *
 * @param[in]  config  Config to update.  0 for all.
 */
static void
update_config_caches (config_t config)
{
  iterator_t configs;

  init_user_config_iterator (&configs, config, 0, 1, NULL);
  while (next (&configs))
    update_config_cache (&configs);
  cleanup_iterator (&configs);
}

/**
 * @brief Update count and growing info in every config across all users.
 *
 * It's up to the caller to organise a transaction.
 */
static void
update_all_config_caches ()
{
  static column_t select_columns[] = CONFIG_ITERATOR_COLUMNS;
  gchar *columns;
  iterator_t configs;

  columns = columns_build_select (select_columns);
  init_iterator (&configs, "SELECT %s FROM configs;", columns);
  g_free (columns);
  while (next (&configs))
    update_config_cache (&configs);
  cleanup_iterator (&configs);
}

/**
 * @brief Update count and growing info in config, without checking user.
 *
 * For use during initialisation.
 *
 * @param[in]  uuid  Config UUID.
 *
 * It's up to the caller to organise a transaction.
 */
void
update_config_cache_init (const char *uuid)
{
  static column_t select_columns[] = CONFIG_ITERATOR_COLUMNS;
  gchar *columns;
  iterator_t configs;

  columns = columns_build_select (select_columns);
  init_iterator (&configs,
                 "SELECT %s FROM configs WHERE uuid = '%s';",
                 columns,
                 uuid);
  g_free (columns);
  while (next (&configs))
    update_config_cache (&configs);
  cleanup_iterator (&configs);
}

/**
 * @brief Inserts a nvt from nvti structure.
 *
 * @param[in] nvti  nvti_t to insert in nvts table.
 * @param[in] dummy dummy pointer to match GFunc prototype.
 *
 */
static void
insert_nvt_from_nvti (gpointer nvti, gpointer dummy)
{
  if (nvti == NULL)
    return;

  if (progress)
    progress ();
  make_nvt_from_nvti (nvti, 0);
}

/**
 * @brief Inserts NVTs in DB from a list of nvti_t structures.
 *
 * @param[in] nvts_list     List of nvts to be inserted.
 */
static void
insert_nvts_list (GList *nvts_list)
{
  g_list_foreach (nvts_list, insert_nvt_from_nvti, NULL);
}

/**
 * @brief Complete an update of the NVT cache.
 *
 * @param[in]  nvts_list    List of nvti_t to insert.
 * @param[in]  mode         -1 updating, -2 rebuilding.
 */
void
manage_complete_nvt_cache_update (GList *nvts_list, int mode)
{
  iterator_t configs;

  /* In the case of rebuild, nvti's are in scanner_plugins_list, insert
   * them into DB.
   */
  insert_nvts_list (nvts_list);

  /* Remove preferences from configs where the preference has vanished from
   * the associated NVT. */
  if (progress)
    progress ();
  init_iterator (&configs, "SELECT id FROM configs;");
  while (next (&configs))
    sql ("DELETE FROM config_preferences"
         " WHERE config = %llu"
         " AND type = 'PLUGINS_PREFS'"
         " AND name NOT IN (SELECT nvt_preferences.name FROM nvt_preferences);",
         get_iterator_resource (&configs));
  cleanup_iterator (&configs);

  if (progress)
    progress ();
  update_all_config_caches ();
  if (progress)
    progress ();
  refresh_nvt_cves ();

  if (mode == -2) sql ("COMMIT;");

  if (progress)
    progress ();
}


/* NVT selectors.
 *
 * An NVT selector is a named selection of NVT's from the cache of all
 * NVT's.
 *
 * An NVT selector is made up of zero or more selectors.  The selectors
 * combine in id order to make a selection.  Depending on the choice
 * of selectors the selection can be static or growing.  A growing
 * selection can grow when new NVT's enter the NVT cache, either because it
 * selects new families or because it selects new NVT's within exising
 * families.
 *
 * There are three types of selectors that an NVT selector can contain.
 *
 *   1) The "all selector", which selects all families and all NVT's in
 *      those families.  The only way to construct the NVT selector so
 *      that it grows to includes new families, is to add this selector.
 *
 *   2) A "family" selector, which designates an entire family.
 *
 *   3) An "NVT" selector, which designates a single NVT.
 *
 *      The naming overlaps here.  It's a selector of type NVT, which is
 *      part of an "NVT selector" (a named collection of selectors).
 *
 * The family and NVT type selectors can either include or exclude the
 * designated NVT's.
 *
 * While the all selector provides a way to select every single NVT, the
 * empty NVT selector corresponds to an empty NVT set.
 *
 * The selectors provide a mechanism to select a wide range of NVT
 * combinations.  The mechanism allows for complex selections involving
 * redundant selectors.  The Manager, however, only implements a simple
 * subset of the possible combinations of selectors.  This simple subset
 * is split into two cases.
 *
 *   1) Constraining the universe.
 *
 *      The all selector and an optional exclude for each family,
 *      optional NVT includes in the excluded families, and optional NVT
 *      includes in all other families.
 *
 *      This allows a growing collection of families, while any family
 *      can still have a static NVT selection.
 *
 *   2) Generating from empty.
 *
 *      An empty set of selectors with an optional include for each family,
 *      optional NVT excludes in the included families, and optional NVT
 *      includes in all other families.
 *
 *      This allows a static collection of families, while any family
 *      can still grow when new NVT's enter the family.
 *
 * Either case allows one or more NVT's to be excluded from the family, both
 * when the family is growing and when the family is static.
 */

/* These could handle strange cases, like when a family is
 * included then excluded, or all is included then later excluded.
 * However, OMP prevents those cases from occuring. */

/**
 * @brief Get the number of families selected by an NVT selector.
 *
 * A growing family which has all current NVT's excluded is still
 * considered as selected by the NVT selector.
 *
 * @param[in]  quoted_selector   SQL-quoted selector name.
 * @param[in]  families_growing  1 if families are growing, else 0.
 *
 * @return The number of families selected by an NVT selector.
 */
int
nvt_selector_family_count (const char* quoted_selector, int families_growing)
{
  if (families_growing)
    /* Assume the only family selectors are excludes. */
    return family_count ()
           - sql_int ("SELECT COUNT(distinct family_or_nvt) FROM nvt_selectors"
                      " WHERE name = '%s'"
                      " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                      " AND exclude = 0"
                      " LIMIT 1;",
                      quoted_selector);

  /* Assume that the only family selectors are includes, and that if a
   * selection has any NVT includes then it only has NVT includes. */
  return sql_int ("SELECT COUNT (DISTINCT family)"
                  " FROM (SELECT DISTINCT family FROM nvt_selectors"
                  "       WHERE name = '%s'"
                  "       AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                  "       AND exclude = 0"
                  "       UNION SELECT family FROM nvt_selectors"
                  "             WHERE name = '%s'"
                  "             AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                  "             AND exclude = 0"
                  "             AND family IS NOT NULL) AS subquery;",
                  quoted_selector,
                  quoted_selector);
}

/**
 * @brief Get the family growth status of an NVT selector.
 *
 * @param[in]  selector  NVT selector.
 *
 * @return 1 growing, 0 static.
 */
static int
nvt_selector_families_growing (const char* selector)
{
  /** @todo Quote selector. */
  /* The number of families can only grow if there is selector that includes
   * all. */
#if 0
  return sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                  " WHERE name = '%s'"
                  " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL)
                  " AND exclude = 0"
                  " LIMIT 1;",
                  selector);
#else
  char *string;
  string = sql_string ("SELECT name FROM nvt_selectors"
                       " WHERE name = '%s'"
                       " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL)
                       " AND exclude = 0"
                       " LIMIT 1;",
                       selector);
  if (string == NULL) return 0;
  free (string);
  return 1;
#endif
}

/**
 * @brief Get the NVT growth status of an NVT selector.
 *
 * @param[in]  quoted_selector   SQL-quoted selector name.
 * @param[in]  families_growing  1 if families are growing, else 0.
 *
 * @return 1 growing, 0 static.
 */
static int
nvt_selector_nvts_growing_2 (const char* quoted_selector, int families_growing)
{
  if (families_growing)
    /* Assume the only family selectors are excludes. */
    return (family_count ()
            - sql_int ("SELECT COUNT(distinct family_or_nvt) FROM nvt_selectors"
                       " WHERE name = '%s'"
                       " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                       " AND exclude = 0"
                       " LIMIT 1;",
                       quoted_selector))
           > 0;

  /* Assume the only family selectors are includes. */
  return sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                  " WHERE name = '%s'"
                  " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                  " AND exclude = 0"
                  " LIMIT 1;",
                  quoted_selector)
         > 0;
}

/** @todo Move these config functions to the config section. */

/**
 * @brief Get the NVT growth status of a config.
 *
 * @param[in]  config  Config.
 *
 * @return 1 growing, 0 static.
 */
int
config_nvts_growing (config_t config)
{
  return sql_int ("SELECT nvts_growing FROM configs"
                  " WHERE id = %llu;",
                  config);
}

/**
 * @brief Get the family growth status of a config.
 *
 * @param[in]  config  Config.
 *
 * @return 1 growing, 0 static.
 */
int
config_families_growing (config_t config)
{
  return sql_int ("SELECT families_growing FROM configs"
                  " WHERE id = %llu;",
                  config);
}

/**
 * @brief Initialise an NVT selector iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  selector  Name of single selector to iterate over, NULL for all.
 * @param[in]  config    Config to limit iteration to, 0 for all.
 * @param[in]  type      Type of selector.  All if config is given.
 */
void
init_nvt_selector_iterator (iterator_t* iterator, const char* selector,
                            config_t config, int type)
{
  gchar *sql;

  assert (selector ? config == 0 : (config ? selector == NULL : 1));
  assert (config ? type == NVT_SELECTOR_TYPE_ANY : (type >= 0 && type <= 2));

  if (selector)
    {
      gchar *quoted_selector = sql_quote (selector);
      sql = g_strdup_printf ("SELECT exclude, family_or_nvt, name, type"
                             " FROM nvt_selectors"
                             " WHERE name = '%s' AND type = %i;",
                             quoted_selector,
                             type);
      g_free (quoted_selector);
    }
  else if (config)
    sql = g_strdup_printf ("SELECT exclude, family_or_nvt, name, type"
                           " FROM nvt_selectors"
                           " WHERE name ="
                           " (SELECT nvt_selector FROM configs"
                           "  WHERE configs.id = %llu);",
                           config);
  else
    sql = g_strdup_printf ("SELECT exclude, family_or_nvt, name, type"
                           " FROM nvt_selectors"
                           " WHERE type = %i;",
                           type);
  init_iterator (iterator, sql);
  g_free (sql);
}

/**
 * @brief Get whether the selector rule is an include rule.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return -1 if iteration is complete, 1 if include, else 0.
 */
int
nvt_selector_iterator_include (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 0);
  return ret == 0;
}

/**
 * @brief Get the NVT or family from an NVT selector iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT selector, or NULL if iteration is complete.
 */
DEF_ACCESS (nvt_selector_iterator_nvt, 1);

/**
 * @brief Get the name from an NVT selector iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT selector, or NULL if iteration is complete.
 */
DEF_ACCESS (nvt_selector_iterator_name, 2);

/**
 * @brief Get the type from an NVT selector.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return -1 if iteration is complete, 1 if include, else 0.
 */
int
nvt_selector_iterator_type (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 3);
  return ret;
}

/**
 * @brief Initialise an NVT selector family iterator.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  all        True if families are growing in the selector, else 0.
 *                        Only considered with a selector.
 * @param[in]  selector   Name of NVT selector.  NULL for all families.
 * @param[in]  ascending  Whether to sort ascending or descending.
 */
void
init_family_iterator (iterator_t* iterator, int all, const char* selector,
                      int ascending)
{
  gchar *quoted_selector;

  if (selector == NULL)
    {
      init_iterator (iterator,
                     "SELECT distinct family FROM nvts"
                     " WHERE family != 'Credentials'"
                     " ORDER BY family %s;",
                     ascending ? "ASC" : "DESC");
      return;
    }

  quoted_selector = sql_quote (selector);
  if (all)
    /* Constraining the universe.  Presume there is a family exclude for
     * every NVT include. */
    init_iterator (iterator,
                   "SELECT distinct family FROM nvts"
                   " WHERE family != 'Credentials'"
                   " EXCEPT"
                   " SELECT distinct family FROM nvt_selectors"
                   " WHERE type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                   " AND exclude = 1"
                   " AND name = '%s'"
                   " UNION"
                   " SELECT distinct family FROM nvt_selectors"
                   " WHERE type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                   " AND exclude = 0"
                   " AND name = '%s'"
                   " ORDER BY 1 %s;", /* 1 is family. */
                   quoted_selector,
                   quoted_selector,
                   ascending ? "ASC" : "DESC");
  else
    /* Generating from empty.  Presume any exclude is covered by an include. */
    init_iterator (iterator,
                   "SELECT distinct family FROM nvt_selectors"
                   " WHERE (type = 1 OR type = 2) AND name = '%s'"
                   " AND family != 'Credentials'"
                   " ORDER BY 1 %s;", /* 1 is family. */
                   quoted_selector,
                   ascending ? "ASC" : "DESC");
  g_free (quoted_selector);
}

/**
 * @brief Get the name from a family iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (family_iterator_name, 0);

/**
 * @brief Get whether an NVT selector family is growing.
 *
 * @param[in]  selector  NVT selector.
 * @param[in]  family    Family name.
 * @param[in]  all       True if selector is an "all" selector, else 0.
 *
 * @return 1 growing, 0 static.
 */
int
nvt_selector_family_growing (const char *selector,
                             const char *family,
                             int all)
{
  int ret;
  gchar *quoted_family;
  gchar *quoted_selector;

  quoted_selector = sql_quote (selector);
  quoted_family = sql_quote (family);

  if (all)
    {
      /* Constraining the universe.  It's static if there is a family
       * exclude. */

      ret = sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                     " WHERE name = '%s'"
                     " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                     " AND family_or_nvt = '%s'"
                     " AND exclude = 1"
                     " LIMIT 1;",
                     quoted_selector,
                     quoted_family);

      g_free (quoted_selector);
      g_free (quoted_family);

      return ret ? 0 : 1;
    }

  /* Generating from empty.  It's growing if there is a family include. */

  ret = sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                 " WHERE name = '%s'"
                 " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                 " AND family_or_nvt = '%s'"
                 " AND exclude = 0"
                 " LIMIT 1;",
                 quoted_selector,
                 quoted_family);

  g_free (quoted_selector);
  g_free (quoted_family);

  return ret ? 1 : 0;
}

/**
 * @brief Get the number of NVTs selected by an NVT selector.
 *
 * @param[in]  selector  NVT selector.
 * @param[in]  family    Family name.  NULL for all.
 * @param[in]  growing   True if the given family is growing, else 0.
 *                       If \param family is NULL, true if the the families
 *                       are growing, else 0.
 *
 * @return Number of NVTs selected in one or all families.
 */
int
nvt_selector_nvt_count (const char *selector,
                        const char *family,
                        int growing)
{
  if (family)
    {
      int ret;

      /* Count in a single family. */

      if (growing)
        {
          gchar *quoted_family = sql_quote (family);
          gchar *quoted_selector = sql_quote (selector);
          ret = sql_int ("SELECT COUNT(*) FROM nvts WHERE family = '%s';",
                         quoted_family);
          ret -= sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                          " WHERE exclude = 1 AND type = 2"
                          " AND name = '%s' AND family = '%s';",
                          quoted_selector,
                          quoted_family);
          g_free (quoted_family);
          g_free (quoted_selector);
        }
      else
        {
          gchar *quoted_selector = sql_quote (selector);
          gchar *quoted_family = sql_quote (family);
          ret = sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                         " WHERE exclude = 0 AND type = 2"
                         " AND name = '%s' AND family = '%s';",
                         quoted_selector,
                         quoted_family);
          g_free (quoted_family);
          g_free (quoted_selector);
        }

      return ret;
   }
 else
   {
     int count;
     iterator_t families;

     /* Count in each family. */

     count = 0;
     init_family_iterator (&families, 0, NULL, 1);
     while (next (&families))
       {
         const char *family = family_iterator_name (&families);
         if (family)
           count += nvt_selector_nvt_count (selector,
                                            family,
                                            nvt_selector_family_growing
                                             (selector, family, growing));
       }
     cleanup_iterator (&families);

     return count;
   }
}

/**
 * @brief Return SQL for selecting NVT's of a config from one family.
 *
 * @param[in]  config      Config.
 * @param[in]  family      Family to limit selection to.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "nvts.id".
 *
 * @return Freshly allocated SELECT statement on success, or NULL on error.
 */
static gchar*
select_config_nvts (const config_t config, const char* family, int ascending,
                    const char* sort_field)
{
  gchar *quoted_selector;
  char *selector = config_nvt_selector (config);
  if (selector == NULL)
    /* The config should always have a selector. */
    return NULL;

  /** @todo Free. */
  quoted_selector = sql_quote (selector);
  free (selector);

  /** @todo Quote family. */

  if (config_nvts_growing (config))
    {
      int constraining;

      /* The number of NVT's can increase. */

      constraining = config_families_growing (config);

      if (constraining)
        {
          /* Constraining the universe. */

          if (sql_int ("SELECT COUNT(*) FROM nvt_selectors WHERE name = '%s';",
                       quoted_selector)
              == 1)
            /* There is one selector, it should be the all selector. */
            return g_strdup_printf
                    ("SELECT %s"
                     " FROM nvts WHERE family = '%s'"
                     " ORDER BY %s %s;",
                     nvt_iterator_columns (),
                     family,
                     sort_field ? sort_field : "name",
                     ascending ? "ASC" : "DESC");

          /* There are multiple selectors. */

          if (sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                       " WHERE name = '%s' AND exclude = 1"
                       " AND type = "
                       G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                       " AND family_or_nvt = '%s'"
                       ";",
                       quoted_selector,
                       family))
            /* The family is excluded, just iterate the NVT includes. */
            return g_strdup_printf
                    ("SELECT %s"
                     " FROM nvts, nvt_selectors"
                     " WHERE"
                     " nvts.family = '%s'"
                     " AND nvt_selectors.name = '%s'"
                     " AND nvt_selectors.family = '%s'"
                     " AND nvt_selectors.type = "
                     G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                     " AND nvt_selectors.exclude = 0"
                     " AND nvts.oid = nvt_selectors.family_or_nvt"
                     " ORDER BY %s %s;",
                     nvt_iterator_columns_nvts (),
                     family,
                     quoted_selector,
                     family,
                     sort_field ? sort_field : "nvts.name",
                     ascending ? "ASC" : "DESC");

          /* The family is included.  Iterate all NVT's minus excluded NVT's. */
          return g_strdup_printf
                  ("SELECT %s"
                   " FROM nvts"
                   " WHERE family = '%s'"
                   " EXCEPT"
                   " SELECT %s"
                   " FROM nvt_selectors, nvts"
                   " WHERE"
                   " nvts.family = '%s'"
                   " AND nvt_selectors.name = '%s'"
                   " AND nvt_selectors.family = '%s'"
                   " AND nvt_selectors.type = "
                   G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                   " AND nvt_selectors.exclude = 1"
                   " AND nvts.oid = nvt_selectors.family_or_nvt"
                   " ORDER BY %s %s;",
                   nvt_iterator_columns (),
                   family,
                   nvt_iterator_columns_nvts (),
                   family,
                   quoted_selector,
                   family,
                   // FIX PG "ERROR: missing FROM-clause" using nvts.name.
                   sort_field && strcmp (sort_field, "nvts.name")
                    ? sort_field : "3", /* 3 is nvts.name. */
                   ascending ? "ASC" : "DESC");
        }
      else
        {
          int all;

          /* Generating from empty. */

          all = sql_int ("SELECT COUNT(*) FROM nvt_selectors"
                         " WHERE name = '%s' AND exclude = 0"
                         " AND type = "
                         G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                         " AND family_or_nvt = '%s';",
                         quoted_selector,
                         family);

          if (all)
            /* There is a family include for this family. */
            return g_strdup_printf
                    ("SELECT %s"
                     " FROM nvts"
                     " WHERE family = '%s'"
                     " EXCEPT"
                     " SELECT %s"
                     " FROM nvt_selectors, nvts"
                     " WHERE"
                     " nvts.family = '%s'"
                     " AND nvt_selectors.name = '%s'"
                     " AND nvt_selectors.family = '%s'"
                     " AND nvt_selectors.type = "
                     G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                     " AND nvt_selectors.exclude = 1"
                     " AND nvts.oid = nvt_selectors.family_or_nvt"
                     " ORDER BY %s %s;",
                     nvt_iterator_columns (),
                     family,
                     nvt_iterator_columns_nvts (),
                     family,
                     quoted_selector,
                     family,
                     // FIX PG "ERROR: missing FROM-clause" using nvts.name.
                     sort_field && strcmp (sort_field, "nvts.name")
                      ? sort_field : "3", /* 3 is nvts.name. */
                     ascending ? "ASC" : "DESC");

          return g_strdup_printf
                  (" SELECT %s"
                   " FROM nvt_selectors, nvts"
                   " WHERE"
                   " nvts.family = '%s'"
                   " AND nvt_selectors.name = '%s'"
                   " AND nvt_selectors.family = '%s'"
                   " AND nvt_selectors.type = "
                   G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                   " AND nvt_selectors.exclude = 0"
                   " AND nvts.oid = nvt_selectors.family_or_nvt"
                   " ORDER BY %s %s;",
                   nvt_iterator_columns_nvts (),
                   family,
                   quoted_selector,
                   family,
                   sort_field ? sort_field : "nvts.name",
                   ascending ? "ASC" : "DESC");
        }
    }
  else
    {
      gchar *sql, *quoted_family;

      /* The number of NVT's is static.  Assume a simple list of NVT
       * includes. */

      quoted_family = sql_quote (family);
      sql = g_strdup_printf
             ("SELECT %s"
              " FROM nvt_selectors, nvts"
              " WHERE nvts.family = '%s'"
              " AND nvt_selectors.exclude = 0"
              " AND nvt_selectors.type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
              " AND nvt_selectors.name = '%s'"
              " AND nvts.oid = nvt_selectors.family_or_nvt"
              " ORDER BY %s %s;",
              nvt_iterator_columns_nvts (),
              quoted_family,
              quoted_selector,
              sort_field ? sort_field : "nvts.id",
              ascending ? "ASC" : "DESC");
      g_free (quoted_family);

      return sql;
    }
}

/**
 * @brief Remove all selectors of a certain family from an NVT selector.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  quoted_family    SQL-quoted family name.
 * @param[in]  type             Selector type to remove.
 *
 * @return 0 success, -1 error.
 */
static void
nvt_selector_remove (const char* quoted_selector,
                     const char* quoted_family,
                     int type)
{
  if (type == NVT_SELECTOR_TYPE_ANY)
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s'"
         " AND"
         " ((type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
         "   AND family = '%s')"
         "  OR (type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
         "      AND family_or_nvt = '%s'));",
         quoted_selector,
         quoted_family,
         quoted_family);
  else if (type == NVT_SELECTOR_TYPE_NVT)
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s'"
         " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
         " AND family = '%s';",
         quoted_selector,
         quoted_family);
  else if (type == NVT_SELECTOR_TYPE_FAMILY)
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s'"
         " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
         " AND family_or_nvt = '%s';",
         quoted_selector,
         quoted_family);
}

/**
 * @brief Remove all selectors of a certain type from an NVT selector.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  family_or_nvt    SQL-quoted family name or NVT UUID.
 * @param[in]  type             Selector type to remove.
 *
 * @return 0 success, -1 error.
 */
static void
nvt_selector_remove_selector (const char* quoted_selector,
                              const char* family_or_nvt,
                              int type)
{
  if (type == NVT_SELECTOR_TYPE_ANY)
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s' AND family_or_nvt = '%s');",
         quoted_selector,
         family_or_nvt);
  else if (type == NVT_SELECTOR_TYPE_ALL)
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s'"
         " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL) ";",
         quoted_selector);
  else
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '%s'"
         " AND type = %i"
         " AND family_or_nvt = '%s';",
         quoted_selector,
         type,
         family_or_nvt);
}

/**
 * @brief Add a selector to an NVT selector.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  quoted_family_or_nvt  SQL-quoted family or NVT name.
 * @param[in]  quoted_family    SQL-quoted family name (NULL for families).
 * @param[in]  exclude          1 exclude selector, 0 include selector.
 *
 * @return 0 success, -1 error.
 */
static void
nvt_selector_add (const char* quoted_selector,
                  const char* quoted_family_or_nvt,
                  const char* quoted_family,
                  int exclude)
{
  if (quoted_family == NULL)
    sql ("INSERT INTO nvt_selectors"
         " (name, exclude, type, family_or_nvt, family)"
         " VALUES ('%s', %i, "
         G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
         ", '%s', '%s');",
         quoted_selector,
         exclude,
         quoted_family_or_nvt,
         quoted_family_or_nvt);
  else
    sql ("INSERT INTO nvt_selectors"
         " (name, exclude, type, family_or_nvt, family)"
         " VALUES ('%s', %i, "
         G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
         ", '%s', '%s');",
         quoted_selector,
         exclude,
         quoted_family_or_nvt,
         quoted_family);
}

/**
 * @brief Check whether a family is selected.
 *
 * Only works for "generating from empty" selection.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  quoted_family    SQL-quoted family name (NULL for families).
 *
 * @return 1 if selected, else 0.
 */
static int
family_is_selected (const char* quoted_selector, const char* quoted_family)
{
  return sql_int ("SELECT count(*) FROM nvt_selectors"
                  " WHERE name = '%s'"
                  " AND (type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT)
                  "      AND family = '%s')"
                  " OR (type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY)
                  "     AND family_or_nvt = '%s');",
                  quoted_selector,
                  quoted_family,
                  quoted_family);
}

/**
 * @brief Check whether an NVT selector has a particular selector.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  family_or_nvt    SQL-quoted UUID of NVT, or family name.
 * @param[in]  type             Selector type.
 * @param[in]  exclude          1 exclude, 0 include.
 *
 * @return 1 if contains include/exclude, else 0.
 */
static int
nvt_selector_has (const char* quoted_selector, const char* family_or_nvt,
                  int type, int exclude)
{
  return sql_int ("SELECT count(*) FROM nvt_selectors"
                  " WHERE name = '%s'"
                  " AND type = %i"
                  " AND exclude = %i"
                  " AND family_or_nvt = '%s'"
                  " LIMIT 1;",
                  quoted_selector,
                  type,
                  exclude,
                  family_or_nvt);
}

/**
 * @brief Refresh NVT selection of a config from given families.
 *
 * @param[in]  config                Config.
 * @param[in]  growing_all_families  Growing families with all selection.
 * @param[in]  static_all_families   Static families with all selection.
 * @param[in]  growing_families      The rest of the growing families.
 * @param[in]  grow_families         1 if families should grow, else 0.
 *
 * @return 0 success, config in use, -1 error.
 */
int
manage_set_config_families (config_t config,
                            GPtrArray* growing_all_families,
                            GPtrArray* static_all_families,
                            GPtrArray* growing_families,
                            int grow_families)
{
  iterator_t families;
  gchar *quoted_selector;
  int constraining;
  char *selector;

  sql_begin_exclusive ();

  if (sql_int ("SELECT count(*) FROM tasks"
               " WHERE config = %llu AND (hidden = 0 OR hidden = 1);",
               config))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  if (config_type (config) > 0)
    {
      sql ("ROLLBACK;");
      return 0;
    }
  constraining = config_families_growing (config);

  if (constraining + grow_families == 1)
    {
      if (switch_representation (config, constraining))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      constraining = constraining == 0;
    }

  selector = config_nvt_selector (config);
  if (selector == NULL)
    {
      /* The config should always have a selector. */
      sql ("ROLLBACK;");
      return -1;
    }
  quoted_selector = sql_quote (selector);

  /* Loop through all the known families. */

  init_family_iterator (&families, 1, NULL, 1);
  while (next (&families))
    {
      const char *family;

      family = family_iterator_name (&families);
      if (family)
        {
          int old_nvt_count, new_nvt_count = 0, was_selected, max_nvt_count;
          int family_growing;
          int growing_all = member (growing_all_families, family);
          int static_all = member (static_all_families, family);
          gchar *quoted_family = sql_quote (family);

          assert ((growing_all && static_all) == 0);

          family_growing = nvt_selector_family_growing (selector,
                                                        family,
                                                        constraining);

          old_nvt_count
            = nvt_selector_nvt_count (selector, family, family_growing);

          max_nvt_count = family_nvt_count (family);

          if (growing_all || static_all)
            {
              if (old_nvt_count == max_nvt_count
                  && ((growing_all && family_growing)
                      || (static_all && family_growing == 0)))
                {
                  /* Already in required state. */
                  g_free (quoted_family);
                  continue;
                }

              was_selected = family_is_selected (quoted_selector,
                                                 quoted_family);

              /* Flush all selectors in the family from the config. */

              nvt_selector_remove (quoted_selector,
                                   quoted_family,
                                   NVT_SELECTOR_TYPE_ANY);

              if (static_all)
                {
                  iterator_t nvts;

                  /* Static selection of all the NVT's currently in the
                   * family. */

                  if (constraining)
                    {
                      /* Constraining the universe. */

                      /* Add an exclude for the family. */

                      nvt_selector_add (quoted_selector,
                                        quoted_family,
                                        NULL,
                                        1);
                    }
                  else
                    {
                      /* Generating from empty. */
                    }

                  /* Add an include for every NVT in the family. */

                  init_nvt_iterator (&nvts, (nvt_t) 0, (config_t) 0, family,
                                     NULL, 1, NULL);
                  while (next (&nvts))
                    {
                      nvt_selector_add (quoted_selector,
                                        nvt_iterator_oid (&nvts),
                                        quoted_family,
                                        0);
                      new_nvt_count++;
                    }
                  cleanup_iterator (&nvts);
                }
              else if (growing_all)
                {
                  /* Selection of an entire family, which grows with the family. */

                  if (constraining)
                    {
                      /* Constraining the universe. */
                    }
                  else
                    {
                      /* Generating from empty.  Add an include for the
                       * family. */

                      nvt_selector_add (quoted_selector,
                                        quoted_family,
                                        NULL,
                                        0);

                    }

                  new_nvt_count = max_nvt_count;
                }

              /* Update the cached config info. */

              sql ("UPDATE configs SET nvt_count = nvt_count - %i + %i,"
                   " nvts_growing = %i, family_count = family_count + %i,"
                   " modification_time = m_now ()"
                   " WHERE id = %llu;",
                   old_nvt_count,
                   new_nvt_count,
                   growing_all
                    ? 1
                    /* Recalculate the NVT growing state. */
                    : nvt_selector_nvts_growing_2 (quoted_selector,
                                                   constraining),
                   was_selected ? 0 : 1,
                   config);
            }
          else
            {
              int must_grow = member (growing_families, family);

              if (must_grow)
                {
                  /* The resulting family must be growing.  If currently
                   * growing, leave as is, otherwise switch family to
                   * growing. */

                  if (old_nvt_count == max_nvt_count)
                    {
                      iterator_t nvts;

                      /* All were selected.  Clear selection, ensuring that
                       * the family is growing in the process.  */

                      nvt_selector_remove (quoted_selector,
                                           quoted_family,
                                           NVT_SELECTOR_TYPE_ANY);

                      if (constraining == 0)
                        /* Generating. */
                        nvt_selector_add (quoted_selector,
                                          quoted_family,
                                          NULL,
                                          0);

                      /* Add an exclude for every NVT in the family. */

                      init_nvt_iterator (&nvts, (nvt_t) 0, (config_t) 0,
                                         family, NULL, 1, NULL);
                      while (next (&nvts))
                        nvt_selector_add (quoted_selector,
                                          nvt_iterator_oid (&nvts),
                                          quoted_family,
                                          1);
                      cleanup_iterator (&nvts);

                      /* Update the cached config info. */

                      sql ("UPDATE configs SET nvt_count = nvt_count - %i,"
                           " nvts_growing = 1, modification_time = m_now ()"
                           " WHERE id = %llu;",
                           old_nvt_count,
                           config);
                    }
                  else if (family_growing == 0)
                    {
                      iterator_t nvts;

                      if (constraining == 0)
                        nvt_selector_add (quoted_selector,
                                          quoted_family,
                                          NULL,
                                          0);

                      /* Remove any included NVT, add excludes for all
                       * other NVT's. */

                      init_nvt_iterator (&nvts, (nvt_t) 0, (config_t) 0,
                                         family, NULL, 1, NULL);
                      while (next (&nvts))
                        if (nvt_selector_has (quoted_selector,
                                              nvt_iterator_oid (&nvts),
                                              NVT_SELECTOR_TYPE_NVT,
                                              0))
                          nvt_selector_remove_selector
                           (quoted_selector,
                            nvt_iterator_oid (&nvts),
                            NVT_SELECTOR_TYPE_NVT);
                        else
                          nvt_selector_add (quoted_selector,
                                            nvt_iterator_oid (&nvts),
                                            quoted_family,
                                            1);
                      cleanup_iterator (&nvts);

                      /* Update the cached config info. */

                      sql ("UPDATE configs SET nvts_growing = 1,"
                           " modification_time = m_now ()"
                           " WHERE id = %llu;",
                           config);
                    }
                }
              else
                {
                  /* The resulting family must be static.  If currently
                   * static, leave as is, otherwise switch family to
                   * static. */

                  if (old_nvt_count == max_nvt_count)
                    {
                      /* All were selected, clear selection, ensuring the
                       * family is static in the process. */

                      nvt_selector_remove (quoted_selector,
                                           quoted_family,
                                           NVT_SELECTOR_TYPE_ANY);
                      if (constraining)
                        nvt_selector_add (quoted_selector,
                                          quoted_family,
                                          NULL,
                                          1);

                      /* Update the cached config info. */

                      sql ("UPDATE configs SET nvts_growing = %i,"
                           " nvt_count = nvt_count - %i,"
                           " family_count = family_count - 1,"
                           " modification_time = m_now ()"
                           " WHERE id = %llu;",
                           /* Recalculate the NVT growing state. */
                           nvt_selector_nvts_growing_2 (quoted_selector,
                                                        constraining),
                           old_nvt_count,
                           config);
                    }
                  else if (family_growing)
                    {
                      iterator_t nvts;

                      if (constraining)
                        nvt_selector_add (quoted_selector,
                                          quoted_family,
                                          NULL,
                                          1);
                      else
                        nvt_selector_remove (quoted_selector,
                                             quoted_family,
                                             NVT_SELECTOR_TYPE_FAMILY);

                      /* Remove any excluded NVT; add includes for all
                       * other NVT's. */

                      init_nvt_iterator (&nvts, (nvt_t) 0, (config_t) 0,
                                         family, NULL, 1, NULL);
                      while (next (&nvts))
                        if (nvt_selector_has (quoted_selector,
                                              nvt_iterator_oid (&nvts),
                                              NVT_SELECTOR_TYPE_NVT,
                                              1))
                          nvt_selector_remove_selector
                            (quoted_selector,
                             nvt_iterator_oid (&nvts),
                             NVT_SELECTOR_TYPE_NVT);
                        else
                          nvt_selector_add (quoted_selector,
                                            nvt_iterator_oid (&nvts),
                                            quoted_family,
                                            0);
                      cleanup_iterator (&nvts);

                      /* Update the cached config info. */

                      sql ("UPDATE configs SET nvts_growing = %i,"
                           " modification_time = m_now ()"
                           " WHERE id = %llu;",
                           /* Recalculate the NVT growing state. */
                           nvt_selector_nvts_growing_2 (quoted_selector,
                                                        constraining),
                           config);
                    }
                }
            }

          g_free (quoted_family);
        }
    }
  cleanup_iterator (&families);

  sql ("COMMIT;");

  g_free (quoted_selector);
  free (selector);
  return 0;
}

/**
 * @brief Insert NVT selectors.
 *
 * @param[in]  quoted_name  Name of NVT selector.
 * @param[in]  selectors    NVT selectors.
 *
 * @return 0 success, -1 error, -3 input error.
 */
static int
insert_nvt_selectors (const char *quoted_name,
                      const array_t* selectors /* nvt_selector_t. */)
{
  int index = 0;
  const nvt_selector_t *selector;
  if (selectors == NULL) return -3;
  while ((selector = (nvt_selector_t*) g_ptr_array_index (selectors, index++)))
    {
      int type;

      if (selector->type == NULL) return -3;

      /** @todo Check that selector->type is actually an integer. */
      type = atoi (selector->type);

      if ((selector->family_or_nvt != NULL)
          && (type == NVT_SELECTOR_TYPE_NVT))
        {
          gchar *quoted_family_or_nvt, *quoted_family, *family = NULL;
          nvti_t *nvti = nvtis_lookup (nvti_cache, selector->family_or_nvt);

          /* An NVT selector. */

          if (nvti)
            {
              family = nvti_family (nvti);

              if (family == NULL)
                {
                  g_warning ("%s: skipping NVT '%s' from import of config '%s'"
                             " because the NVT is missing a family in the"
                             " cache",
                             __FUNCTION__,
                             selector->family_or_nvt,
                             quoted_name);
                  continue;
                }
            }
          else
            {
              g_warning ("%s: skipping NVT '%s' from import of config '%s'"
                         " because the NVT is missing from the cache",
                         __FUNCTION__,
                         selector->family_or_nvt,
                         quoted_name);
              continue;
            }

          quoted_family_or_nvt = sql_quote (selector->family_or_nvt);
          quoted_family = sql_quote (family);
          sql ("INSERT into nvt_selectors (name, exclude, type, family_or_nvt,"
               " family)"
               " VALUES ('%s', %i, %i, '%s', '%s');",
               quoted_name,
               selector->include ? 0 : 1,
               type,
               quoted_family_or_nvt,
               quoted_family);
          g_free (quoted_family_or_nvt);
          g_free (quoted_family);
        }
      else if (selector->family_or_nvt)
        {
          gchar *quoted_family_or_nvt;

          /* A family selector. */

          if (type != NVT_SELECTOR_TYPE_FAMILY)
            {
              g_warning ("%s: skipping NVT '%s' from import of config '%s'"
                         " because the type is wrong (expected family)",
                         __FUNCTION__,
                         selector->family_or_nvt,
                         quoted_name);
              continue;
            }

          quoted_family_or_nvt = sql_quote (selector->family_or_nvt);

          sql ("INSERT into nvt_selectors (name, exclude, type, family_or_nvt,"
               " family)"
               " VALUES ('%s', %i, %i, '%s', '%s');",
               quoted_name,
               selector->include ? 0 : 1,
               type,
               quoted_family_or_nvt,
               quoted_family_or_nvt);
          g_free (quoted_family_or_nvt);
        }
      else
        {
          /* An "all" selector. */

          if (type != NVT_SELECTOR_TYPE_ALL)
            {
              g_warning ("%s: skipping NVT from import of config '%s'"
                         " because the type is wrong (expected all)",
                         __FUNCTION__,
                         quoted_name);
              continue;
            }

          sql ("INSERT into nvt_selectors (name, exclude, type, family_or_nvt,"
               " family)"
               " VALUES ('%s', %i, %i, NULL, NULL);",
               quoted_name,
               selector->include ? 0 : 1,
               type);
        }
    }
  return 0;
}


/* NVT preferences. */

/**
 * @brief Add an NVT preference.
 *
 * @param[in]  name    The name of the preference.
 * @param[in]  value   The value of the preference.
 * @param[in]  remove  Whether to remove the preference from the database first.
 */
void
manage_nvt_preference_add (const char* name, const char* value, int remove)
{
  gchar* quoted_name = sql_quote (name);
  gchar* quoted_value = sql_quote (value);

  if (remove)
    {
      sql_begin_exclusive ();
      sql ("DELETE FROM nvt_preferences WHERE name = '%s';", quoted_name);
    }

  if (strcmp (name, "port_range"))
    sql ("INSERT into nvt_preferences (name, value)"
         " VALUES ('%s', '%s');",
         quoted_name, quoted_value);

  if (remove)
    sql ("COMMIT;");

  g_free (quoted_name);
  g_free (quoted_value);
}

/**
 * @brief Enable the NVT preferences.
 */
void
manage_nvt_preferences_enable ()
{
  sql ("DELETE FROM %s.meta WHERE name = 'nvt_preferences_enabled';",
       sql_schema ());
  sql ("INSERT INTO %s.meta (name, value)"
       " VALUES ('nvt_preferences_enabled', 1);",
       sql_schema ());
}

/**
 * @brief Initialise an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  name      Name of NVT, NULL for all preferences.
 */
void
init_nvt_preference_iterator (iterator_t* iterator, const char *name)
{
  if (name)
    {
      gchar *quoted_name = sql_quote (name);
      init_iterator (iterator,
                     "SELECT name, value FROM nvt_preferences"
                     " WHERE name LIKE '%s[%%'"
                     " AND name != 'cache_folder'"
                     " AND name != 'include_folders'"
                     " AND name != 'nasl_no_signature_check'"
                     " AND name != 'network_targets'"
                     " AND name != 'ntp_save_sessions'"
                     " AND name NOT LIKE 'server_info_%%'"
                     /* Task preferences. */
                     " AND name != 'max_checks'"
                     " AND name != 'max_hosts'"
                     " ORDER BY name ASC",
                     quoted_name);
      g_free (quoted_name);
    }
  else
    init_iterator (iterator,
                   "SELECT name, value FROM nvt_preferences"
                   " WHERE name != 'cache_folder'"
                   " AND name != 'include_folders'"
                   " AND name != 'nasl_no_signature_check'"
                   " AND name != 'network_targets'"
                   " AND name != 'ntp_save_sessions'"
                   " AND name NOT LIKE 'server_info_%%'"
                   /* Task preferences. */
                   " AND name != 'max_checks'"
                   " AND name != 'max_hosts'"
                   " ORDER BY name ASC");
}

/**
 * @brief Get the name from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_preference_iterator_name, 0);

/**
 * @brief Get the value from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (nvt_preference_iterator_value, 1);

/**
 * @brief Get the real name from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Real name.
 */
char*
nvt_preference_iterator_real_name (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 0);
  if (ret)
    {
      int value_start = -1, value_end = -1, count;
      /* LDAPsearch[entry]:Timeout value */
      count = sscanf (ret, "%*[^[][%*[^]]]:%n%*[ -~]%n", &value_start, &value_end);
      if (count == 0 && value_start > 0 && value_end > 0)
        {
          ret += value_start;
          return g_strdup (ret);
        }
      return g_strdup (ret);
    }
  return NULL;
}

/**
 * @brief Get the type from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type.
 */
char*
nvt_preference_iterator_type (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 0);
  if (ret)
    {
      int type_start = -1, type_end = -1, count;
      count = sscanf (ret, "%*[^[][%n%*[^]]%n]:", &type_start, &type_end);
      if (count == 0 && type_start > 0 && type_end > 0)
        {
          ret += type_start;
          return g_strndup (ret, type_end - type_start);
        }
      return NULL;
    }
  return NULL;
}

/**
 * @brief Get the NVT from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT.
 */
char*
nvt_preference_iterator_nvt (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, 0);
  if (ret)
    {
      int type_start = -1, count;
      count = sscanf (ret, "%*[^[]%n[%*[^]]]:", &type_start);
      if (count == 0 && type_start > 0)
        {
          return g_strndup (ret, type_start);
        }
      return NULL;
    }
  return NULL;
}

/**
 * @brief Get the config value from an NVT preference iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  config    Config.
 *
 * @return Freshly allocated config value.
 */
char*
nvt_preference_iterator_config_value (iterator_t* iterator, config_t config)
{
  gchar *quoted_name, *value;
  const char *ret;
  if (iterator->done) return NULL;

  quoted_name = sql_quote (iterator_string (iterator, 0));
  value = sql_string ("SELECT value FROM config_preferences"
                      " WHERE config = %llu"
                      " AND name = '%s'"
                      /* Ensure that the NVT pref comes first, in case an
                       * error in the GSA added the NVT pref as a Scanner
                       * pref. */
                      " ORDER BY type",
                      config,
                      quoted_name);
  g_free (quoted_name);
  if (value) return value;

  ret = iterator_string (iterator, 1);
  if (ret) return g_strdup (ret);
  return NULL;
}

/**
 * @brief Get the number preferences available for an NVT.
 *
 * @param[in]  name  Name of NVT.
 *
 * @return Number of possible preferences on NVT.
 */
int
nvt_preference_count (const char *name)
{
  gchar *quoted_name = sql_quote (name);
  int ret = sql_int ("SELECT COUNT(*) FROM nvt_preferences"
                     " WHERE name LIKE '%s[%%';",
                     quoted_name);
  g_free (quoted_name);
  return ret;
}

/**
 * @brief Get the value of a task preference.
 *
 * @param[in]  task  Task.
 * @param[in]  name  Preference name.
 *
 * @return Freshly allocated task preference value or NULL if pref missing.
 */
char*
task_preference_value (task_t task, const char *name)
{
  gchar *quoted_name, *value;

  quoted_name = sql_quote (name);
  value = sql_string ("SELECT value FROM task_preferences"
                      " WHERE task = %llu"
                      " AND name = '%s';",
                      task,
                      quoted_name);
  if (value)
    {
      g_free (quoted_name);
      return value;
    }

  value = sql_string ("SELECT value FROM nvt_preferences"
                      " WHERE name = '%s';",
                      quoted_name);
  if (value)
    {
      g_free (quoted_name);
      return value;
    }

  return NULL;
}

/**
 * @brief Set the preferences of a task.
 *
 * Only the given preferences are affected.  A NULL value means to remove
 * the preference (reverts to using scanner value).
 *
 * @param[in]  task         Task.
 * @param[in]  preferences  Preferences.
 */
void
set_task_preferences (task_t task, array_t *preferences)
{
  if (preferences)
    {
      guint index;
      for (index = 0; index < preferences->len; index++)
        {
          name_value_t *pair;
          pair = (name_value_t*) g_ptr_array_index (preferences, index);
          if (pair && pair->name)
            {
              gchar *quoted_name;
              quoted_name = sql_quote (pair->name);
              if (pair->value)
                {
                  gchar *quoted_value;
                  quoted_value = sql_quote (pair->value);
                  sql_begin_immediate ();
                  if (sql_int ("SELECT COUNT(*) FROM task_preferences"
                               " WHERE task = %llu AND name = '%s';",
                               task,
                               quoted_name))
                    sql ("UPDATE task_preferences"
                         " SET value = '%s'"
                         " WHERE task = %llu AND name = '%s';",
                         quoted_value,
                         task,
                         quoted_name);
                  else
                    sql ("INSERT INTO task_preferences"
                         " (task, name, value)"
                         " VALUES"
                         " (%llu, '%s', '%s');",
                         task,
                         quoted_name,
                         quoted_value);
                  sql ("COMMIT;");
                  g_free (quoted_value);
                }
              else
                sql ("DELETE FROM task_preferences WHERE name = '%s';",
                     quoted_name);
              g_free (quoted_name);
              sql ("UPDATE tasks SET modification_time = m_now ()"
                   " WHERE id = %llu;",
                   task);
            }
        }
    }
}


/* LSC Credentials. */

/**
 * @brief Find an LSC credential given a UUID.
 *
 * @param[in]   uuid            UUID of LSC credential.
 * @param[out]  lsc_credential  LSC credential return, 0 if succesfully failed
 *                              to find credential.
 *
 * @return FALSE on success (including if failed to find LSC credential),
 *         TRUE on error.
 */
gboolean
find_lsc_credential (const char* uuid, lsc_credential_t* lsc_credential)
{
  gchar *quoted_uuid = sql_quote (uuid);
  if (user_owns_uuid ("lsc_credential", quoted_uuid, 0) == 0)
    {
      g_free (quoted_uuid);
      *lsc_credential = 0;
      return FALSE;
    }
  switch (sql_int64 (lsc_credential,
                     "SELECT id FROM lsc_credentials WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *lsc_credential = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Find an LSC credential for a specific permission, given a UUID.
 *
 * @param[in]   uuid            UUID of lsc_credential.
 * @param[out]  lsc_credential  LSC credential return, 0 if succesfully failed
 *                              to find LSC credential.
 * @param[in]   permission      Permission.
 *
 * @return FALSE on success (including if failed to find lsc_credential), TRUE
 *         on error.
 */
gboolean
find_lsc_credential_with_permission (const char* uuid,
                                     lsc_credential_t* lsc_credential,
                                     const char *permission)
{
  return find_resource_with_permission ("lsc_credential", uuid, lsc_credential,
                                        permission, 0);
}

/**
 * @brief Length of password generated in create_lsc_credential.
 */
#define PASSWORD_LENGTH 10

/**
 * @brief Create an LSC credential.
 *
 * @param[in]  name            Name of LSC credential.  Must be at least one
 *                             character long.
 * @param[in]  comment         Comment on LSC credential.
 * @param[in]  login           Name of LSC credential user.  Must be at least
 *                             one character long.
 * @param[in]  given_password  Password for password-only credential, NULL to
 *                             generate credentials.
 * @param[in]  key_private     Private key, or NULL.
 * @param[out] lsc_credential  Created LSC credential.
 *
 * @return 0 success, 1 LSC credential exists already, 2 name contains space,
 *         3 Failed to create public key from private key/password,
 *         99 permission denied, -1 error.
 */
int
create_lsc_credential (const char* name, const char* comment, const char* login,
                       const char* given_password, const char* key_private,
                       lsc_credential_t *lsc_credential)
{
  gchar *quoted_name, *private_key;
  int i;
  GRand *rand;
  gchar password[PASSWORD_LENGTH];
  const char *s = login;

  assert (name && strlen (name) > 0);
  assert (login && strlen (login) > 0);
  assert (current_credentials.uuid);
  assert (comment);

  sql_begin_immediate ();

  if (user_may ("create_lsc_credential") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (resource_with_name_exists (name, "lsc_credential", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_name = sql_quote (name);
  if (key_private)
    {
      lsc_crypt_ctx_t crypt_ctx;
      gchar *quoted_login, *quoted_phrase, *quoted_comment, *quoted_private;

      if (!openvas_ssh_public_from_private (key_private, given_password))
        return 3;

      /* Key pair credential. */
      if (!strcmp (key_private, ";;encrypted;;"))
        {
          g_free (quoted_name);
          sql ("ROLLBACK;");
          return -1;
        }

      /* Encrypt password and private key.  Note that we do not need
         to call sql_quote because the result of the encryption is
         base64 encoded and does not contain apostrophes.  */
      if (!disable_encrypted_credentials)
        {
          crypt_ctx = lsc_crypt_new ();
          quoted_phrase = lsc_crypt_encrypt (crypt_ctx,
                                             "password", given_password,
                                             "private_key", key_private, NULL);
          if (!quoted_phrase)
            {
              g_free (quoted_name);
              lsc_crypt_release (crypt_ctx);
              sql ("ROLLBACK;");
              return -1;
            }
          quoted_private = g_strdup (";;encrypted;;");
        }
      else
        {
          crypt_ctx = NULL;
          quoted_phrase = given_password ? sql_quote (given_password)
                                         : g_strdup ("");
          quoted_private = sql_quote (key_private);
        }
      quoted_login = sql_quote (login);
      quoted_comment = sql_quote (comment);

      sql ("INSERT INTO lsc_credentials"
           " (uuid, name, owner, login, password, comment,"
           "  private_key, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (), '%s',"
           "  (SELECT id FROM users WHERE users.uuid = '%s'),"
           "  '%s', '%s', '%s', '%s', m_now (), m_now ());",
           quoted_name,
           current_credentials.uuid,
           quoted_login,
           quoted_phrase,
           quoted_comment,
           quoted_private);

      g_free (quoted_name);
      g_free (quoted_login);
      g_free (quoted_phrase);
      g_free (quoted_comment);
      g_free (quoted_private);
      lsc_crypt_release (crypt_ctx);

      if (lsc_credential)
        *lsc_credential = sql_last_insert_id ();

      sql ("COMMIT;");
      return 0;
    }

  if (given_password)
    {
      lsc_crypt_ctx_t crypt_ctx;
      gchar *quoted_login, *quoted_password, *quoted_comment, *quoted_private;

      /* Password-only credential. */

      if (!disable_encrypted_credentials)
        {
          crypt_ctx = lsc_crypt_new ();
          quoted_password = lsc_crypt_encrypt (crypt_ctx,
                                               "password", given_password,
                                               NULL);
          if (!quoted_password)
            {
              g_free (quoted_name);
              lsc_crypt_release (crypt_ctx);
              sql ("ROLLBACK;");
              return -1;
            }
          quoted_private = g_strdup ("';;encrypted;;'");
        }
      else
        {
          crypt_ctx = NULL;
          quoted_password = sql_quote (given_password);
          quoted_private = g_strdup ("NULL");
        }
      quoted_login = sql_quote (login);
      quoted_comment = sql_quote (comment);

      sql ("INSERT INTO lsc_credentials"
           " (uuid, name, owner, login, password, comment, "
           "  private_key, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (), '%s',"
           "  (SELECT id FROM users WHERE users.uuid = '%s'),"
           "  '%s', '%s', '%s', %s, m_now (), m_now ());",
           quoted_name,
           current_credentials.uuid,
           quoted_login,
           quoted_password,
           quoted_comment,
           quoted_private);

      g_free (quoted_name);
      g_free (quoted_login);
      g_free (quoted_password);
      g_free (quoted_comment);
      g_free (quoted_private);
      lsc_crypt_release (crypt_ctx);

      if (lsc_credential)
        *lsc_credential = sql_last_insert_id ();

      sql ("COMMIT;");
      return 0;
    }

  /* Ensure the login is alphanumeric, to help the package generation. */

  while (*s)
    if (isalnum (*s))
      s++;
    else
      {
        g_free (quoted_name);
        sql ("ROLLBACK;");
        return 2;
      }

  /* Create the keys and packages. */

  rand = g_rand_new ();
  for (i = 0; i < PASSWORD_LENGTH - 1; i++)
    {
      password[i] = (gchar) g_rand_int_range (rand, '0', 'z');
      if (password[i] == '\\')
        password[i] = '{';
    }
  password[PASSWORD_LENGTH - 1] = '\0';
  g_rand_free (rand);

  if (lsc_user_keys_create (password, &private_key))
    {
      g_free (quoted_name);
      sql ("ROLLBACK;");
      return -1;
    }

  {
    lsc_crypt_ctx_t crypt_ctx;
    gchar *quoted_login, *quoted_password, *quoted_comment;
    gchar *quoted_private_key;

    /* Generated key credential. */

    if (!disable_encrypted_credentials)
      {
        crypt_ctx = lsc_crypt_new ();
        quoted_password = lsc_crypt_encrypt (crypt_ctx,
                                             "password", password,
                                             "private_key", private_key, NULL);
        if (!quoted_password)
          {
            lsc_crypt_release (crypt_ctx);
            g_free (private_key);
            g_free (quoted_name);
            sql ("ROLLBACK;");
            return -1;
          }
        quoted_private_key = g_strdup (";;encrypted;;");
      }
    else
      {
        crypt_ctx = NULL;
        quoted_password = sql_quote (password);
        quoted_private_key = sql_quote (private_key);
      }
    quoted_login = sql_quote (login);
    quoted_comment = sql_quote (comment);

    sql_quiet ("INSERT INTO lsc_credentials"
               " (uuid, name, owner, login, password, comment,"
               "  private_key, creation_time, modification_time)"
               " VALUES"
               " (make_uuid (), '%s',"
               "  (SELECT id FROM users WHERE users.uuid = '%s'),"
               "  '%s', '%s', '%s', '%s', m_now (), m_now ());",
               quoted_name,
               current_credentials.uuid,
               quoted_login,
               quoted_password,
               quoted_comment,
               quoted_private_key);

    g_free (quoted_name);
    g_free (quoted_login);
    g_free (quoted_password);
    g_free (quoted_comment);
    g_free (quoted_private_key);
    lsc_crypt_release (crypt_ctx);
  }

  g_free (private_key);

  if (lsc_credential)
    *lsc_credential = sql_last_insert_id ();

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create an LSC Credential from an existing one.
 *
 * @param[in]  name                 Name of new LSC Credential. NULL to copy
 *                                  from existing.
 * @param[in]  comment              Comment on new LSC Credential. NULL to copy
 *                                  from existing.
 * @param[in]  lsc_credential_id    UUID of existing LSC Credential.
 * @param[out] new_lsc_credential   New LSC Credential.
 *
 * @return 0 success, 1 LSC Credential exists already, 2 failed to find
 *         existing LSC Credential, -1 error.
 */
int
copy_lsc_credential (const char* name, const char* comment,
                     const char *lsc_credential_id,
                     lsc_credential_t* new_lsc_credential)
{
  return copy_resource ("lsc_credential", name, comment, lsc_credential_id,
                        "login, password, private_key", 1, new_lsc_credential);
}

/**
 * @brief Return whether an LSC credential is the packaged type.
 *
 * @param[in]  cred_id  The LSC Credential uuid.
 *
 * @return 0 false, 1 true.
 */
static int
lsc_credential_packaged (const char *cred_id)
{
  get_data_t get;
  iterator_t iter;
  const char *key;

  memset (&get, '\0', sizeof (get));
  get.id = g_strdup (cred_id);
  if (init_lsc_credential_iterator (&iter, &get) || !next (&iter))
    return 0;

  key = lsc_credential_iterator_private_key (&iter);
  cleanup_iterator (&iter);
  return key ? 1 : 0;
}

/**
 * @brief Modify a LSC Credential.
 *
 * @param[in]   lsc_credential_id   UUID of lsc credential.
 * @param[in]   name                Name of lsc credential.
 * @param[in]   comment             Comment on lsc credential.
 * @param[in]   login               Login of lsc credential.
 * @param[in]   password            Password of lsc credential.
 *
 * @return 0 success, 1 failed to find credential, 2 credential with new name
 *         exists, 3 lsc_credential_id required, 4 attempt to modify login/pass
 *         of packaged lsc credential, 99 permission denied, -1 internal error.
 */
int
modify_lsc_credential (const char *lsc_credential_id,
                       const char *name, const char *comment,
                       const char *login, const char *password)
{
  lsc_credential_t lsc_credential;

  if (lsc_credential_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_lsc_credential") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  lsc_credential = 0;
  if (find_lsc_credential_with_permission (lsc_credential_id, &lsc_credential,
                                           "modify_lsc_credential"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (lsc_credential == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check attempt to change login or password of packaged LSC credential */
  if ((login || password) && lsc_credential_packaged (lsc_credential_id))
    return 4;

  /* Check whether a lsc_credential with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "lsc_credential", lsc_credential))
        {
          sql ("ROLLBACK;");
          return 2;
        }
    }

  /* Update values */

  if (name)
    set_lsc_credential_name (lsc_credential, name);

  if (comment)
    set_lsc_credential_comment (lsc_credential, comment);

  if (login)
    set_lsc_credential_login (lsc_credential, login);

  if (password)
    set_lsc_credential_password (lsc_credential, password);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete an LSC credential.
 *
 * @param[in]  lsc_credential_id  UUID of LSC credential.
 * @param[in]  ultimate           Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because the LSC credential is in use, 99 permission
 *         denied, -1 error.
 */
int
delete_lsc_credential (const char *lsc_credential_id, int ultimate)
{
  lsc_credential_t lsc_credential = 0;

  sql_begin_immediate ();

  if (user_may ("delete_lsc_credential") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_lsc_credential_with_permission (lsc_credential_id, &lsc_credential,
                                           "delete_lsc_credential"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (lsc_credential == 0)
    {
      if (find_trash ("lsc_credential", lsc_credential_id, &lsc_credential))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (lsc_credential == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a target in the trashcan. */
      if (sql_int ("SELECT count(*) FROM targets_trash"
                   " WHERE (lsc_credential = %llu"
                   "        AND ssh_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " OR (smb_lsc_credential = %llu"
                   "     AND smb_location = " G_STRINGIFY (LOCATION_TRASH) ");",
                   lsc_credential,
                   lsc_credential))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("lsc_credential", lsc_credential,
                               LOCATION_TRASH);
      tags_set_orphans ("lsc_credential", lsc_credential,
                        LOCATION_TRASH);

      sql ("DELETE FROM lsc_credentials_trash WHERE id = %llu;", lsc_credential);
      sql ("COMMIT;");
      return 0;
    }


  if (sql_int ("SELECT count(*) FROM targets"
               " WHERE lsc_credential = %llu"
               " OR smb_lsc_credential = %llu"
               " OR esxi_lsc_credential = %llu",
               lsc_credential,
               lsc_credential,
               lsc_credential))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  if (ultimate == 0)
    {
      sql ("INSERT INTO lsc_credentials_trash"
           " (uuid, owner, name, login, password, comment, "
           "  private_key, creation_time, modification_time)"
           " SELECT uuid, owner, name, login, password, comment, "
           "  private_key, creation_time, modification_time"
           " FROM lsc_credentials WHERE id = %llu;",
           lsc_credential);

      /* Update the credential references in any trashcan targets.  This
       * situation is possible if the user restores the credential when the
       * target is in the trashcan. */
      sql ("UPDATE targets_trash"
           " SET ssh_location = " G_STRINGIFY (LOCATION_TRASH) ","
           "     lsc_credential = %llu"
           " WHERE lsc_credential = %llu;",
           sql_last_insert_id (),
           lsc_credential);
      sql ("UPDATE targets_trash"
           " SET smb_location = " G_STRINGIFY (LOCATION_TRASH) ","
           " smb_lsc_credential = %llu"
           " WHERE smb_lsc_credential = %llu;",
           sql_last_insert_id (),
           lsc_credential);
      sql ("UPDATE targets_trash"
           " SET esxi_location = " G_STRINGIFY (LOCATION_TRASH) ","
           " esxi_lsc_credential = %llu"
           " WHERE esxi_lsc_credential = %llu;",
           sql_last_insert_id (),
           lsc_credential);

      permissions_set_locations ("lsc_credential", lsc_credential,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("lsc_credential", lsc_credential,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("lsc_credential", lsc_credential,
                               LOCATION_TABLE);
      tags_set_orphans ("lsc_credential", lsc_credential, LOCATION_TABLE);
    }

  sql ("DELETE FROM lsc_credentials WHERE id = %llu;", lsc_credential);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Filter columns for LSC Credential iterator.
 */
#define LSC_CREDENTIAL_ITERATOR_FILTER_COLUMNS                                 \
 { GET_ITERATOR_FILTER_COLUMNS, "login", NULL }

/**
 * @brief LSC Credential iterator columns.
 */
#define LSC_CREDENTIAL_ITERATOR_COLUMNS                                       \
 {                                                                            \
   GET_ITERATOR_COLUMNS (lsc_credentials),                                    \
   { "login", NULL },                                                         \
   { "password", NULL },                                                      \
   { "private_key", NULL },                                                   \
   { NULL, NULL }                                                             \
 }

/**
 * @brief LSC Credential iterator columns for trash case.
 */
#define LSC_CREDENTIAL_ITERATOR_TRASH_COLUMNS                                 \
 {                                                                            \
   GET_ITERATOR_COLUMNS (lsc_credentials_trash),                              \
   { "login", NULL },                                                         \
   { "password", NULL },                                                      \
   { "private_key", NULL },                                                   \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Count number of LSC Credentials.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of LSC Credentials in filtered set.
 */
int
lsc_credential_count (const get_data_t *get)
{
  static const char *filter_columns[] = LSC_CREDENTIAL_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = LSC_CREDENTIAL_ITERATOR_COLUMNS;
  static column_t trash_columns[] = LSC_CREDENTIAL_ITERATOR_TRASH_COLUMNS;
  return count ("lsc_credential", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Check whether a LSC Credential is in use.
 *
 * @param[in]  lsc_credential  LSC Credential.
 *
 * @return 1 yes, 0 no.
 */
int
lsc_credential_in_use (lsc_credential_t lsc_credential)
{
  return !!sql_int ("SELECT count (*) FROM targets"
                    " WHERE lsc_credential = %llu"
                    " OR smb_lsc_credential = %llu"
                    " OR esxi_lsc_credential = %llu;",
                    lsc_credential,
                    lsc_credential,
                    lsc_credential);
}

/**
 * @brief Check whether a trashcan LSC Credential is in use.
 *
 * @param[in]  lsc_credential  LSC Credential.
 *
 * @return 1 yes, 0 no.
 */
int
trash_lsc_credential_in_use (lsc_credential_t lsc_credential)
{
  return !!sql_int ("SELECT count (*) FROM targets_trash"
                    " WHERE (lsc_credential = %llu"
                    " AND ssh_location = " G_STRINGIFY (LOCATION_TRASH)")"
                    " OR (smb_lsc_credential = %llu"
                    " AND smb_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                    " OR (esxi_lsc_credential = %llu"
                    " AND esxi_location = " G_STRINGIFY (LOCATION_TRASH) ");",
                    lsc_credential,
                    lsc_credential,
                    lsc_credential);
}

/**
 * @brief Check whether a LSC Credential is writable.
 *
 * @param[in]  lsc_credential  LSC Credential.
 *
 * @return 1 yes, 0 no.
 */
int
lsc_credential_writable (lsc_credential_t lsc_credential)
{
  return 1;
}

/**
 * @brief Check whether a trashcan LSC Credential is writable.
 *
 * @param[in]  lsc_credential  LSC Credential.
 *
 * @return 1 yes, 0 no.
 */
int
trash_lsc_credential_writable (lsc_credential_t lsc_credential)
{
  return 1;
}

/**
 * @brief Set the name of an LSC credential.
 *
 * @param[in]  lsc_credential  The LSC credential.
 * @param[in]  name            Name.
 */
void
set_lsc_credential_name (lsc_credential_t lsc_credential, const char *name)
{
  gchar *quoted_name = sql_quote (name);
  sql ("UPDATE lsc_credentials SET name = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       lsc_credential);
  g_free (quoted_name);
}

/**
 * @brief Set the comment of an LSC credential.
 *
 * @param[in]  lsc_credential  The LSC credential.
 * @param[in]  comment         Comment.
 */
void
set_lsc_credential_comment (lsc_credential_t lsc_credential,
                            const char *comment)
{
  gchar *quoted_comment = sql_quote (comment);
  sql ("UPDATE lsc_credentials SET comment = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_comment,
       lsc_credential);
  g_free (quoted_comment);
}

/**
 * @brief Set the login of an LSC credential.
 *
 * @param[in]  lsc_credential  The LSC credential.
 * @param[in]  login           Login.
 */
void
set_lsc_credential_login (lsc_credential_t lsc_credential, const char *login)
{
  gchar *quoted_login = sql_quote (login);
  sql ("UPDATE lsc_credentials SET login = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_login,
       lsc_credential);
  g_free (quoted_login);
}

/**
 * @brief Set the password of an LSC credential.
 *
 * @param[in]  lsc_credential  The LSC credential.
 * @param[in]  password        Password.
 */
void
set_lsc_credential_password (lsc_credential_t lsc_credential,
                             const char *password)
{
  lsc_crypt_ctx_t crypt_ctx;
  gchar *quoted_password, *quoted_private;

  if (!disable_encrypted_credentials)
    {
      crypt_ctx = lsc_crypt_new ();
      quoted_password = lsc_crypt_encrypt (crypt_ctx,
                                           "password", password, NULL);
      if (!quoted_password)
        {
          g_critical ("%s: encryption failed", G_STRFUNC);
          lsc_crypt_release (crypt_ctx);
          return;
        }
      quoted_private = g_strdup ("';;encrypted;;'");
    }
  else
    {
      crypt_ctx = NULL;
      quoted_password = sql_quote (password);
      quoted_private = g_strdup ("NULL");
    }

  sql ("UPDATE lsc_credentials SET password = '%s', private_key = %s,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_password, quoted_private,
       lsc_credential);
  g_free (quoted_password);
  g_free (quoted_private);
  lsc_crypt_release (crypt_ctx);
}

/**
 * @brief Initialise an LSC Credential iterator, given a single LSC credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  lsc_credential  Single LSC credential to iterate.
 */
void
init_lsc_credential_iterator_one (iterator_t* iterator,
                                  lsc_credential_t lsc_credential)
{
  get_data_t get;

  assert (lsc_credential);

  memset (&get, '\0', sizeof (get));
  get.id = lsc_credential_uuid (lsc_credential);
  get.filter = "owner=any permission=get_lsc_credentials";

  init_lsc_credential_iterator (iterator, &get);
}

/**
 * @brief Initialise a LSC Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find filter, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_lsc_credential_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = LSC_CREDENTIAL_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = LSC_CREDENTIAL_ITERATOR_COLUMNS;
  static column_t trash_columns[] = LSC_CREDENTIAL_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "lsc_credential",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/*
 * Common code for lsc_credential_iterator_password and
 * lsc_credential_iterator_private_key.
 */
static const char*
lsc_credential_iterator_pass_or_priv (iterator_t* iterator, int want_privkey)
{
  const char *password, *privkey, *result;

  if (iterator->done)
    return NULL;
  password = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  privkey  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
  /* If we do not have a private key, there is no encrypted data.
     Return the password as is or NULL.  */
  if (!privkey)
    return want_privkey? NULL : password;
  /* If we have a private key but it has not the flag value, we return
     the password field or the private key.  */
  if (strcmp (privkey, ";;encrypted;;"))
    return want_privkey? privkey : password;
  /* This is an encrypted credential.  */
  if (!iterator->crypt_ctx)
    iterator->crypt_ctx = lsc_crypt_new ();
  if (want_privkey)
    result = lsc_crypt_get_private_key (iterator->crypt_ctx, password);
  else
    result = lsc_crypt_get_password (iterator->crypt_ctx, password);

  return result;
}

/**
 * @brief Get the login from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Login, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (lsc_credential_iterator_login, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the password from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Password, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
lsc_credential_iterator_password (iterator_t* iterator)
{
  return lsc_credential_iterator_pass_or_priv (iterator, 0);
}


/**
 * @brief Get the private_key from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Private_key, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
lsc_credential_iterator_private_key (iterator_t* iterator)
{
  return lsc_credential_iterator_pass_or_priv (iterator, 1);
}

/**
 * @brief Get the rpm from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Rpm, or NULL if iteration is complete. Free with g_free().
 */
char*
lsc_credential_iterator_rpm (iterator_t *iterator)
{
  const char *private_key, *login, *pass;
  void *rpm;
  char *public_key;
  gsize rpm_size;
  gchar *rpm64;

  if (iterator->done) return NULL;

  private_key = lsc_credential_iterator_private_key (iterator);
  pass = lsc_credential_iterator_password (iterator);
  public_key = openvas_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = lsc_credential_iterator_login (iterator);
  if (lsc_user_rpm_recreate (login, public_key, &rpm, &rpm_size))
    {
      g_free (public_key);
      return NULL;
    }
  g_free (public_key);
  rpm64 = (rpm && rpm_size)
          ? g_base64_encode (rpm, rpm_size)
          : g_strdup ("");
  free (rpm);
  return rpm64;
}

/**
 * @brief Get the deb from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Deb, or NULL if iteration is complete. Free with g_free().
 */
char*
lsc_credential_iterator_deb (iterator_t *iterator)
{
  const char *login, *private_key, *pass;
  char *public_key;
  void *deb, *rpm;
  gsize deb_size, rpm_size;
  gchar *deb64;

  if (iterator->done) return NULL;

  private_key = lsc_credential_iterator_private_key (iterator);
  pass = lsc_credential_iterator_password (iterator);
  public_key = openvas_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = lsc_credential_iterator_login (iterator);
  if (lsc_user_rpm_recreate (login, public_key, &rpm, &rpm_size))
    {
      g_free (public_key);
      return NULL;
    }
  g_free (public_key);

  if (lsc_user_deb_recreate (login, rpm, rpm_size, &deb, &deb_size))
    {
      free (rpm);
      return NULL;
    }
  free (rpm);
  deb64 = (deb && deb_size)
          ? g_base64_encode (deb, deb_size)
          : g_strdup ("");
  free (deb);
  return deb64;
}

/**
 * @brief Get the exe from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Exe, or NULL if iteration is complete. Free with g_free().
 */
char*
lsc_credential_iterator_exe (iterator_t *iterator)
{
  const char *login, *password;
  void *exe;
  gsize exe_size;
  gchar *exe64;

  if (iterator->done) return NULL;

  login = lsc_credential_iterator_login (iterator);
  password = lsc_credential_iterator_password (iterator);
  if (lsc_user_exe_recreate (login, password, &exe, &exe_size))
    return NULL;
  exe64 = (exe && exe_size)
          ? g_base64_encode (exe, exe_size)
          : g_strdup ("");
  free (exe);
  return exe64;
}

/**
 * @brief Get the UUID of an LSC credential.
 *
 * @param[in]  lsc_credential  LSC credential.
 *
 * @return UUID.
 */
char*
lsc_credential_uuid (lsc_credential_t lsc_credential)
{
  return sql_string ("SELECT uuid FROM lsc_credentials WHERE id = %llu;",
                     lsc_credential);
}

/**
 * @brief Get the UUID of an LSC credential in the trashcan.
 *
 * @param[in]  lsc_credential  LSC credential.
 *
 * @return UUID.
 */
char*
trash_lsc_credential_uuid (lsc_credential_t lsc_credential)
{
  return sql_string ("SELECT uuid FROM lsc_credentials_trash"
                     " WHERE id = %llu;",
                     lsc_credential);
}

/**
 * @brief Get the name of an LSC credential.
 *
 * @param[in]  lsc_credential  LSC credential.
 *
 * @return Name.
 */
char*
lsc_credential_name (lsc_credential_t lsc_credential)
{
  return sql_string ("SELECT name FROM lsc_credentials WHERE id = %llu;",
                     lsc_credential);
}

/**
 * @brief Get the name of an LSC credential in the trashcan.
 *
 * @param[in]  lsc_credential  LSC credential.
 *
 * @return Name.
 */
char*
trash_lsc_credential_name (lsc_credential_t lsc_credential)
{
  return sql_string ("SELECT name FROM lsc_credentials_trash"
                     " WHERE id = %llu;",
                     lsc_credential);
}

/**
 * @brief Return whether a trashcan lsc_credential is readable.
 *
 * @param[in]  lsc_credential  LSC credential.
 *
 * @return 1 if readable, else 0.
 */
int
trash_lsc_credential_readable (lsc_credential_t lsc_credential)
{
  char *uuid;
  lsc_credential_t found;

  if (lsc_credential == 0)
    return 0;
  uuid = lsc_credential_uuid (lsc_credential);
  if (find_trash ("lsc_credential", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Initialise an LSC credential target iterator.
 *
 * Iterates over all targets that use the credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  lsc_credential  Name of LSC credential.
 * @param[in]  ascending       Whether to sort ascending or descending.
 */
void
init_lsc_credential_target_iterator (iterator_t* iterator,
                                     lsc_credential_t lsc_credential,
                                     int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (lsc_credential);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_targets"));
  available = where_owned ("target", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT uuid, name, %s FROM targets"
                 " WHERE lsc_credential = %llu"
                 " OR smb_lsc_credential = %llu"
                 " OR esxi_lsc_credential = %llu"
                 " ORDER BY name %s;",
                 available,
                 lsc_credential,
                 lsc_credential,
                 lsc_credential,
                 ascending ? "ASC" : "DESC");

  g_free (available);
}

/**
 * @brief Get the uuid from an LSC credential_target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Uuid, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (lsc_credential_target_iterator_uuid, 0);

/**
 * @brief Get the name from an LSC credential_target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (lsc_credential_target_iterator_name, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
lsc_credential_target_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}


/* Agents. */

/**
 * @brief Find a agent for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of agent.
 * @param[out]  agent       Agent return, 0 if succesfully failed to find agent.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find agent), TRUE on error.
 */
gboolean
find_agent_with_permission (const char* uuid, agent_t* agent,
                            const char *permission)
{
  return find_resource_with_permission ("agent", uuid, agent, permission, 0);
}

/**
 * @brief Find a signature in a feed.
 *
 * @param[in]   location            Feed directory to search for signature.
 * @param[in]   installer_filename  Installer filename.
 * @param[out]  signature           Freshly allocated installer signature.
 * @param[out]  signature_size      Size of installer signature.
 * @param[out]  uuid                Address for basename of linked signature
 *                                  when the signature was found in the private
 *                                  directory, if desired, else NULL.
 *
 * @return 0 success, -1 error.
 */
static int
find_signature (const gchar *location, const gchar *installer_filename,
                gchar **signature, gsize *signature_size, gchar **uuid)
{
  gchar *installer_basename = g_path_get_basename (installer_filename);

  if (uuid)
    *uuid = NULL;

  if (strlen (installer_basename))
    {
      gchar *signature_filename, *signature_basename;
      GError *error = NULL;

      signature_basename  = g_strdup_printf ("%s.asc", installer_basename);
      g_free (installer_basename);
      signature_filename = g_build_filename (OPENVAS_NVT_DIR,
                                             location,
                                             signature_basename,
                                             NULL);
      tracef ("signature_filename: %s\n", signature_filename);

      g_file_get_contents (signature_filename, signature, signature_size,
                           &error);
      g_free (signature_filename);
      if (error)
        {
          if (uuid && (error->code == G_FILE_ERROR_NOENT))
            {
              char *real;
              gchar **split;

              g_error_free (error);
              error = NULL;
              signature_filename = g_build_filename (OPENVAS_NVT_DIR,
                                                     "private",
                                                     location,
                                                     signature_basename,
                                                     NULL);
              tracef ("signature_filename (private): %s\n", signature_filename);
              g_free (signature_basename);
              g_file_get_contents (signature_filename, signature, signature_size,
                                   &error);
              if (error)
                {
                  g_free (signature_filename);
                  g_error_free (error);
                  return -1;
                }

              real = realpath (signature_filename, NULL);
              g_free (signature_filename);
              tracef ("real pathname: %s\n", real);
              if (real == NULL)
                return -1;
              split = g_strsplit (basename (real), ".", 2);
              if (*split)
                *uuid = g_strdup (*split);
              else
                *uuid = g_strdup (basename (real));
              tracef ("*uuid: %s\n", *uuid);
              g_strfreev (split);
              free (real);
              return 0;
            }
          g_free (signature_basename);
          g_error_free (error);
          return -1;
        }
      g_free (signature_basename);
      return 0;
    }

  g_free (installer_basename);
  return -1;
}


/**
 * @brief Return the name of the sysconf GnuPG home directory
 *
 * Returns the name of the GnuPG home directory to use when checking
 * signatures.  It is the directory openvas/gnupg under the sysconfdir
 * that was set by configure (usually $prefix/etc).
 *
 * @return Static name of the Sysconf GnuPG home directory.
 */
static const char *
get_sysconf_gpghome ()
{
  static char *name;

  if (!name)
    name = g_build_filename (OPENVAS_SYSCONF_DIR, "gnupg", NULL);

  return name;
}


/**
 * @brief Return the name of the trusted keys file name.
 *
 * We currently use the name pubring.gpg to be compatible with
 * previous installations.  That file should best be installed
 * read-only so that it is not accidentally accessed while we are
 * running a verification.  All files in that keyring are assumed to
 * be fully trustworthy.
 *
 * @return Static file name.
 */
static const char *
get_trustedkeys_name ()
{
  static char *name;

  if (!name)
    name = g_build_filename (get_sysconf_gpghome (), "pubring.gpg", NULL);

  return name;
}



/**
 * @brief Execute gpg to verify an installer signature.
 *
 * @param[in]  installer       Installer.
 * @param[in]  installer_size  Size of installer.
 * @param[in]  signature       Installer signature.
 * @param[in]  signature_size  Size of installer signature.
 * @param[out] trust           Trust value.
 *
 * @return 0 success, -1 error.
 */
static int
verify_signature (const gchar *installer, gsize installer_size,
                  const gchar *signature, gsize signature_size,
                  int *trust)
{
  gchar **cmd;
  gint exit_status;
  int ret = 0, installer_fd, signature_fd;
  gchar *standard_out = NULL;
  gchar *standard_err = NULL;
  char installer_file[] = "/tmp/openvasmd-installer-XXXXXX";
  char signature_file[] = "/tmp/openvasmd-signature-XXXXXX";
  GError *error = NULL;

  installer_fd = mkstemp (installer_file);
  if (installer_fd == -1)
    return -1;

  g_file_set_contents (installer_file, installer, installer_size, &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      close (installer_fd);
      return -1;
    }

  signature_fd = mkstemp (signature_file);
  if (signature_fd == -1)
    {
      close (installer_fd);
      return -1;
    }

  g_file_set_contents (signature_file, signature, signature_size, &error);
  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      close (installer_fd);
      close (signature_fd);
      return -1;
    }

  cmd = (gchar **) g_malloc (10 * sizeof (gchar *));

  cmd[0] = g_strdup ("gpgv");
  cmd[1] = g_strdup ("--homedir");
  cmd[2] = g_strdup (get_sysconf_gpghome ());
  cmd[3] = g_strdup ("--quiet");
  cmd[4] = g_strdup ("--keyring");
  cmd[5] = g_strdup (get_trustedkeys_name ());
  cmd[6] = g_strdup ("--");
  cmd[7] = g_strdup (signature_file);
  cmd[8] = g_strdup (installer_file);
  cmd[9] = NULL;
  g_debug ("%s: Spawning in /tmp/: %s %s %s %s %s %s %s %s %s\n",
           __FUNCTION__,
           cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5],
           cmd[6], cmd[7], cmd[8]);
  if ((g_spawn_sync ("/tmp/",
                     cmd,
                     NULL,                 /* Environment. */
                     G_SPAWN_SEARCH_PATH,
                     NULL,                 /* Setup func. */
                     NULL,
                     &standard_out,
                     &standard_err,
                     &exit_status,
                     NULL) == FALSE)
      || (WIFEXITED (exit_status) == 0)
      || WEXITSTATUS (exit_status))
    {
      if (WEXITSTATUS (exit_status) == 1)
        *trust = TRUST_NO;
      else
        {
#if 0
          g_debug ("%s: failed to run gpgv(%s): %d (WIF %i, WEX %i)",
                   __FUNCTION__, get_trustedkeys_name (),
                   exit_status,
                   WIFEXITED (exit_status),
                   WEXITSTATUS (exit_status));
          g_debug ("%s: stdout: %s\n", __FUNCTION__, standard_out);
          g_debug ("%s: stderr: %s\n", __FUNCTION__, standard_err);
          ret = -1;
#endif
          /* This can be caused by the contents of the signature file, so
           * always return success. */
          *trust = TRUST_UNKNOWN;
        }
    }
  else
    *trust = TRUST_YES;

  g_free (cmd[0]);
  g_free (cmd[1]);
  g_free (cmd[2]);
  g_free (cmd[3]);
  g_free (cmd[4]);
  g_free (cmd[5]);
  g_free (cmd[6]);
  g_free (cmd[7]);
  g_free (cmd[8]);
  g_free (cmd);
  g_free (standard_out);
  g_free (standard_err);
  close (installer_fd);
  close (signature_fd);
  g_remove (installer_file);
  g_remove (signature_file);

  return ret;
}

/**
 * @brief Create an agent entry.
 *
 * @param[in]  name           Name of agent.  Must be at least one character long.
 * @param[in]  comment        Comment on agent.
 * @param[in]  installer_64   Installer, in base64.
 * @param[in]  installer_filename   Installer filename.
 * @param[in]  installer_signature_64   Installer signature, in base64.
 * @param[in]  howto_install  Install HOWTO, in base64.
 * @param[in]  howto_use      Usage HOWTO, in base64.
 * @param[out] agent          Created agent.
 *
 * @return 0 success, 1 agent exists already, 99 permission denied, -1 error.
 */
int
create_agent (const char* name, const char* comment, const char* installer_64,
              const char* installer_filename, const char* installer_signature_64,
              const char* howto_install, const char* howto_use, agent_t *agent)
{
  gchar *installer, *installer_signature;
  int installer_trust;
  gsize installer_size, installer_signature_size;

  assert (strlen (name) > 0);
  assert (installer_64);
  assert (installer_filename);
  assert (installer_signature_64);
  assert (current_credentials.uuid);

  installer_trust = TRUST_UNKNOWN;
  installer_size = 0;
  installer_signature_size = 0;

  /* Translate the installer and signature. */

  if (strlen (installer_64))
    installer = (gchar*) g_base64_decode (installer_64, &installer_size);
  else
    installer = g_strdup ("");

  if (strlen (installer_signature_64))
    installer_signature = (gchar*) g_base64_decode (installer_signature_64,
                                                    &installer_signature_size);
  else
    installer_signature = g_strdup ("");

  /* Verify the installer signature. */

  if (strlen (installer_signature))
    {
      if (verify_signature (installer, installer_size, installer_signature,
                            installer_signature_size, &installer_trust))
        {
          g_free (installer);
          g_free (installer_signature);
          return -1;
        }
    }
  else
    {
      g_free (installer_signature);

      if (find_signature ("agents", installer_filename, &installer_signature,
                          &installer_signature_size, NULL)
          == 0)
        {
          if (verify_signature (installer, installer_size, installer_signature,
                                installer_signature_size, &installer_trust))
            {
              g_free (installer);
              g_free (installer_signature);
              return -1;
            }
        }
    }

  /* Check that the name is unique. */

  sql_begin_immediate ();

  if (user_may ("create_agent") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (resource_with_name_exists (name, "agent", 0))
    {
      g_free (installer);
      g_free (installer_signature);
      sql ("ROLLBACK;");
      return 1;
    }

  /* Insert the packages. */

  {
    int ret;
    sql_stmt_t *stmt;
    gchar *quoted_name, *quoted_comment, *quoted_filename;

    while (1)
      {
        quoted_name = sql_quote (name);
        quoted_comment = sql_quote (comment ?: "");
        quoted_filename = sql_quote (installer_filename);

        /* Prepare statement. */

        stmt = sql_prepare ("INSERT INTO agents"
                            " (uuid, name, owner, comment, installer,"
                            "  installer_64, installer_filename,"
                            "  installer_signature_64,"
                            "  installer_trust, installer_trust_time,"
                            "  howto_install, howto_use,"
                            "  creation_time, modification_time)"
                            " VALUES"
                            " (make_uuid (), '%s',"
                            "  (SELECT id FROM users"
                            "   WHERE users.uuid = '%s'),"
                            "  '%s',"
                            "  $1, $2,"           /* installer, installer_64 */
                            "  '%s',"
                            "  $3,"               /* installer_signature_64 */
                            "  %i, %i, $4,"       /* howto_install */
                            "  $5, m_now (), m_now ());", /* howto_use */
                            quoted_name, current_credentials.uuid,
                            quoted_comment, quoted_filename,
                            installer_trust, (int) time (NULL));
        g_free (quoted_name);
        g_free (quoted_comment);
        g_free (quoted_filename);
        if (stmt == NULL)
          {
            g_warning ("%s: sql_prepare failed\n", __FUNCTION__);
            g_free (installer);
            g_free (installer_signature);
            sql ("ROLLBACK;");
            return -1;
          }

        /* Bind the packages to the "$numbers" in the SQL statement. */

        if (sql_bind_text (stmt, 1, installer, installer_size))
          {
            g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            g_free (installer);
            g_free (installer_signature);
            return -1;
          }
        g_free (installer);

        if (sql_bind_text (stmt, 2, installer_64, strlen (installer_64)))
          {
            g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            g_free (installer_signature);
            return -1;
          }
        g_free (installer_signature);

        if (sql_bind_text (stmt, 3, installer_signature_64,
                           strlen (installer_signature_64)))
          {
            g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            return -1;
          }

        if (sql_bind_text (stmt, 4, howto_install, strlen (howto_install)))
          {
            g_warning ("%s: sql_bind_text failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            return -1;
          }

        if (sql_bind_blob (stmt, 5, howto_use, strlen (howto_use)))
          {
            g_warning ("%s: sql_bind_blob failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            return -1;
          }

        /* Run the statement. */

        while ((ret = sql_exec (stmt)) > 0);
        if (ret == -2 || ret == 2)
          {
            /* Gave up with statement reset, or schema error.  Retry. */
            sql_finalize (stmt);
            continue;
          }
        if (ret < 0)
          {
            g_warning ("%s: sql_exec failed\n", __FUNCTION__);
            sql ("ROLLBACK;");
            return -1;
          }
        break;
      }

    sql_finalize (stmt);
  }

  if (agent)
    *agent = sql_last_insert_id ();

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create an agent from an existing agent.
 *
 * @param[in]  name          Name of new agent. NULL to copy from existing.
 * @param[in]  comment       Comment on new agent. NULL to copy from existing.
 * @param[in]  agent_id      UUID of existing schedule.
 * @param[out] new_agent     New agent.
 *
 * @return 0 success, 1 agent exists already, 2 failed to find existing
 *         agent, 99 permission denied, -1 error.
 */
int
copy_agent (const char* name, const char* comment, const char *agent_id,
            agent_t* new_agent)
{
  return copy_resource ("agent", name, comment, agent_id,
                        "installer, installer_64, installer_filename,"
                        " installer_signature_64, installer_trust,"
                        " installer_trust_time, howto_install, howto_use",
                        1, new_agent);
}

/**
 * @brief Modify an agent.
 *
 * @param[in]   agent_id        UUID of agent.
 * @param[in]   name            Name of agent.
 * @param[in]   comment         Comment on agent.
 *
 * @return 0 success, 1 failed to find agent, 2 agent with new name exists,
 *         3 agent_id required, 99 permission denied, -1 internal error.
 */
int
modify_agent (const char *agent_id, const char *name, const char *comment)
{
  gchar *quoted_name, *quoted_comment;
  agent_t agent;

  if (agent_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_agent") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  agent = 0;
  if (find_agent_with_permission (agent_id, &agent, "modify_agent"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (agent == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a agent with the same name exists already. */
  if (resource_with_name_exists (name, "agent", agent))
    {
      sql ("ROLLBACK;");
      return 2;
    }
  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");

  sql ("UPDATE agents SET"
       " name = '%s',"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name, quoted_comment, agent);

  g_free (quoted_comment);
  g_free (quoted_name);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete an agent.
 *
 * @param[in]  agent_id   UUID of agent.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 2 failed to find agent, 99 permission denied, -1 error.
 */
int
delete_agent (const char *agent_id, int ultimate)
{
  agent_t agent = 0;

  sql_begin_immediate ();

  if (user_may ("delete_agent") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_agent_with_permission (agent_id, &agent, "delete_agent"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (agent == 0)
    {
      if (find_trash ("agent", agent_id, &agent))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (agent == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      permissions_set_orphans ("agent", agent, LOCATION_TRASH);
      tags_set_orphans ("agent", agent, LOCATION_TRASH);

      sql ("DELETE FROM agents_trash WHERE id = %llu;", agent);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      sql ("INSERT INTO agents_trash"
           " (uuid, owner, name, comment, installer, installer_64,"
           "  installer_filename, installer_signature_64, installer_trust,"
           "  installer_trust_time, howto_install, howto_use, creation_time,"
           "  modification_time)"
           " SELECT"
           "  uuid, owner, name, comment, installer, installer_64,"
           "  installer_filename, installer_signature_64, installer_trust,"
           "  installer_trust_time, howto_install, howto_use, creation_time,"
           "  modification_time"
           " FROM agents WHERE id = %llu;",
           agent);

      permissions_set_locations ("agent", agent,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("agent", agent,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("agent", agent, LOCATION_TABLE);
      tags_set_orphans ("agent", agent, LOCATION_TABLE);
    }

  sql ("DELETE FROM agents WHERE id = %llu;", agent);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Check whether an agent is in use.
 *
 * @param[in]  agent  Agent.
 *
 * @return 1 yes, 0 no.
 */
int
agent_in_use (agent_t agent)
{
  return 0;
}

/**
 * @brief Check whether a trashcan agent is writable.
 *
 * @param[in]  agent  Agent.
 *
 * @return 1 yes, 0 no.
 */
int
trash_agent_in_use (agent_t agent)
{
  return 0;
}

/**
 * @brief Check whether a agent is writable.
 *
 * @param[in]  agent  Agent.
 *
 * @return 1 yes, 0 no.
 */
int
agent_writable (agent_t agent)
{
  return (agent_in_use (agent) == 0);
}

/**
 * @brief Check whether a trashcan agent is writable.
 *
 * @param[in]  agent  Agent.
 *
 * @return 1 yes, 0 no.
 */
int
trash_agent_writable (agent_t agent)
{
  return (trash_agent_in_use (agent) == 0);
}

/**
 * @brief Verify an agent.
 *
 * @param[in]  agent_id  Agent UUID.
 *
 * @return 0 success, 1 failed to find agent, 99 permission denied, -1 error.
 */
int
verify_agent (const char *agent_id)
{
  agent_t agent;
  int agent_trust = TRUST_UNKNOWN;
  iterator_t agents;
  get_data_t get;

  sql_begin_immediate ();

  if (user_may ("verify_agent") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  agent = 0;
  if (find_agent_with_permission (agent_id, &agent, "verify_agent"))
    return -1;

  if (agent == 0)
    return 1;

  memset (&get, 0, sizeof (get));
  get.filter = g_strdup_printf ("uuid=%s owner=any permission=any", agent_id);
  init_agent_iterator (&agents, &get);
  g_free (get.filter);
  if (next (&agents))
    {
      const char *signature_64;
      gchar *agent_signature = NULL;
      gsize agent_signature_size;

      signature_64 = agent_iterator_installer_signature_64 (&agents);

      find_signature ("agents",
                      agent_iterator_installer_filename (&agents),
                      &agent_signature,
                      &agent_signature_size,
                      NULL);

      if ((signature_64 && strlen (signature_64))
          || agent_signature)
        {
          const char *installer;
          gsize installer_size;

          installer = agent_iterator_installer (&agents);
          installer_size = agent_iterator_installer_size (&agents);

          if (signature_64 && strlen (signature_64))
            {
              gchar *signature;
              gsize signature_length;

              /* Try the signature from the database. */

              signature = (gchar*) g_base64_decode (signature_64,
                                                    &signature_length);

              if (verify_signature (installer, installer_size, signature,
                                    signature_length, &agent_trust))
                {
                  g_warning ("%s: verify_signature error\n", __FUNCTION__);
                  cleanup_iterator (&agents);
                  g_free (agent_signature);
                  sql ("ROLLBACK;");
                  return -1;
                }
            }

          /* If the database signature is empty or the database
           * signature is bad, and there is a feed signature, then
           * try the feed signature. */
          if (((agent_trust == TRUST_NO)
               || (agent_trust == TRUST_UNKNOWN))
              && agent_signature)
            {
              if (verify_signature (installer, installer_size, agent_signature,
                                    strlen (agent_signature), &agent_trust))
                {
                  g_warning ("%s: verify_signature error\n", __FUNCTION__);
                  cleanup_iterator (&agents);
                  g_free (agent_signature);
                  sql ("ROLLBACK;");
                  return -1;
                }

              if (agent_trust == TRUST_YES)
                {
                  gchar *quoted_signature, *base64;
                  base64 = (strlen (agent_signature)
                            ? g_base64_encode ((guchar*) agent_signature,
                                               agent_signature_size)
                            : g_strdup (""));
                  quoted_signature = sql_quote (base64);
                  g_free (base64);
                  sql ("UPDATE agents SET installer_signature_64 = '%s'"
                       " WHERE id = %llu;",
                       quoted_signature,
                       agent);
                  g_free (quoted_signature);
                }
            }
          g_free (agent_signature);
        }
    }
  else
    {
      g_warning ("%s: agent iterator empty\n", __FUNCTION__);
      cleanup_iterator (&agents);
      sql ("ROLLBACK;");
      return -1;
    }
  cleanup_iterator (&agents);

  sql ("UPDATE agents SET installer_trust = %i, installer_trust_time = %i"
       " WHERE id = %llu;",
       agent_trust,
       time (NULL),
       agent);
  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Return the UUID of an agent.
 *
 * @param[in]   agent  Agent.
 *
 * @return UUID of Agent.
 */
char *
agent_uuid (agent_t agent)
{
  return sql_string ("SELECT uuid FROM agents WHERE id = %llu;",
                     agent);
}

/**
 * @brief Filter columns for agent iterator.
 */
#define AGENT_ITERATOR_FILTER_COLUMNS                                 \
 { GET_ITERATOR_FILTER_COLUMNS, "trust", NULL }

/**
 * @brief Agent iterator columns.
 */
#define AGENT_ITERATOR_COLUMNS                                        \
 {                                                                    \
   GET_ITERATOR_COLUMNS (agents),                                     \
   { "installer", NULL },                                             \
   { "installer_64", NULL },                                          \
   { "installer_filename", NULL },                                    \
   { "installer_signature_64", NULL },                                \
   { "installer_trust" , NULL },                                      \
   { "installer_trust_time", NULL },                                  \
   { "howto_install", NULL },                                         \
   { "howto_use", NULL },                                             \
   {                                                                  \
     "(CASE"                                                          \
     "  WHEN installer_trust = 1 THEN 'yes'"                          \
     "  WHEN installer_trust = 2 THEN 'no'"                           \
     "  WHEN installer_trust = 3 THEN 'unknown'"                      \
     "  ELSE ''"                                                      \
     "  END)"                                                         \
     " || ' (' || iso_time (installer_trust_time) || ')'",            \
     "trust"                                                          \
   },                                                                 \
   { NULL, NULL }                                                     \
 }

/**
 * @brief Agent iterator columns for trash case.
 */
#define AGENT_ITERATOR_TRASH_COLUMNS                                  \
 {                                                                    \
   GET_ITERATOR_COLUMNS (agents_trash),                               \
   { "installer", NULL },                                             \
   { "installer_64", NULL },                                          \
   { "installer_filename", NULL },                                    \
   { "installer_signature_64", NULL },                                \
   { "installer_trust" , NULL },                                      \
   { "installer_trust_time", NULL },                                  \
   { "howto_install", NULL },                                         \
   { "howto_use", NULL },                                             \
   {                                                                  \
     "(CASE"                                                          \
     "  WHEN installer_trust = 1 THEN 'yes'"                          \
     "  WHEN installer_trust = 2 THEN 'no'"                           \
     "  WHEN installer_trust = 3 THEN 'unknown'"                      \
     "  ELSE ''"                                                      \
     "  END)"                                                         \
     " || ' (' || iso_time (installer_trust_time) || ')'",            \
     "trust"                                                          \
   },                                                                 \
   { NULL, NULL }                                                     \
 }

/**
 * @brief Get the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Resource.
 */
resource_t
get_iterator_resource (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, 0);
}

/**
 * @brief Get the UUID of the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID of the resource or NULL if iteration is complete.
 */
DEF_ACCESS (get_iterator_uuid, 1);

/**
 * @brief Get the name of the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name of the resource or NULL if iteration is complete.
 */
DEF_ACCESS (get_iterator_name, 2);

/**
 * @brief Get the comment from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Comment.
 */
const char*
get_iterator_comment (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return "";
  ret = iterator_string (iterator, 3);
  return ret ? ret : "";
}

/**
 * @brief Get the creation time of the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Creation time of the resource or NULL if iteration is complete.
 */
DEF_ACCESS (get_iterator_creation_time, 4);

/**
 * @brief Get the modification time of the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Modification time of the resource or NULL if iteration is complete.
 */
DEF_ACCESS (get_iterator_modification_time, 5);

/**
 * @brief Get the owner name of the resource from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Owner name of the resource or NULL if iteration is complete.
 */
DEF_ACCESS (get_iterator_owner_name, 8);

/**
 * @brief Get the owner from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Owner.
 */
user_t
get_iterator_owner (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, 9);
}

/**
 * @brief Initialise an agent iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find agent, failed to find filter, -1 error.
 */
int
init_agent_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = AGENT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = AGENT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = AGENT_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "agent",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the installer from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Installer, or NULL if iteration is complete.  Freed
 *         by cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_installer, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the installer_64 from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Base 64 encoded installer, or NULL if iteration is complete.  Freed
 *         by cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_installer_64, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the installer size from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Installer size, or NULL if iteration is complete.  Freed
 *         by cleanup_iterator.
 */
gsize
agent_iterator_installer_size (iterator_t* iterator)
{
  const char *installer_64;
  gsize installer_size;

  installer_64 = agent_iterator_installer_64 (iterator);
  if (installer_64 && strlen (installer_64))
    {
      gchar *installer;
      installer = (gchar*) g_base64_decode ((gchar*) installer_64,
                                            &installer_size);
      g_free (installer);
      return installer_size;
    }
  return 0;
}

/**
 * @brief Get the installer_filename from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Installer filename, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_installer_filename, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the installer_signature_64 from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Installer signature in base64, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_installer_signature_64,
            GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the trust value from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Trust value.
 */
const char*
agent_iterator_trust (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  switch (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4))
    {
      case 1:  return "yes";
      case 2:  return "no";
      case 3:  return "unknown";
      default: return NULL;
    }
}

/**
 * @brief Get the installer trust time from a agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time agent installer was verified.
 */
time_t
agent_iterator_trust_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
  return ret;
}

/**
 * @brief Get the install HOWTO from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Install HOWTO, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_howto_install, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the usage HOWTO from an agent iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Usage HOWTO, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (agent_iterator_howto_use, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Count number of agents.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of agents in filtered set.
 */
int
agent_count (const get_data_t *get)
{
  static const char *filter_columns[] = AGENT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = AGENT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = AGENT_ITERATOR_TRASH_COLUMNS;

  return count ("agent", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}


/* Notes. */

/**
 * @brief Find a note for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of note.
 * @param[out]  note        Note return, 0 if succesfully failed to find note.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find note), TRUE on error.
 */
gboolean
find_note_with_permission (const char* uuid, note_t* note,
                           const char *permission)
{
  return find_resource_with_permission ("note", uuid, note, permission, 0);
}

/**
 * @brief Create a note.
 *
 * @param[in]  active      NULL or -1 on, 0 off, n on for n days.
 * @param[in]  nvt         OID of noted NVT.
 * @param[in]  text        Note text.
 * @param[in]  hosts       Hosts to apply note to, NULL for any host.
 * @param[in]  port        Port to apply note to, NULL for any port.
 * @param[in]  severity    Severity to apply note to, "" or NULL for any.
 * @param[in]  threat      Threat to apply note to, "" or NULL for any threat.
 *                         Only used if severity is "" or NULL.
 * @param[in]  task        Task to apply note to, 0 for any task.
 * @param[in]  result      Result to apply note to, 0 for any result.
 * @param[out] note        Created note.
 *
 * @return 0 success, 1 failed to find NVT, 2 invalid port, 99 permission
 *         denied, -1 error.
 */
int
create_note (const char* active, const char* nvt, const char* text,
             const char* hosts, const char* port, const char* severity,
             const char* threat, task_t task, result_t result, note_t *note)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  gchar *quoted_nvt;
  double severity_dbl;

  if (user_may ("create_note") == 0)
    return 99;

  if (nvt == NULL)
    return -1;

  quoted_nvt = sql_quote (nvt);
  if (strcmp (nvt, "0")
      && (sql_int ("SELECT count (*) FROM nvts WHERE oid = '%s'", quoted_nvt)
          == 0))
    {
      g_free (quoted_nvt);
      return 1;
    }
  g_free (quoted_nvt);

  if (port && validate_results_port (port))
    return 2;

  if (text == NULL)
    return -1;

  if (threat && strcmp (threat, "High") && strcmp (threat, "Medium")
      && strcmp (threat, "Low") && strcmp (threat, "Log")
      && strcmp (threat, "Debug") && strcmp (threat, ""))
    return -1;

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);

  severity_dbl = 0.0;
  if (severity != NULL && strcmp (severity, ""))
    {
      if (sscanf (severity, "%lf", &severity_dbl) != 1
          || ((severity_dbl < 0.0 || severity_dbl > 10.0)
              && severity_dbl != SEVERITY_LOG
              && severity_dbl != SEVERITY_DEBUG))
        return 3;
      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else if (threat != NULL && strcmp (threat, ""))
    {
      if (strcmp (threat, "Alarm") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "High") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Medium") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Low") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Log") == 0)
        severity_dbl = SEVERITY_LOG;
      else if (strcmp (threat, "Debug") == 0)
        severity_dbl = SEVERITY_DEBUG;
      else
        return -1;

      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else
    quoted_severity = g_strdup ("NULL");

  sql ("INSERT INTO notes"
       " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
       "  port, severity, task, result, end_time)"
       " VALUES"
       " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', %i, %i, %s, %s, %s, %s, %llu, %llu, %i);",
       current_credentials.uuid,
       nvt,
       time (NULL),
       time (NULL),
       quoted_text,
       quoted_hosts,
       quoted_port,
       quoted_severity,
       task,
       result,
       (active == NULL || (strcmp (active, "-1") == 0))
         ? 0
         : (strcmp (active, "0")
             ? (time (NULL) + (atoi (active) * 60 * 60 * 24))
             : 1));

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);

  if (note)
    *note = sql_last_insert_id ();

  return 0;
}

/**
 * @brief Create a note from an existing note.
 *
 * @param[in]  note_id   UUID of existing note.
 * @param[out] new_note  New note.
 *
 * @return 0 success, 1 note exists already, 2 failed to find existing
 *         note, -1 error.
 */
int
copy_note (const char *note_id, note_t* new_note)
{
  return copy_resource ("note", NULL, NULL, note_id,
                        "nvt, text, hosts, port, severity, task, result,"
                        "end_time",
                        1, new_note);
}

/**
 * @brief Delete a note.
 *
 * @param[in]  note_id    UUID of note.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 2 failed to find note, 99 permission denied, -1 error.
 */
int
delete_note (const char *note_id, int ultimate)
{
  note_t note = 0;

  sql_begin_immediate ();

  if (user_may ("delete_note") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_note_with_permission (note_id, &note, "delete_note"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (note == 0)
    {
      if (find_trash ("note", note_id, &note))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (note == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      permissions_set_orphans ("note", note, LOCATION_TRASH);
      tags_set_orphans ("note", note, LOCATION_TRASH);

      sql ("DELETE FROM notes_trash WHERE id = %llu;", note);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      sql ("INSERT INTO notes_trash"
           " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
           "  port, severity, task, result, end_time)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, task, result, end_time"
           " FROM notes WHERE id = %llu;",
           note);

      permissions_set_locations ("note", note,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("note", note,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("note", note, LOCATION_TABLE);
      tags_set_orphans ("note", note, LOCATION_TABLE);
    }

  sql ("DELETE FROM notes WHERE id = %llu;", note);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return the UUID of a note.
 *
 * @param[in]   note  Note.
 * @param[out]  id    Pointer to a newly allocated string.
 *
 * @return 0.
 */
int
note_uuid (note_t note, char ** id)
{
  *id = sql_string ("SELECT uuid FROM notes WHERE id = %llu;",
                    note);
  return 0;
}

/**
 * @brief Modify a note.
 *
 * @param[in]  note        Note.
 * @param[in]  active      NULL or -2 leave as is, -1 on, 0 off, n on for n
 *                         days.
 * @param[in]  text        Note text.
 * @param[in]  hosts       Hosts to apply note to, NULL for any host.
 * @param[in]  port        Port to apply note to, NULL for any port.
 * @param[in]  severity    Severity to apply note to, "" or NULL for any.
 * @param[in]  threat      Threat to apply note to, "" or NULL for any threat.
 *                         Only used if severity is "" or NULL.
 * @param[in]  task        Task to apply note to, 0 for any task.
 * @param[in]  result      Result to apply note to, 0 for any result.
 *
 * @return 0 success, -1 error, 1 syntax error in active, 2 invalid port,
 *         3 invalid severity.
 */
int
modify_note (note_t note, const char *active, const char* text,
             const char* hosts, const char* port, const char* severity,
             const char* threat, task_t task, result_t result)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  double severity_dbl;

  if (note == 0)
    return -1;

  if (text == NULL)
    return -1;

  if (threat && strcmp (threat, "High") && strcmp (threat, "Medium")
      && strcmp (threat, "Low") && strcmp (threat, "Log")
      && strcmp (threat, "Debug") && strcmp (threat, "Alarm")
      && strcmp (threat, ""))
    return -1;

  if (port && validate_results_port (port))
    return 2;

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);

  severity_dbl = 0.0;
  if (severity != NULL && strcmp (severity, ""))
    {
      if (sscanf (severity, "%lf", &severity_dbl) != 1
          || ((severity_dbl < 0.0 || severity_dbl > 10.0)
              && severity_dbl != SEVERITY_LOG
              && severity_dbl != SEVERITY_DEBUG))
        return 3;
      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else if (threat != NULL && strcmp (threat, ""))
    {
      if (strcmp (threat, "Alarm") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "High") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Medium") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Low") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Log") == 0)
        severity_dbl = SEVERITY_LOG;
      else if (strcmp (threat, "Debug") == 0)
        severity_dbl = SEVERITY_DEBUG;
      else
        return -1;

      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else
    quoted_severity = g_strdup ("NULL");

  if ((active == NULL) || (strcmp (active, "-2") == 0))
    sql ("UPDATE notes SET"
         " modification_time = %i,"
         " text = %s,"
         " hosts = %s,"
         " port = %s,"
         " severity = %s,"
         " task = %llu,"
         " result = %llu"
         " WHERE id = %llu;",
         time (NULL),
         quoted_text,
         quoted_hosts,
         quoted_port,
         quoted_severity,
         task,
         result,
         note);
  else
    {
      const char *point;
      point = active;
      if (strcmp (point, "-1"))
        {
          while (*point && isdigit (*point)) point++;
          if (*point)
            return 1;
        }
      sql ("UPDATE notes SET"
           " end_time = %i,"
           " modification_time = %i,"
           " text = %s,"
           " hosts = %s,"
           " port = %s,"
           " severity = %s,"
           " task = %llu,"
           " result = %llu"
           " WHERE id = %llu;",
           (strcmp (active, "-1")
             ? (strcmp (active, "0")
                 ? (time (NULL) + atoi (active) * 60 * 60 * 24)
                 : 1)
             : 0),
           time (NULL),
           quoted_text,
           quoted_hosts,
           quoted_port,
           quoted_severity,
           task,
           result,
           note);
    }

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);

  return 0;
}

/**
 * @brief Filter columns for note iterator.
 */
#define NOTE_ITERATOR_FILTER_COLUMNS                                          \
 { ANON_GET_ITERATOR_FILTER_COLUMNS, "name", "nvt", "text", "nvt_id",         \
   "task_name", "task_id", "hosts", "port", "active", "result", "severity",   \
   NULL }

/**
 * @brief Note iterator columns.
 */
#define NOTE_ITERATOR_COLUMNS                                              \
 {                                                                         \
   { "notes.id", "id" },                                                   \
   { "notes.uuid", "uuid" },                                               \
   { " (SELECT name FROM nvts WHERE oid = notes.nvt)", "name" },           \
   { "CAST ('' AS TEXT)", NULL },                                          \
   { "iso_time (notes.creation_time)", NULL },                             \
   { "iso_time (notes.modification_time)", NULL },                         \
   { "notes.creation_time", "created" },                                   \
   { "notes.modification_time", "modified" },                              \
   { "(SELECT name FROM users WHERE users.id = notes.owner)", "_owner" },  \
   { "owner", NULL },                                                      \
   /* Columns specific to notes. */                                        \
   { "notes.nvt", "oid" },                                                 \
   { "notes.text", "text" },                                               \
   { "notes.hosts", "hosts" },                                             \
   { "notes.port", "port" },                                               \
   { "severity_to_level (notes.severity, 1)", "threat" },                  \
   { "notes.task", NULL },                                                 \
   { "notes.result", "result" },                                           \
   { "notes.end_time", NULL },                                             \
   { "(notes.end_time = 0) OR (notes.end_time >= m_now ())", "active" },   \
   { "(SELECT name FROM nvts WHERE oid = notes.nvt)", "nvt" },             \
   { "notes.nvt", "nvt_id" },                                              \
   { "(SELECT uuid FROM tasks WHERE id = notes.task)", "task_id" },        \
   { "(SELECT name FROM tasks WHERE id = notes.task)", "task_name" },      \
   { "notes.severity", "severity" },                                       \
   { "(SELECT name FROM users WHERE users.id = notes.owner)", "_owner" },  \
   { NULL, NULL }                                                          \
 }

/**
 * @brief Note iterator columns for trash case.
 */
#define NOTE_ITERATOR_TRASH_COLUMNS                                              \
 {                                                                               \
   { "notes_trash.id", "id" },                                                   \
   { "notes_trash.uuid", "uuid" },                                               \
   { "CAST ('' AS TEXT)", NULL },                                                \
   { "CAST ('' AS TEXT)", NULL },                                                \
   { "iso_time (notes_trash.creation_time)", NULL },                             \
   { "iso_time (notes_trash.modification_time)", NULL },                         \
   { "notes_trash.creation_time", "created" },                                   \
   { "notes_trash.modification_time", "modified" },                              \
   { "(SELECT name FROM users WHERE users.id = notes_trash.owner)", "_owner" },  \
   { "owner", NULL },                                                            \
   /* Columns specific to notes_trash. */                                        \
   { "notes_trash.nvt", "oid" },                                                 \
   { "notes_trash.text", "text" },                                               \
   { "notes_trash.hosts", "hosts" },                                             \
   { "notes_trash.port", "port" },                                               \
   { "severity_to_level (notes_trash.severity, 1)", "threat" },                  \
   { "notes_trash.task", NULL },                                                 \
   { "notes_trash.result", "result" },                                           \
   { "notes_trash.end_time", NULL },                                             \
   { "(notes_trash.end_time = 0) OR (notes_trash.end_time >= m_now ())",         \
     "active" },                                                                 \
   { "(SELECT name FROM nvts WHERE oid = notes_trash.nvt)", "nvt" },             \
   { "notes_trash.nvt", "nvt_id" },                                              \
   { "(SELECT uuid FROM tasks WHERE id = notes_trash.task)", "task_id" },        \
   { "(SELECT name FROM tasks WHERE id = notes_trash.task)", "task_name" },      \
   { "notes_trash.severity", "severity" },                                       \
   { NULL, NULL }                                                                \
 }

/**
 * @brief Count number of notes.
 *
 * @param[in]  get         GET params.
 * @param[in]  result      Result to limit notes to, 0 for all.
 * @param[in]  task        If result is > 0, task whose notes on result to
 *                         include, otherwise task to limit notes to.  0 for
 *                         all tasks.
 * @param[in]  nvt         NVT to limit notes to, 0 for all.
 *
 * @return Total number of notes in filtered set.
 */
int
note_count (const get_data_t *get, nvt_t nvt, result_t result, task_t task)
{
  static const char *filter_columns[] = NOTE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = NOTE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = NOTE_ITERATOR_TRASH_COLUMNS;
  gchar *result_clause, *filter, *task_id;
  int ret;

  /* Treat the "task_id" filter keyword as if the task was given in "task". */

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  task_id = filter_term_value (filter ? filter : get->filter, "task_id");

  g_free (filter);

  if (task_id)
    {
      find_task (task_id, &task);
      g_free (task_id);
    }

  if (result)
    {
      gchar *severity_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup_printf ("(SELECT CASE"
                                        " WHEN results.severity"
                                        "      > " G_STRINGIFY (SEVERITY_LOG)
                                        " THEN nvts.cvss_base"
                                        " ELSE results.severity END"
                                        " FROM results, nvts"
                                        " WHERE (nvts.oid = results.nvt)"
                                        "   AND (results.id = %llu))",
                                        result);
      else
        severity_sql = g_strdup_printf ("(SELECT results.severity"
                                        " FROM results"
                                        " WHERE results.id = %llu)",
                                        result);

      result_clause = g_strdup_printf (" AND"
                                       " (result = %llu"
                                       "  OR (result = 0 AND nvt ="
                                       "      (SELECT results.nvt FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (hosts is NULL"
                                       "      OR hosts = ''"
                                       "      OR hosts_contains (hosts,"
                                       "      (SELECT results.host FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (port is NULL"
                                       "      OR port = ''"
                                       "      OR port ="
                                       "      (SELECT results.port FROM results"
                                       "       WHERE results.id = %llu))"
                                       " AND (severity_matches_ov (%s,"
                                       "                           severity))"
                                       " AND (task = 0 OR task = %llu)",
                                       result,
                                       result,
                                       result,
                                       result,
                                       severity_sql,
                                       task);
      g_free (severity_sql);
    }
  else if (task)
    {
      result_clause = g_strdup_printf
                       (" AND (notes.task = %llu OR notes.task = 0)"
                        " AND nvt IN"
                        " (SELECT DISTINCT nvt FROM results"
                        "  WHERE results.task = %llu)"
                        " AND (notes.result = 0"
                        "      OR (SELECT task FROM results"
                        "          WHERE results.id = notes.result)"
                        "         = %llu)",
                        task,
                        task,
                        task);
    }
  else if (nvt)
    {
      result_clause = g_strdup_printf
                       (" AND (notes.nvt ="
                        " (SELECT oid FROM nvts WHERE nvts.id = %llu))"
                        " AND ((notes.owner IS NULL) OR (notes.owner ="
                        " (SELECT id FROM users WHERE users.uuid = '%s')))",
                        nvt,
                        current_credentials.uuid);
    }
  else
    result_clause = NULL;

  ret = count ("note",
               get,
               columns,
               trash_columns,
               filter_columns,
               task || nvt,
               NULL,
               result_clause,
               TRUE);

  g_free (result_clause);

  return ret;
}

/**
 * @brief Initialise a note iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 * @param[in]  result      Result to limit notes to, 0 for all.
 * @param[in]  task        If result is > 0, task whose notes on result to
 *                         include, otherwise task to limit notes to.  0 for
 *                         all tasks.
 * @param[in]  nvt         NVT to limit notes to, 0 for all.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_note_iterator (iterator_t* iterator, const get_data_t *get, nvt_t nvt,
                    result_t result, task_t task)
{
  static const char *filter_columns[] = NOTE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = NOTE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = NOTE_ITERATOR_TRASH_COLUMNS;
  gchar *result_clause, *filter, *task_id;
  int ret;

  assert (current_credentials.uuid);
  assert ((nvt && get->id) == 0);
  assert ((task && get->id) == 0);

  assert (result ? nvt == 0 : 1);
  assert (task ? nvt == 0 : 1);

  /* Treat the "task_id" filter keyword as if the task was given in "task". */

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  task_id = filter_term_value (filter ? filter : get->filter, "task_id");

  g_free (filter);

  if (task_id)
    {
      find_task (task_id, &task);
      g_free (task_id);
    }

  if (result)
    {
      gchar *severity_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup_printf ("(SELECT CASE"
                                        " WHEN results.severity"
                                        "      > " G_STRINGIFY (SEVERITY_LOG)
                                        " THEN nvts.cvss_base"
                                        " ELSE results.severity END"
                                        " FROM results, nvts"
                                        " WHERE (nvts.oid = results.nvt)"
                                        "   AND (results.id = %llu))",
                                        result);
      else
        severity_sql = g_strdup_printf ("(SELECT results.severity"
                                        " FROM results"
                                        " WHERE results.id = %llu)",
                                        result);

      result_clause = g_strdup_printf (" AND"
                                       " (result = %llu"
                                       "  OR (result = 0 AND nvt ="
                                       "      (SELECT results.nvt FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (hosts is NULL"
                                       "      OR hosts = ''"
                                       "      OR hosts_contains (hosts,"
                                       "      (SELECT results.host FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (port is NULL"
                                       "      OR port = ''"
                                       "      OR port ="
                                       "      (SELECT results.port FROM results"
                                       "       WHERE results.id = %llu))"
                                       " AND (severity_matches_ov (%s,"
                                       "                           severity))"
                                       " AND (task = 0 OR task = %llu)",
                                       result,
                                       result,
                                       result,
                                       result,
                                       severity_sql,
                                       task);

      g_free (severity_sql);
    }
  else if (task)
    {
      result_clause = g_strdup_printf
                       (" AND (notes.task = %llu OR notes.task = 0)"
                        " AND nvt IN"
                        " (SELECT DISTINCT nvt FROM results"
                        "  WHERE results.task = %llu)"
                        " AND (notes.result = 0"
                        "      OR (SELECT task FROM results"
                        "          WHERE results.id = notes.result)"
                        "         = %llu)",
                        task,
                        task,
                        task);
    }
  else if (nvt)
    {
      result_clause = g_strdup_printf
                       (" AND (notes.nvt ="
                        " (SELECT oid FROM nvts WHERE nvts.id = %llu))"
                        " AND ((notes.owner IS NULL) OR (notes.owner ="
                        " (SELECT id FROM users WHERE users.uuid = '%s')))",
                        nvt,
                        current_credentials.uuid);
    }
  else
    result_clause = NULL;

  ret = init_get_iterator (iterator,
                           "note",
                           get,
                           columns,
                           trash_columns,
                           filter_columns,
                           task || nvt,
                           NULL,
                           result_clause,
                           TRUE);

  g_free (result_clause);

  return ret;
}

/**
 * @brief Get the NVT OID from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT OID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (note_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the text from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Text, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (note_iterator_text, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the hosts from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (note_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the port from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Port, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (note_iterator_port, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the threat from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Threat.
 */
const char *
note_iterator_threat (iterator_t *iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
  if (ret == NULL) return NULL;
  return message_type_threat (ret);
}

/**
 * @brief Get the task from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The task associated with the note, or 0 on error.
 */
task_t
note_iterator_task (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
}

/**
 * @brief Get the result from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The result associated with the note, or 0 on error.
 */
result_t
note_iterator_result (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (result_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

/**
 * @brief Get the end time from an note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time until which note applies.  0 for always.  1 means the
 *         note has been explicitly turned off.
 */
time_t
note_iterator_end_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 7);
  return ret;
}

/**
 * @brief Get the active status from an note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if active, else 0.
 */
int
note_iterator_active (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
  return ret;
}

/**
 * @brief Get the NVT name from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (note_iterator_nvt_name, GET_ITERATOR_COLUMN_COUNT + 9);

/**
 * @brief Get the severity from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity to apply the note to, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (note_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 13);


/* Overrides. */

/**
 * @brief Find a override for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of override.
 * @param[out]  override    Override return, 0 if succesfully failed to find
 *                          override.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find override), TRUE on
 *         error.
 */
gboolean
find_override_with_permission (const char* uuid, override_t* override,
                               const char *permission)
{
  return find_resource_with_permission ("override", uuid, override, permission,
                                        0);
}

/**
 * @brief Create an override.
 *
 * @param[in]  active      NULL or -1 on, 0 off, n on for n days.
 * @param[in]  nvt         OID of overrided NVT.
 * @param[in]  text        Override text.
 * @param[in]  hosts       Hosts to apply override to, NULL for any host.
 * @param[in]  port        Port to apply override to, NULL for any port.
 * @param[in]  threat      Threat to apply override to, "" or NULL for any threat.
 * @param[in]  new_threat  Threat to override result to.
 * @param[in]  severity    Severity to apply override to, "" or NULL for any.
 * @param[in]  new_severity Severity score to override "Alarm" type results to.
 * @param[in]  task        Task to apply override to, 0 for any task.
 * @param[in]  result      Result to apply override to, 0 for any result.
 * @param[out] override    Created override.
 *
 * @return 0 success, 1 failed to find NVT, 2 invalid port, 3 invalid severity,
 *         99 permission denied, -1 error.
 */
int
create_override (const char* active, const char* nvt, const char* text,
                 const char* hosts, const char* port, const char* threat,
                 const char* new_threat, const char* severity,
                 const char* new_severity, task_t task, result_t result,
                 override_t* override)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  gchar *quoted_nvt;
  double severity_dbl, new_severity_dbl;

  if (user_may ("create_override") == 0)
    return 99;

  if (nvt == NULL)
    return -1;

  if (text == NULL)
    return -1;

  quoted_nvt = sql_quote (nvt);
  if (strcmp (nvt, "0")
      && (sql_int ("SELECT count (*) FROM nvts WHERE oid = '%s'", quoted_nvt)
          == 0))
    {
      g_free (quoted_nvt);
      return 1;
    }
  g_free (quoted_nvt);

  if (port && validate_results_port (port))
    return 2;

  if (threat && strcmp (threat, "High") && strcmp (threat, "Medium")
      && strcmp (threat, "Low") && strcmp (threat, "Log")
      && strcmp (threat, "Debug") && strcmp (threat, "Alarm")
      && strcmp (threat, ""))
    return -1;

  if (new_threat && strcmp (new_threat, "High") && strcmp (new_threat, "Medium")
      && strcmp (new_threat, "Low") && strcmp (new_threat, "Log")
      && strcmp (new_threat, "Debug") && strcmp (new_threat, "False Positive")
      && strcmp (new_threat, "Alarm") && strcmp (new_threat, ""))
    return -1;

  severity_dbl = 0.0;
  if (severity != NULL && strcmp (severity, ""))
    {
      if (sscanf (severity, "%lf", &severity_dbl) != 1
          || ((severity_dbl < 0.0 || severity_dbl > 10.0)
              && severity_dbl != SEVERITY_LOG
              && severity_dbl != SEVERITY_DEBUG))
        return 3;
      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else if (threat != NULL && strcmp (threat, ""))
    {
      if (strcmp (threat, "Alarm") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "High") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Medium") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Low") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Log") == 0)
        severity_dbl = SEVERITY_LOG;
      else if (strcmp (threat, "Debug") == 0)
        severity_dbl = SEVERITY_DEBUG;
      else
        return -1;

      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else
    quoted_severity = g_strdup ("NULL");

  new_severity_dbl = 0.0;
  if (new_severity != NULL && strcmp (new_severity, ""))
    {
      if (sscanf (new_severity, "%lf", &new_severity_dbl) != 1
          || ((new_severity_dbl < 0.0 || new_severity_dbl > 10.0)
              && new_severity_dbl != SEVERITY_LOG
              && new_severity_dbl != SEVERITY_FP
              && new_severity_dbl != SEVERITY_DEBUG))
        {
          g_free (quoted_severity);
          return 2;
        }
    }
  else if (new_threat != NULL && strcmp (new_threat, ""))
    {
      if (strcmp (new_threat, "Alarm") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "High") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "Medium") == 0)
        new_severity_dbl = 5.0;
      else if (strcmp (new_threat, "Low") == 0)
        new_severity_dbl = 2.0;
      else if (strcmp (new_threat, "Log") == 0)
        new_severity_dbl = SEVERITY_LOG;
      else if (strcmp (new_threat, "Debug") == 0)
        new_severity_dbl = SEVERITY_DEBUG;
      else
        return -1;
    }
  else
    {
      g_free (quoted_severity);
      return -1;
    }

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);

  sql ("INSERT INTO overrides"
       " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
       "  port, severity, new_severity, task, result, end_time)"
       " VALUES"
       " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', %i, %i, %s, %s, %s, %s, %1.1f, %llu, %llu, %i);",
       current_credentials.uuid,
       nvt,
       time (NULL),
       time (NULL),
       quoted_text,
       quoted_hosts,
       quoted_port,
       quoted_severity,
       new_severity_dbl,
       task,
       result,
       (active == NULL || (strcmp (active, "-1") == 0))
         ? 0
         : (strcmp (active, "0")
             ? (time (NULL) + (atoi (active) * 60 * 60 * 24))
             : 1));

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);

  if (override)
    *override = sql_last_insert_id ();

  reports_clear_count_cache (1);

  return 0;
}

/**
 * @brief Return the UUID of an override.
 *
 * @param[in]   override  Override.
 * @param[out]  id        Pointer to a newly allocated string.
 *
 * @return 0.
 */
int
override_uuid (override_t override, char ** id)
{
  *id = sql_string ("SELECT uuid FROM overrides WHERE id = %llu;",
                    override);
  return 0;
}

/**
 * @brief Create a override from an existing override.
 *
 * @param[in]  override_id   UUID of existing override.
 * @param[out] new_override  New override.
 *
 * @return 0 success, 1 override exists already, 2 failed to find existing
 *         override, -1 error.
 */
int
copy_override (const char *override_id, override_t* new_override)
{
  return copy_resource ("override", NULL, NULL, override_id,
                        "nvt, text, hosts, port, severity, new_severity, task,"
                        " result, end_time",
                        1, new_override);
}

/**
 * @brief Delete a override.
 *
 * @param[in]  override_id  UUID of override.
 * @param[in]  ultimate     Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 2 failed to find override, 99 permission denied, -1 error.
 */
int
delete_override (const char *override_id, int ultimate)
{
  override_t override;

  sql_begin_immediate ();

  if (user_may ("delete_override") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  override = 0;

  if (find_override_with_permission (override_id, &override, "delete_override"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (override == 0)
    {
      if (find_trash ("override", override_id, &override))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (override == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      permissions_set_orphans ("override", override, LOCATION_TRASH);
      tags_set_orphans ("override", override, LOCATION_TRASH);

      sql ("DELETE FROM overrides_trash WHERE id = %llu;", override);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      sql ("INSERT INTO overrides_trash"
           " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
           "  port, severity, new_severity, task, result, end_time)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, new_severity,task,"
           "        result, end_time"
           " FROM overrides WHERE id = %llu;",
           override);

      permissions_set_locations ("override", override,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("override", override,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("override", override, LOCATION_TABLE);
      tags_set_orphans ("override", override, LOCATION_TABLE);
    }

  sql ("DELETE FROM overrides WHERE id = %llu;", override);

  reports_clear_count_cache (1);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Modify an override.
 *
 * @param[in]  override    Override.
 * @param[in]  active      NULL or -2 leave as is, -1 on, 0 off, n on for n
 *                         days.
 * @param[in]  text        Override text.
 * @param[in]  hosts       Hosts to apply override to, NULL for any host.
 * @param[in]  port        Port to apply override to, NULL for any port.
 * @param[in]  threat      Threat to apply override to, "" or NULL for any threat.
 * @param[in]  new_threat  Threat to override result to.
 * @param[in]  severity    Severity to apply override to, "" or NULL for any threat.
 * @param[in]  new_severity Severity score to override "Alarm" type results to.
 * @param[in]  task        Task to apply override to, 0 for any task.
 * @param[in]  result      Result to apply override to, 0 for any result.
 *
 * @return 0 success, -1 error, 1 syntax error in active, 2 invalid port,
 *         3 invalid severity score.
 */
int
modify_override (override_t override, const char *active, const char* text,
                 const char* hosts, const char* port, const char* threat,
                 const char* new_threat, const char* severity,
                 const char* new_severity, task_t task, result_t result)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  double severity_dbl, new_severity_dbl;

  if (override == 0)
    return -1;

  if (text == NULL)
    return -1;

  if (port && validate_results_port (port))
    return 2;

  if (threat && strcmp (threat, "High") && strcmp (threat, "Medium")
      && strcmp (threat, "Low") && strcmp (threat, "Log")
      && strcmp (threat, "Debug") && strcmp (threat, "Alarm")
      && strcmp (threat, ""))
    return -1;

  if (new_threat && strcmp (new_threat, "High") && strcmp (new_threat, "Medium")
      && strcmp (new_threat, "Low") && strcmp (new_threat, "Log")
      && strcmp (new_threat, "Debug") && strcmp (new_threat, "False Positive")
      && strcmp (new_threat, "Alarm") && strcmp (new_threat, ""))
    return -1;

  severity_dbl = 0.0;
  if (severity != NULL && strcmp (severity, ""))
    {
      if (sscanf (severity, "%lf", &severity_dbl) != 1
          || ((severity_dbl < 0.0 || severity_dbl > 10.0)
              && severity_dbl != SEVERITY_LOG
              && severity_dbl != SEVERITY_DEBUG))
        return 3;
      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else if (threat != NULL && strcmp (threat, ""))
    {
      if (strcmp (threat, "Alarm") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "High") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Medium") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Low") == 0)
        severity_dbl = 0.1;
      else if (strcmp (threat, "Log") == 0)
        severity_dbl = SEVERITY_LOG;
      else if (strcmp (threat, "Debug") == 0)
        severity_dbl = SEVERITY_DEBUG;
      else
        return -1;

      quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl);
    }
  else
    quoted_severity = g_strdup ("NULL");

  new_severity_dbl = 0.0;
  if (new_severity != NULL && strcmp (new_severity, ""))
    {
      if (sscanf (new_severity, "%lf", &new_severity_dbl) != 1
          || ((new_severity_dbl < 0.0 || new_severity_dbl > 10.0)
              && new_severity_dbl != SEVERITY_LOG
              && new_severity_dbl != SEVERITY_FP
              && new_severity_dbl != SEVERITY_DEBUG))
        {
          g_free (quoted_severity);
          return 3;
        }
    }
  else if (new_threat != NULL && strcmp (new_threat, ""))
    {
      if (strcmp (new_threat, "Alarm") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "High") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "Medium") == 0)
        new_severity_dbl = 5.0;
      else if (strcmp (new_threat, "Low") == 0)
        new_severity_dbl = 2.0;
      else if (strcmp (new_threat, "Log") == 0)
        new_severity_dbl = SEVERITY_LOG;
      else if (strcmp (new_threat, "Debug") == 0)
        new_severity_dbl = SEVERITY_DEBUG;
      else
        {
          g_free (quoted_severity);
          return -1;
        }
    }
  else
    {
      g_free (quoted_severity);
      return -1;
    }

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);

  if ((active == NULL) || (strcmp (active, "-2") == 0))
    sql ("UPDATE overrides SET"
         " modification_time = %i,"
         " text = %s,"
         " hosts = %s,"
         " port = %s,"
         " severity = %s,"
         " new_severity = %f,"
         " task = %llu,"
         " result = %llu"
         " WHERE id = %llu;",
         time (NULL),
         quoted_text,
         quoted_hosts,
         quoted_port,
         quoted_severity,
         new_severity_dbl,
         task,
         result,
         override);
  else
    {
      const char *point;
      point = active;
      if (strcmp (point, "-1"))
        {
          while (*point && isdigit (*point)) point++;
          if (*point)
            return 1;
        }
      sql ("UPDATE overrides SET"
           " end_time = %i,"
           " modification_time = %i,"
           " text = %s,"
           " hosts = %s,"
           " port = %s,"
           " severity = %s,"
           " new_severity = %f,"
           " task = %llu,"
           " result = %llu"
           " WHERE id = %llu;",
           (strcmp (active, "-1")
             ? (strcmp (active, "0")
                 ? (time (NULL) + atoi (active) * 60 * 60 * 24)
                 : 1)
             : 0),
           time (NULL),
           quoted_text,
           quoted_hosts,
           quoted_port,
           quoted_severity,
           new_severity_dbl,
           task,
           result,
           override);
    }

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);

  reports_clear_count_cache (1);

  return 0;
}

/**
 * @brief Filter columns for override iterator.
 */
#define OVERRIDE_ITERATOR_FILTER_COLUMNS                                      \
 { ANON_GET_ITERATOR_FILTER_COLUMNS, "name", "nvt", "text", "nvt_id",         \
   "task_name", "task_id", "hosts", "port", "threat", "new_threat", "active", \
   "result", "severity", "new_severity", NULL }

/**
 * @brief Override iterator columns.
 */
#define OVERRIDE_ITERATOR_COLUMNS                                           \
 {                                                                          \
   { "overrides.id", "id" },                                                \
   { "overrides.uuid", "uuid" },                                            \
   { "(SELECT name FROM nvts WHERE oid = overrides.nvt)", "name" },         \
   { "CAST ('' AS TEXT)", NULL },                                           \
   { "iso_time (overrides.creation_time)", NULL },                          \
   { "iso_time (overrides.modification_time)", NULL },                      \
   { "overrides.creation_time", "created" },                                \
   { "overrides.modification_time", "modified" },                           \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides.owner)",           \
     "_owner"                                                               \
   },                                                                       \
   { "owner", NULL },                                                       \
   /* Columns specific to overrides. */                                     \
   { "overrides.nvt", "oid" },                                              \
   { "overrides.text", "text" },                                            \
   { "overrides.hosts", "hosts" },                                          \
   { "overrides.port", "port" },                                            \
   { "severity_to_level (overrides.severity, 1)", "threat" },               \
   { "severity_to_level (overrides.new_severity, 0)", "new_threat" },       \
   { "overrides.task", NULL },                                              \
   { "overrides.result", "result" },                                        \
   { "overrides.end_time", NULL },                                          \
   {                                                                        \
     "(overrides.end_time = 0) OR (overrides.end_time >= m_now ())",        \
     "active"                                                               \
   },                                                                       \
   { "(SELECT name FROM nvts WHERE oid = overrides.nvt)", "nvt" },          \
   { "overrides.nvt", "nvt_id" },                                           \
   { "(SELECT uuid FROM tasks WHERE id = overrides.task)", "task_id" },     \
   { "(SELECT name FROM tasks WHERE id = overrides.task)", "task_name" },   \
   { "overrides.severity", "severity" },                                    \
   { "overrides.new_severity", "new_severity" },                            \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides.owner)",           \
     "_owner"                                                               \
   },                                                                       \
   { NULL, NULL }                                                           \
 }

/**
 * @brief Override iterator columns for trash case.
 */
#define OVERRIDE_ITERATOR_TRASH_COLUMNS                                     \
 {                                                                          \
   { "overrides_trash.id", "id" },                                          \
   { "overrides_trash.uuid", "uuid" },                                      \
   { "CAST ('' AS TEXT)", NULL },                                           \
   { "CAST ('' AS TEXT)", NULL },                                           \
   { "iso_time (overrides_trash.creation_time)", NULL },                    \
   { "iso_time (overrides_trash.modification_time)", NULL },                \
   { "overrides_trash.creation_time", "created" },                          \
   { "overrides_trash.modification_time", "modified" },                     \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides_trash.owner)",     \
     "_owner"                                                               \
   },                                                                       \
   { "owner", NULL },                                                       \
   /* Columns specific to overrides_trash. */                               \
   { "overrides_trash.nvt", "oid" },                                        \
   { "overrides_trash.text", "text" },                                      \
   { "overrides_trash.hosts", "hosts" },                                    \
   { "overrides_trash.port", "port" },                                      \
   { "severity_to_level (overrides_trash.severity, 1)", "threat" },         \
   { "severity_to_level (overrides_trash.new_severity, 0)", "new_threat" }, \
   { "overrides_trash.task", NULL },                                        \
   { "overrides_trash.result", "result" },                                  \
   { "overrides_trash.end_time", NULL },                                    \
   {                                                                        \
     "(overrides_trash.end_time = 0)"                                       \
     " OR (overrides_trash.end_time >= m_now ())",                          \
     "active"                                                               \
   },                                                                       \
   { "(SELECT name FROM nvts WHERE oid = overrides_trash.nvt)", "nvt" },    \
   { "overrides_trash.nvt", "nvt_id" },                                     \
   {                                                                        \
     "(SELECT uuid FROM tasks WHERE id = overrides_trash.task)",            \
     "task_id"                                                              \
   },                                                                       \
   {                                                                        \
     "(SELECT name FROM tasks WHERE id = overrides_trash.task)",            \
     "task_name"                                                            \
   },                                                                       \
   { "overrides_trash.severity", NULL },                                    \
   { "overrides_trash.new_severity", NULL },                                \
   { NULL, NULL }                                                           \
 }

/**
 * @brief Count number of overrides.
 *
 * @param[in]  get         GET params.
 * @param[in]  result      Result to limit overrides to, 0 for all.
 * @param[in]  task        If result is > 0, task whose overrides on result to
 *                         include, otherwise task to limit overrides to.  0 for
 *                         all tasks.
 * @param[in]  nvt         NVT to limit overrides to, 0 for all.
 *
 * @return Total number of overrides in filtered set.
 */
int
override_count (const get_data_t *get, nvt_t nvt, result_t result, task_t task)
{
  static const char *filter_columns[] = OVERRIDE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OVERRIDE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = OVERRIDE_ITERATOR_TRASH_COLUMNS;
  gchar *result_clause, *filter, *task_id;
  int ret;

  /* Treat the "task_id" filter keyword as if the task was given in "task". */

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  task_id = filter_term_value (filter ? filter : get->filter, "task_id");

  g_free (filter);

  if (task_id)
    {
      find_task (task_id, &task);
      g_free (task_id);
    }

  if (result)
    {
      gchar *severity_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup_printf ("(SELECT CASE"
                                        " WHEN results.severity"
                                        "      > " G_STRINGIFY (SEVERITY_LOG)
                                        " THEN nvts.cvss_base"
                                        " ELSE results.severity END"
                                        " FROM results, nvts"
                                        " WHERE (nvts.oid = results.nvt)"
                                        "   AND (results.id = %llu))",
                                        result);
      else
        severity_sql = g_strdup_printf ("(SELECT results.severity"
                                        " FROM results"
                                        " WHERE results.id = %llu)",
                                        result);

      result_clause = g_strdup_printf (" AND"
                                       " (result = %llu"
                                       "  OR (result = 0 AND nvt ="
                                       "      (SELECT results.nvt FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (hosts is NULL"
                                       "      OR hosts = ''"
                                       "      OR hosts_contains (hosts,"
                                       "      (SELECT results.host FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (port is NULL"
                                       "      OR port = ''"
                                       "      OR port ="
                                       "      (SELECT results.port FROM results"
                                       "       WHERE results.id = %llu))"
                                       " AND (severity_matches_ov (%s,"
                                       "                           severity))"
                                       " AND (task = 0 OR task = %llu)",
                                       result,
                                       result,
                                       result,
                                       result,
                                       severity_sql,
                                       task);

      g_free (severity_sql);
    }
  else if (task)
    {
      result_clause = g_strdup_printf
                       (" AND (overrides.task = %llu OR overrides.task = 0)"
                        " AND nvt IN"
                        " (SELECT DISTINCT nvt FROM results"
                        "  WHERE results.task = %llu)"
                        " AND (overrides.result = 0"
                        "      OR (SELECT task FROM results"
                        "          WHERE results.id = overrides.result)"
                        "         = %llu)",
                        task,
                        task,
                        task);
    }
  else if (nvt)
    {
      gchar *owned_clause;

      owned_clause = where_owned_for_get ("override", NULL);

      result_clause = g_strdup_printf
                       (" AND (overrides.nvt"
                        "      = (SELECT oid FROM nvts WHERE nvts.id = %llu))"
                        " AND %s",
                        nvt,
                        owned_clause);

      g_free (owned_clause);
    }
  else
    result_clause = NULL;

  ret = count ("override",
               get,
               columns,
               trash_columns,
               filter_columns,
               task || nvt,
               NULL,
               result_clause,
               TRUE);

  g_free (result_clause);

  return ret;
}

/**
 * @brief Initialise an override iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 * @param[in]  result      Result to limit overrides to, 0 for all.
 * @param[in]  task        If result is > 0, task whose overrides on result to
 *                         include, otherwise task to limit overrides to.  0 for
 *                         all tasks.
 * @param[in]  nvt         NVT to limit overrides to, 0 for all.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_override_iterator (iterator_t* iterator, const get_data_t *get, nvt_t nvt,
                        result_t result, task_t task)
{
  static const char *filter_columns[] = OVERRIDE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OVERRIDE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = OVERRIDE_ITERATOR_TRASH_COLUMNS;
  gchar *result_clause, *filter, *task_id;
  int ret;

  assert (current_credentials.uuid);
  assert ((nvt && get->id) == 0);
  assert ((task && get->id) == 0);

  assert (result ? nvt == 0 : 1);
  assert (task ? nvt == 0 : 1);

  /* Treat the "task_id" filter keyword as if the task was given in "task". */

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  task_id = filter_term_value (filter ? filter : get->filter, "task_id");

  g_free (filter);

  if (task_id)
    {
      find_task (task_id, &task);
      g_free (task_id);
    }

  if (result)
    {
      gchar *severity_sql;

      if (setting_dynamic_severity_int ())
        severity_sql = g_strdup_printf ("(SELECT CASE"
                                        " WHEN results.severity"
                                        "      > " G_STRINGIFY (SEVERITY_LOG)
                                        " THEN nvts.cvss_base"
                                        " ELSE results.severity END"
                                        " FROM results, nvts"
                                        " WHERE (nvts.oid = results.nvt)"
                                        "   AND (results.id = %llu))",
                                        result);
      else
        severity_sql = g_strdup_printf ("(SELECT results.severity"
                                        " FROM results"
                                        " WHERE results.id = %llu)",
                                        result);

      result_clause = g_strdup_printf (" AND"
                                       " (result = %llu"
                                       "  OR (result = 0 AND nvt ="
                                       "      (SELECT results.nvt FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (hosts is NULL"
                                       "      OR hosts = ''"
                                       "      OR hosts_contains (hosts,"
                                       "      (SELECT results.host FROM results"
                                       "       WHERE results.id = %llu)))"
                                       " AND (port is NULL"
                                       "      OR port = ''"
                                       "      OR port ="
                                       "      (SELECT results.port FROM results"
                                       "       WHERE results.id = %llu))"
                                       " AND (severity_matches_ov (%s,"
                                       "                           severity))"
                                       " AND (task = 0 OR task = %llu)",
                                       result,
                                       result,
                                       result,
                                       result,
                                       severity_sql,
                                       task);

      g_free (severity_sql);
    }
  else if (task)
    {
      result_clause = g_strdup_printf
                       (" AND (overrides.task = %llu OR overrides.task = 0)"
                        " AND nvt IN"
                        " (SELECT DISTINCT nvt FROM results"
                        "  WHERE results.task = %llu)"
                        " AND (overrides.result = 0"
                        "      OR (SELECT task FROM results"
                        "          WHERE results.id = overrides.result)"
                        "         = %llu)",
                        task,
                        task,
                        task);
    }
  else if (nvt)
    {
      gchar *owned_clause;

      owned_clause = where_owned_for_get ("override", NULL);

      result_clause = g_strdup_printf
                       (" AND (overrides.nvt ="
                        " (SELECT oid FROM nvts WHERE nvts.id = %llu))"
                        " AND %s",
                        nvt,
                        owned_clause);

      g_free (owned_clause);
    }
  else
    result_clause = NULL;

  ret = init_get_iterator (iterator,
                           "override",
                           get,
                           columns,
                           trash_columns,
                           filter_columns,
                           task || nvt,
                           NULL,
                           result_clause,
                           TRUE);

  g_free (result_clause);

  return ret;
}

/**
 * @brief Get the NVT OID from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT OID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (override_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the text from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Text, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (override_iterator_text, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the hosts from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (override_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the port from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Port, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (override_iterator_port, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the threat from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Threat.
 */
const char *
override_iterator_threat (iterator_t *iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
  return ret;
}

/**
 * @brief Get the threat from an override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Threat.
 */
const char *
override_iterator_new_threat (iterator_t *iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
  return ret;
}

/**
 * @brief Get the task from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The task associated with the override, or 0 on error.
 */
task_t
override_iterator_task (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

/**
 * @brief Get the result from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The result associated with the override, or 0 on error.
 */
result_t
override_iterator_result (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (result_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7);
}

/**
 * @brief Get the end time from an override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time until which override applies.  0 for always.  1 means the
 *         override has been explicitly turned off.
 */
time_t
override_iterator_end_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
  return ret;
}

/**
 * @brief Get the active status from an override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if active, else 0.
 */
int
override_iterator_active (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
  return ret;
}

/**
 * @brief Get the NVT name from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (override_iterator_nvt_name, GET_ITERATOR_COLUMN_COUNT + 10);

/**
 * @brief Get the severity from an override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity score to which the override applies or NULL if
 *         iteration is complete, Freed by cleanup_iterator.
 */
DEF_ACCESS (override_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 14);

/**
 * @brief Get the new severity from an override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity score to override to or NULL if
 *         iteration is complete, Freed by cleanup_iterator.
 */
DEF_ACCESS (override_iterator_new_severity, GET_ITERATOR_COLUMN_COUNT + 15);


/* Scanners */

/**
 * @brief Create the given scanner.
 *
 * @param[in]  log_config       Log configuration.
 * @param[in]  database         Location of manage database.
 * @param[in]  name             Name of scanner.
 * @param[in]  host             Host of scanner.
 * @param[in]  port             Port of scanner.
 * @param[in]  type             Type of scanner.
 * @param[in]  ca_pub_path      CA Certificate path.
 * @param[in]  key_pub_path     Certificate path.
 * @param[in]  key_priv_path    Private key path.
 *
 * @return 0 success, -1 error, -2 database is wrong version, -3 database needs
 *         to be initialised from server.
 */
int
manage_create_scanner (GSList *log_config, const gchar *database,
                       const char *name, const char *host, const char *port,
                       const char *type, const char *ca_pub_path,
                       const char *key_pub_path, const char *key_priv_path)
{
  const gchar *db;
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);
  current_credentials.uuid = "";

  if (!g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error))
    {
      g_warning ("%s: %s\n", __FUNCTION__, error->message);
      g_error_free (error);
      return -1;
    }
  if (!g_file_get_contents (key_pub_path, &key_pub, NULL, &error))
    {
      g_warning ("%s: %s\n", __FUNCTION__, error->message);
      g_error_free (error);
      g_free (ca_pub);
      return -1;
    }
  if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error))
    {
      g_warning ("%s: %s\n", __FUNCTION__, error->message);
      g_error_free (error);
      g_free (ca_pub);
      g_free (key_pub);
      return -1;
    }
  ret = create_scanner (name, NULL, host, port, type, NULL, ca_pub, key_pub,
                        key_priv);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  switch (ret)
    {
      case 0:
        printf ("Scanner created.\n");
        break;
      case 1:
        printf ("Scanner exists already.\n");
        break;
      case 2:
        printf ("Invalid value provided.\n");
        break;
      default:
        printf ("Failed to create scanner.\n");
        break;
    }
  cleanup_manage_process (TRUE);

  return ret;
}

/**
 * @brief Delete the given scanner.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  uuid        UUID of scanner.
 *
 * @return 0 success, 2 failed to find scanner, 3 scanner can't be deleted,
 *         -1 error.  -2 database is wrong version, -3 database needs to be
 *         initialised from server.
 */
int
manage_delete_scanner (GSList *log_config, const gchar *database,
                       const gchar *uuid)
{
  const gchar *db;
  int ret;

  assert (uuid);
  if (!strcmp (uuid, SCANNER_UUID_DEFAULT))
    {
      printf ("Default OpenVAS Scanner can't be deleted.\n");
      return 3;
    }

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  current_credentials.uuid = "";
  switch ((ret = delete_scanner (uuid, 1)))
    {
      case 0:
        printf ("Scanner deleted.\n");
        break;
      case 1:
        printf ("Scanner in use.\n");
        break;
      case 2:
        printf ("Failed to find scanner.\n");
        break;
      default:
        printf ("Internal Error.\n");
        break;
    }
  current_credentials.uuid = NULL;

  cleanup_manage_process (TRUE);
  return ret;
}

/**
 * @brief Modify the given scanner.
 *
 * @param[in]  log_config       Log configuration.
 * @param[in]  database         Location of manage database.
 * @param[in]  name             Name of scanner.
 * @param[in]  host             Host of scanner.
 * @param[in]  port             Port of scanner.
 * @param[in]  type             Type of scanner.
 * @param[in]  ca_pub_path      CA Certificate path.
 * @param[in]  key_pub_path     Certificate path.
 * @param[in]  key_priv_path    Private key path.
 *
 * @return 0 success, , 1 failed to find scanner, 2 scanner with new name
 *         exists, 3 scanner_id required, 4 invalid value, 99 permission
 *         denied, -1 error, -2 database is wrong version, -3 database needs
 *         to be initialised from server.
 */
int
manage_modify_scanner (GSList *log_config, const gchar *database,
                       const char *scanner_id, const char *name,
                       const char *host, const char *port,
                       const char *type, const char *ca_pub_path,
                       const char *key_pub_path, const char *key_priv_path)
{
  const gchar *db;
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);
  current_credentials.uuid = "";

  if (ca_pub_path)
    {
      if (g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error) == 0)
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          return -1;
        }
    }
  else
    ca_pub = NULL;
  if (key_pub_path)
    {
      if (g_file_get_contents (key_pub_path, &key_pub, NULL, &error) == 0)
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          g_free (ca_pub);
          return -1;
        }
    }
  else
    key_pub = NULL;
  if (key_priv_path)
    {
      if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error))
        {
          g_warning ("%s: %s\n", __FUNCTION__, error->message);
          g_error_free (error);
          g_free (ca_pub);
          g_free (key_pub);
          return -1;
        }
    }
  else
    key_priv = NULL;
  ret = modify_scanner (scanner_id, name, NULL, host, port, type, ca_pub,
                        key_pub, key_priv);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  switch (ret)
    {
      case 0:
        printf ("Scanner modified.\n");
        break;
      case 2:
        printf ("Scanner with new name exists already.\n");
        break;
      case 3:
        printf ("Scanner ID required.\n");
        break;
      case 4:
        printf ("Invalid value.\n");
        break;
      case 99:
        printf ("Permission denied.\n");
        break;
      default:
        printf ("Failed to modify scanner.\n");
        break;
    }
  cleanup_manage_process (TRUE);

  return ret;
}

/**
 * @brief Verify the given scanner.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  uuid        UUID of scanner.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to verify scanner,
 *         -1 error.  -2 database is wrong version, -3 database needs to be
 *         initialised from server.
 */
int
manage_verify_scanner (GSList *log_config, const gchar *database,
                       const gchar *uuid)
{
  const gchar *db;
  int ret;
  char *version;

  assert (uuid);

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;
  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  current_credentials.uuid = "";
  switch ((ret = verify_scanner (uuid, &version)))
    {
      case 0:
        printf ("Scanner version: %s.\n", version);
        g_free (version);
        break;
      case 1:
        printf ("Failed to find scanner.\n");
        break;
      case 2:
        printf ("Failed to verify scanner.\n");
        break;
      default:
        printf ("Internal Error.\n");
        break;
    }
  current_credentials.uuid = NULL;

  cleanup_manage_process (TRUE);
  return ret;
}

/**
 * @brief Find a scanner given a UUID.
 *
 * @param[in]   uuid    UUID of scanner.
 * @param[out]  scanner Scanner return, 0 if succesfully failed to find scanner.
 *
 * @return FALSE on success (including if failed to find scanner), TRUE on error.
 */
gboolean
find_scanner (const char* uuid, scanner_t *scanner)
{
  return find_resource ("scanner", uuid, scanner);
}

/**
 * @brief Find a scanner for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of scanner.
 * @param[out]  scanner     Scanner return, 0 if succesfully failed to find
 *                          scanner.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find scanner),
 *         TRUE on error.
 */
gboolean
find_scanner_with_permission (const char* uuid, scanner_t* scanner,
                              const char *permission)
{
  return find_resource_with_permission ("scanner", uuid, scanner, permission,
                                        0);
}

/**
 * @brief Create a scanner.
 *
 * @param[in]   name        Name of scanner.
 * @param[in]   comment     Comment on scanner.
 * @param[in]   host        Host of scanner.
 * @param[in]   port        Port of scanner.
 * @param[in]   type        Type of scanner.
 * @param[in]   ca_cert     CA Certificate for scanner.
 * @param[in]   key_pub     Certificate for scanner.
 * @param[in]   key_priv    Private key for scanner.
 *
 * @return 0 success, 1 scanner exists already, 2 Invalid value,
 *         99 permission denied.
 */
int
create_scanner (const char* name, const char *comment, const char *host,
                const char *port, const char *type, scanner_t *new_scanner,
                const char *ca_pub, const char *key_pub, const char *key_priv)
{
  char *quoted_name, *quoted_comment, *quoted_host, *quoted_ca_pub;
  char *quoted_key_pub, *quoted_key_priv;
  int iport, itype;

  assert (current_credentials.uuid);
  assert (name);

  sql_begin_immediate ();

  if (user_may ("create_scanner") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (!host || !port || !type || !ca_pub || !key_pub || !key_priv)
    return 2;
  iport = atoi (port);
  itype = atoi (type);
  if (iport <= 0 || iport > 65535)
    return 2;
  if (itype <= SCANNER_TYPE_NONE || itype >= SCANNER_TYPE_MAX)
    return 2;
  if (openvas_get_host_type (host) == -1)
    return 2;
  if (resource_with_name_exists (name, "scanner", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");
  quoted_host = sql_quote (host ?: "");
  quoted_ca_pub = sql_quote (ca_pub ?: "");
  quoted_key_pub = sql_quote (key_pub ?: "");
  quoted_key_priv = sql_quote (key_priv ?: "");
  sql ("INSERT INTO scanners (uuid, name, owner, comment, host, port, type,"
       "                      ca_pub, key_pub, key_priv,"
       "                      creation_time, modification_time)"
       " VALUES (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '%s', %d, %d, '%s', '%s', '%s', m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment, quoted_host,
       iport, itype, quoted_ca_pub, quoted_key_pub, quoted_key_priv);

  if (new_scanner)
    *new_scanner = sql_last_insert_id ();

  sql ("COMMIT;");
  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_ca_pub);
  g_free (quoted_key_pub);
  g_free (quoted_key_priv);
  return 0;
}

/**
 * @brief Create a cannerchedule from an existing cannerchedule.
 *
 * @param[in]  name         Name of new scanner. NULL to copy from existing.
 * @param[in]  comment      Comment on new scanner. NULL to copy from
 *                          existing.
 * @param[in]  scanner_id   UUID of existing scanner.
 * @param[out] new_scanner  New scanner.
 *
 * @return 0 success, 1 scanner exists already, 2 failed to find existing
 *         scanner, -1 error.
 */
int
copy_scanner (const char* name, const char* comment, const char *scanner_id,
              scanner_t* new_scanner)
{
  return copy_resource ("scanner", name, comment, scanner_id,
                        "host, port, type, ca_pub, key_pub, key_priv", 1,
                        new_scanner);
}

/**
 * @brief Modify an scanner.
 *
 * @param[in]   scanner_id  UUID of scanner.
 * @param[in]   name        Name of scanner.
 * @param[in]   comment     Comment on scanner.
 * @param[in]   host        Host of scanner.
 * @param[in]   port        Port of scanner.
 * @param[in]   type        Type of scanner.
 * @param[in]   ca_cert     CA Certificate of scanner or NULL.
 * @param[in]   key_pub     Certificate of scanner or NULL.
 * @param[in]   key_priv    Private key of scanner or NULL.
 *
 * @return 0 success, 1 failed to find scanner, 2 scanner with new name exists,
 *         3 scanner_id required, 4 invalid value, 99 permission denied, -1
 *         internal error.
 */
int
modify_scanner (const char *scanner_id, const char *name, const char *comment,
                const char *host, const char *port, const char *type,
                const char *ca_pub, const char *key_pub, const char *key_priv)
{
  gchar *quoted_name, *quoted_comment, *quoted_host, *new_port, *new_type;
  scanner_t scanner = 0;
  int iport, itype;

  assert (current_credentials.uuid);

  if (scanner_id == NULL)
    return 3;

  if (port)
    {
      iport = atoi (port);
      if (iport <= 0 || iport > 65535)
        return 4;
    }
  else
    /* Keep compiler quiet. */
    iport = 0;

  if (type)
    {
      itype = atoi (type);
      if (itype <= SCANNER_TYPE_NONE || itype >= SCANNER_TYPE_MAX)
        return 4;
    }
  else
    /* Keep compiler quiet. */
    itype = 0;

  if (host && (openvas_get_host_type (host) == -1))
    return 4;

  sql_begin_immediate ();

  if (user_may ("modify_scanner") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_scanner_with_permission (scanner_id, &scanner, "modify_scanner"))
    {
      sql ("ROLLBACK;");
      return -1;
    }
  if (scanner == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a scanner with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "scanner", scanner))
        {
          sql ("ROLLBACK;");
          return 2;
        }
    }

  quoted_name = name ? sql_quote (name) : NULL;
  quoted_comment = sql_quote (comment ?: "");
  quoted_host = host ? sql_quote (host) : NULL;
  new_port = port ? g_strdup_printf ("%d", iport) : g_strdup ("port");
  new_type = type ? g_strdup_printf ("%d", itype) : g_strdup ("type");
  sql ("UPDATE scanners SET name = %s%s%s, comment = %s%s%s, host = %s%s%s,"
       " port = %s, type = %s, modification_time = m_now () WHERE id = %llu;",
       quoted_name ? "'" : "",
       quoted_name ? quoted_name : "name",
       quoted_name ? "'" : "",
       quoted_comment ? "'" : "",
       quoted_comment ? quoted_comment : "comment",
       quoted_comment ? "'" : "",
       quoted_host ? "'" : "",
       quoted_host ? quoted_host : "host",
       quoted_host ? "'" : "",
       new_port,
       new_type,
       scanner);
  g_free (new_type);
  g_free (new_port);
  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);

  if (ca_pub && *ca_pub)
    {
      char *quoted_ca_pub = sql_quote (ca_pub);
      sql ("UPDATE scanners SET ca_pub = '%s' WHERE id = %llu;", quoted_ca_pub,
           scanner);
      g_free (quoted_ca_pub);
    }
  if (key_pub && *key_pub)
    {
      char *quoted_key_pub = sql_quote (key_pub);
      sql ("UPDATE scanners SET key_pub = '%s' WHERE id = %llu;",
           quoted_key_pub, scanner);
      g_free (quoted_key_pub);
    }
  if (key_priv && *key_priv)
    {
      char *quoted_key_priv = sql_quote (key_priv);
      sql ("UPDATE scanners SET key_priv = '%s' WHERE id = %llu;",
           quoted_key_priv, scanner);
      g_free (quoted_key_priv);
    }
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Delete a scanner.
 *
 * @param[in]  scanner_id   UUID of scanner.
 * @param[in]  ultimate     Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 scanner in use, 2 failed to find scanner, 99 permission
 *         denied, -1 error.
 */
int
delete_scanner (const char *scanner_id, int ultimate)
{
  scanner_t scanner = 0;

  sql_begin_immediate ();

  if (user_may ("delete_scanner") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_scanner_with_permission (scanner_id, &scanner, "delete_scanner"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (scanner == 0)
    {
      if (find_trash ("scanner", scanner_id, &scanner))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (scanner == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      permissions_set_orphans ("scanner", scanner, LOCATION_TRASH);
      tags_set_orphans ("scanner", scanner, LOCATION_TRASH);

      sql ("DELETE FROM scanners_trash WHERE id = %llu;", scanner);
      sql ("COMMIT;");
      return 0;
    }

  if (scanner_in_use (scanner))
    return 1;

  if (ultimate == 0)
    {
      sql ("INSERT INTO scanners_trash"
           " (uuid, owner, name, comment, host, port, type, ca_pub,"
           "  key_pub, key_priv, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, host, port, type, ca_pub,"
           "        key_pub, key_priv, creation_time, modification_time"
           " FROM scanners WHERE id = %llu;", scanner);

      permissions_set_locations ("scanner", scanner,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("scanner", scanner,
                          sql_last_insert_id (), LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("scanner", scanner, LOCATION_TABLE);
      tags_set_orphans ("scanner", scanner, LOCATION_TABLE);
    }

  sql ("DELETE FROM scanners WHERE id = %llu;", scanner);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Filter columns for scanner iterator.
 */
#define SCANNER_ITERATOR_FILTER_COLUMNS                              \
 { GET_ITERATOR_FILTER_COLUMNS, "host", "port", "type", NULL }

/**
 * @brief Scanner iterator columns.
 */
#define SCANNER_ITERATOR_COLUMNS                                     \
 {                                                                   \
   GET_ITERATOR_COLUMNS (scanners),                                  \
   { "host" , NULL },                                                \
   { "port" , NULL },                                                \
   { "type", NULL },                                                 \
   { "ca_pub", NULL },                                               \
   { "key_pub", NULL },                                              \
   { "key_priv", NULL },                                             \
   { NULL, NULL }                                                    \
 }

/**
 * @brief Scanner iterator columns for trash case.
 */
#define SCANNER_ITERATOR_TRASH_COLUMNS                               \
 {                                                                   \
   GET_ITERATOR_COLUMNS (scanners_trash),                            \
   { "host" , NULL },                                                \
   { "port" , NULL },                                                \
   { "type", NULL },                                                 \
   { NULL, NULL }                                                    \
 }

/**
 * @brief Initialise an scanner iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to find filter, -1 error.
 */
int
init_scanner_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = SCANNER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SCANNER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SCANNER_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator, "scanner", get, columns, trash_columns,
                            filter_columns, 0, NULL, NULL, TRUE);
}

/**
 * @brief Get the host from an scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Host, or NULL if iteration is complete.  Freed
 *         by cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_host, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the port from an scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Port, or -1 if iteration is complete.
 */
int
scanner_iterator_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  return ret;
}

/**
 * @brief Get the type from an scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type, or SCANNER_TYPE_NONE if iteration is complete.
 */
int
scanner_iterator_type (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return SCANNER_TYPE_NONE;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
  return ret;
}

/**
 * @brief Get the CA Certificate from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return CA Certificate, or NULL if iteration is complete. Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_ca_pub, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the Scanner Certificate from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Scanner Certificate, or NULL if iteration is complete. Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_key_pub, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the Scanner private key from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Scanner private key, or NULL if iteration is complete. Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_key_priv, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Initialise a scanner task iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  scanner   Scanner.
 */
void
init_scanner_task_iterator (iterator_t* iterator, scanner_t scanner)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (scanner);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT id, uuid, name, %s FROM tasks"
                 " WHERE scanner = %llu AND hidden = 0"
                 " ORDER BY name ASC;",
                 available,
                 scanner);

  g_free (available);
}

/**
 * @brief Get the UUID from a scanner task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (scanner_task_iterator_uuid, 1);

/**
 * @brief Get the name from a scanner task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (scanner_task_iterator_name, 2);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
scanner_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}

/**
 * @brief Check whether an scanner is in use.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
scanner_in_use (scanner_t scanner)
{
  return !!sql_int ("SELECT count(*) FROM tasks" " WHERE scanner = %llu;",
                    scanner);
}

/**
 * @brief Check whether a trashcan scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
trash_scanner_in_use (scanner_t scanner)
{
  return 0;
}

/**
 * @brief Check whether a scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
scanner_writable (scanner_t scanner)
{
  return (scanner_in_use (scanner) == 0);
}

/**
 * @brief Check whether a trashcan scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
trash_scanner_writable (scanner_t scanner)
{
  return (trash_scanner_in_use (scanner) == 0);
}

/**
 * @brief Return the name of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
scanner_name (scanner_t scanner)
{
  return sql_string ("SELECT name FROM scanners WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Return the UUID of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated UUID.
 */
char *
scanner_uuid (scanner_t scanner)
{
  return sql_string ("SELECT uuid FROM scanners WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Return the host of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated host.
 */
char *
scanner_host (scanner_t scanner)
{
  return sql_string ("SELECT host FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Return the port of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Scanner port, -1 if not found;
 */
int
scanner_port (scanner_t scanner)
{
  int port;
  char *str;
  str = sql_string ("SELECT port FROM scanners WHERE id = %llu;", scanner);
  if (!str)
    return -1;
  port = atoi (str);
  g_free (str);
  return port;
}

/**
 * @brief Return the type of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Scanner type, -1 if not found;
 */
int
scanner_type (scanner_t scanner)
{
  int type;
  char *str;
  str = sql_string ("SELECT type FROM scanners WHERE id = %llu;", scanner);
  if (!str)
    return -1;
  type = atoi (str);
  g_free (str);
  return type;
}

/**
 * @brief Return the CA Certificate of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated CA Certificate.
 */
char *
scanner_ca_pub (scanner_t scanner)
{
  return sql_string ("SELECT ca_pub FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Return the Certificate of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated Certificate.
 */
char *
scanner_key_pub (scanner_t scanner)
{
  return sql_string ("SELECT key_pub FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Return the private key of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated private key.
 */
char *
scanner_key_priv (scanner_t scanner)
{
  return sql_string ("SELECT key_priv FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Count number of scanners.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of scanners in filtered set.
 */
int
scanner_count (const get_data_t *get)
{
  static const char *extra_columns[] = SCANNER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SCANNER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SCANNER_ITERATOR_TRASH_COLUMNS;

  return count ("scanner", get, columns, trash_columns, extra_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Create a new connection to an OSP scanner.
 *
 * @param[in]   scanner     Scanner.
 *
 * @return New connection if success, NULL otherwise.
 */
osp_connection_t *
osp_scanner_connect (scanner_t scanner)
{
  int port;
  osp_connection_t *connection;
  char *host, *ca_pub, *key_pub, *key_priv;

  assert (scanner);
  host = scanner_host (scanner);
  port = scanner_port (scanner);
  ca_pub = scanner_ca_pub (scanner);
  key_pub = scanner_key_pub (scanner);
  key_priv = scanner_key_priv (scanner);
  connection = osp_connection_new (host, port, ca_pub, key_pub, key_priv);

  g_free (host);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  return connection;
}

/**
 * @brief Get an OSP Scanner's get_version info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  s_name      Scanner name.
 * @param[out]  s_ver       Scanner version.
 * @param[out]  d_name      Daemon name.
 * @param[out]  d_name      Daemon version.
 * @param[out]  p_name      Protocol name.
 * @param[out]  p_name      Protocol version.
 *
 * @return 0 success, 1 for failure.
 */
int
osp_get_version_from_iterator (iterator_t *iterator, char **s_name,
                               char **s_ver, char **d_name, char **d_ver,
                               char **p_name, char **p_ver)
{
  osp_connection_t *connection;

  assert (iterator);
  connection = osp_connection_new (scanner_iterator_host (iterator),
                                   scanner_iterator_port (iterator),
                                   scanner_iterator_ca_pub (iterator),
                                   scanner_iterator_key_pub (iterator),
                                   scanner_iterator_key_priv (iterator));
  if (!connection)
    return 1;
  if (osp_get_version (connection, s_name, s_ver, d_name, d_ver, p_name, p_ver))
    return 1;
  osp_connection_close (connection);
  return 0;
}

/**
 * @brief Get an OSP Scanner's get_scanner_details info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  desc        Scanner description.
 * @param[out]  params      Scanner parameters.
 *
 * @return 0 success, 1 for failure.
 */
int
osp_get_details_from_iterator (iterator_t *iterator, char **desc,
                               GSList **params)
{
  osp_connection_t *connection;

  assert (iterator);
  connection = osp_connection_new (scanner_iterator_host (iterator),
                                   scanner_iterator_port (iterator),
                                   scanner_iterator_ca_pub (iterator),
                                   scanner_iterator_key_pub (iterator),
                                   scanner_iterator_key_priv (iterator));
  if (!connection)
    return 1;
  if (osp_get_scanner_details (connection, desc, params))
    return 1;
  osp_connection_close (connection);
  return 0;
}

/**
 * @brief Verify a scanner.
 *
 * @param[in]   scanner_id  Scanner UUID.
 * @param[out]  version     Version returned by the scanner.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to get version,
 *         99 if permission denied, -1 error.
 */
int
verify_scanner (const char *scanner_id, char **version)
{
  get_data_t get;
  iterator_t scanner;

  if (user_may ("verify_scanner") == 0)
    return 99;
  memset (&get, '\0', sizeof (get));
  get.id = g_strdup (scanner_id);
  if (init_scanner_iterator (&scanner, &get) || !next (&scanner))
    {
      g_free (get.id);
      return 1;
    }
  g_free (get.id);
  if (scanner_iterator_type (&scanner) == SCANNER_TYPE_OSP)
    {
      int ret = osp_get_version_from_iterator (&scanner, NULL, version, NULL,
                                               NULL, NULL, NULL);
      cleanup_iterator (&scanner);
      if (ret)
        return 2;
      return 0;
    }
  else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_OPENVAS)
    {
      int ret = openvas_scanner_set_address (scanner_iterator_host (&scanner),
                                             scanner_iterator_port (&scanner));
      openvas_scanner_set_certs (scanner_iterator_ca_pub (&scanner),
                                 scanner_iterator_key_pub (&scanner),
                                 scanner_iterator_key_priv (&scanner));
      cleanup_iterator (&scanner);
      if (ret == -1)
        return 2;
      if (openvas_scanner_connected ())
        openvas_scanner_close ();
      if (openvas_scanner_connect () || openvas_scanner_init (0)
          || openvas_scanner_close ())
        return 2;
      if (version)
        *version = g_strdup ("OTP/2.0");
      return 0;
    }
  assert (0);
  return -1;
}


/* Schedules. */

/**
 * @brief Find a schedule for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of schedule.
 * @param[out]  schedule    Schedule return, 0 if succesfully failed to find schedule.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find schedule), TRUE on error.
 */
gboolean
find_schedule_with_permission (const char* uuid, schedule_t* schedule,
                             const char *permission)
{
  return find_resource_with_permission ("schedule", uuid, schedule, permission, 0);
}

/**
 * @brief Create a schedule.
 *
 * @param[in]   name        Name of schedule.
 * @param[in]   comment     Comment on schedule.
 * @param[in]   first_time  First time action will run.
 * @param[in]   period      How often the action will run in seconds.  0 means
 *                          once.
 * @param[in]   period_months  The months part of the period.
 * @param[in]   duration    The length of the time window the action will run
 *                          in.  0 means entire duration of action.
 * @param[out]  timezone    Timezone.
 * @param[out]  schedule    Created schedule.
 *
 * @return 0 success, 1 schedule exists already, 99 permission denied.
 */
int
create_schedule (const char* name, const char *comment, time_t first_time,
                 time_t period, time_t period_months, time_t duration,
                 const char* timezone, schedule_t *schedule)
{
  gchar *quoted_name, *quoted_timezone;
  gchar *insert_timezone;
  long offset;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (user_may ("create_schedule") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }


  if (resource_with_name_exists (name, "schedule", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  quoted_name = sql_quote (name);

  if (timezone && strcmp (timezone, ""))
    insert_timezone = g_strdup (timezone);
  else
    insert_timezone = sql_string ("SELECT timezone FROM users"
                                  " WHERE users.uuid = '%s';",
                                  current_credentials.uuid);

  if (insert_timezone == NULL)
    insert_timezone = g_strdup ("UTC");
  else
    {
      insert_timezone = g_strstrip (insert_timezone);
      if (strcmp (insert_timezone, "") == 0)
        {
          g_free (insert_timezone);
          insert_timezone = g_strdup ("UTC");
        }
    }

  offset = current_offset (insert_timezone);
  quoted_timezone = sql_quote (insert_timezone);

  if (comment)
    {
      gchar *quoted_comment = sql_nquote (comment, strlen (comment));
      sql ("INSERT INTO schedules"
           " (uuid, name, owner, comment, first_time, period, period_months,"
           "  duration, timezone, initial_offset, creation_time,"
           "  modification_time)"
           " VALUES"
           " (make_uuid (), '%s',"
           "  (SELECT id FROM users WHERE users.uuid = '%s'),"
           "  '%s', %i, %i, %i, %i,"
           "  '%s',"
           "  %li, m_now (), m_now ());",
           quoted_name, current_credentials.uuid, quoted_comment, first_time,
           period, period_months, duration, quoted_timezone, offset);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO schedules"
         " (uuid, name, owner, comment, first_time, period, period_months,"
         "  duration, timezone, initial_offset, creation_time,"
         "  modification_time)"
         " VALUES"
         " (make_uuid (), '%s',"
         "  (SELECT id FROM users WHERE users.uuid = '%s'),"
         "  '', %i, %i, %i, %i,"
         "  '%s',"
         "  %li, m_now (), m_now ());",
         quoted_name, current_credentials.uuid, first_time, period,
         period_months, duration, quoted_timezone, offset);

  if (schedule)
    *schedule = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (insert_timezone);
  g_free (quoted_timezone);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a schedule from an existing schedule.
 *
 * @param[in]  name          Name of new schedule. NULL to copy from existing.
 * @param[in]  comment       Comment on new schedule. NULL to copy from
 *                           existing.
 * @param[in]  schedule_id   UUID of existing schedule.
 * @param[out] new_schedule  New schedule.
 *
 * @return 0 success, 1 schedule exists already, 2 failed to find existing
 *         schedule, -1 error.
 */
int
copy_schedule (const char* name, const char* comment, const char *schedule_id,
               schedule_t* new_schedule)
{
  return copy_resource ("schedule", name, comment, schedule_id,
                        "first_time, period, period_months, duration,"
                        " timezone, initial_offset",
                        1, new_schedule);
}

/**
 * @brief Delete a schedule.
 *
 * @param[in]  schedule_id  Schedule.
 * @param[in]  ultimate     Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the schedule,
 *         99 permission denied, -1 error.
 */
int
delete_schedule (const char *schedule_id, int ultimate)
{
  schedule_t schedule = 0;

  sql_begin_immediate ();

  if (user_may ("delete_schedule") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_schedule_with_permission (schedule_id, &schedule, "delete_schedule"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (schedule == 0)
    {
      if (find_trash ("schedule", schedule_id, &schedule))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (schedule == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE schedule = %llu"
                   " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   schedule))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("schedule", schedule, LOCATION_TRASH);
      tags_set_orphans ("schedule", schedule, LOCATION_TRASH);

      sql ("DELETE FROM schedules_trash WHERE id = %llu;", schedule);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE schedule = %llu"
                   " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (hidden = 0 OR hidden = 1);",
                   schedule))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO schedules_trash"
           " (uuid, owner, name, comment, first_time, period, period_months,"
           "  duration, timezone, initial_offset, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, first_time, period, period_months,"
           "        duration, timezone, initial_offset, creation_time,"
           "        modification_time"
           " FROM schedules WHERE id = %llu;",
           schedule);

      /* Update the location of the schedule in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET schedule = %llu,"
           "     schedule_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE schedule = %llu"
           " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           schedule);

      permissions_set_locations ("schedule", schedule,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("schedule", schedule,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else if (sql_int ("SELECT count(*) FROM tasks"
                    " WHERE schedule = %llu"
                    " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE),
                    schedule))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  else
    {
      permissions_set_orphans ("schedule", schedule, LOCATION_TABLE);
      tags_set_orphans ("schedule", schedule, LOCATION_TABLE);
    }

  sql ("DELETE FROM schedules WHERE id = %llu;", schedule);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return whether a schedule is in use by a task.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return 1 if in use, else 0.
 */
int
schedule_in_use (schedule_t schedule)
{
  return !!sql_int ("SELECT count (*) FROM tasks WHERE schedule = %llu;",
                    schedule);
}

/**
 * @brief Return whether a trashcan schedule is in use by a task.
 *
 * @param[in]  schedule  schedule.
 *
 * @return 1 if in use, else 0.
 */
int
trash_schedule_in_use (schedule_t schedule)
{
  return !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE schedule = %llu"
                    " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH),
                    schedule);
}

/**
 * @brief Return whether a schedule is writable.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return 1 if writable, else 0.
 */
int
schedule_writable (schedule_t schedule)
{
  return (schedule_in_use (schedule) == 0);
}

/**
 * @brief Return whether a trashcan schedule is writable.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return 1 if writable, else 0.
 */
int
trash_schedule_writable (schedule_t schedule)
{
  return (trash_schedule_in_use (schedule) == 0);
}

/**
 * @brief Return whether a trashcan schedule is readable.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return 1 if readable, else 0.
 */
int
trash_schedule_readable (schedule_t schedule)
{
  char *uuid;
  schedule_t found;

  if (schedule == 0)
    return 0;
  uuid = schedule_uuid (schedule);
  if (find_trash ("schedule", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Return the UUID of a schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Newly allocated UUID.
 */
char *
schedule_uuid (schedule_t schedule)
{
  return sql_string ("SELECT uuid FROM schedules WHERE id = %llu;",
                     schedule);
}

/**
 * @brief Return the name of a schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Newly allocated name.
 */
char *
schedule_name (schedule_t schedule)
{
  return sql_string ("SELECT name FROM schedules WHERE id = %llu;",
                     schedule);
}

/**
 * @brief Return the period of a schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Period in seconds.
 */
int
schedule_period (schedule_t schedule)
{
  return sql_int ("SELECT period FROM schedules WHERE id = %llu;",
                  schedule);
}

/**
 * @brief Return the period of a schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Period in seconds.
 */
int
schedule_duration (schedule_t schedule)
{
  return sql_int ("SELECT duration FROM schedules WHERE id = %llu;",
                  schedule);
}

/**
 * @brief Filter columns for schedule iterator.
 */
#define SCHEDULE_ITERATOR_FILTER_COLUMNS                                      \
 { GET_ITERATOR_FILTER_COLUMNS, "first_time", "period", "period_months",      \
   "duration", "timezone", "initial_offset", "first_run", "next_run", NULL }

/**
 * @brief Schedule iterator columns.
 */
#define SCHEDULE_ITERATOR_COLUMNS                                          \
 {                                                                         \
   GET_ITERATOR_COLUMNS (schedules),                                       \
   { "first_time", NULL },                                                 \
   { "period", NULL },                                                     \
   { "period_months", NULL },                                              \
   { "duration", NULL },                                                   \
   { "timezone", NULL },                                                   \
   { "initial_offset", NULL },                                             \
   { "next_time (first_time, period, period_months)", "next_run" },        \
   { "first_time", "first_run" },                                          \
   { NULL, NULL }                                                          \
 }

/**
 * @brief Schedule iterator columns for trash case.
 */
#define SCHEDULE_ITERATOR_TRASH_COLUMNS                                    \
 {                                                                         \
   GET_ITERATOR_COLUMNS (schedules_trash),                                 \
   { "first_time", NULL },                                                 \
   { "period", NULL },                                                     \
   { "period_months", NULL },                                              \
   { "duration", NULL },                                                   \
   { "timezone", NULL },                                                   \
   { "initial_offset", NULL },                                             \
   { NULL, NULL }                                                          \
 }

/**
 * @brief Count the number of schedules.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of schedules filtered set.
 */
int
schedule_count (const get_data_t *get)
{
  static const char *filter_columns[] = SCHEDULE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SCHEDULE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SCHEDULE_ITERATOR_TRASH_COLUMNS;
  return count ("schedule", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find filter, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_schedule_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = SCHEDULE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SCHEDULE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SCHEDULE_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "schedule",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the first time from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return First time of schedule.
 */
time_t
schedule_iterator_first_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT);
  return ret;
}

/**
 * @brief Get the next time a schedule could be schedulable.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Next time an action associated with schedule could be run.
 */
time_t
schedule_iterator_next_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
  return ret;
}

/**
 * @brief Get the period from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Period of schedule.
 */
time_t
schedule_iterator_period (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
  return ret;
}

/**
 * @brief Get the period months from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Period of schedule (months).
 */
time_t
schedule_iterator_period_months (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
  return ret;
}

/**
 * @brief Get the duration from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Duration of schedule.
 */
time_t
schedule_iterator_duration (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
  return ret;
}

/**
 * @brief Get the timezone from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Timezone, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (schedule_iterator_timezone, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the initial offset from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Initial offset, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
time_t
schedule_iterator_initial_offset (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
  return ret;
}

/**
 * @brief Initialise a task schedule iterator.
 *
 * Lock the database before initialising.
 *
 * @param[in]  iterator        Iterator.
 *
 * @return 0 success, 1 failed to get lock, -1 error.
 */
int
init_task_schedule_iterator (iterator_t* iterator)
{
  int ret;
  gchar *task_clause, *schedule_clause;
  get_data_t get;
  array_t *permissions;

  ret = sql_begin_exclusive_giveup ();
  if (ret)
    return ret;

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("start_task"));
  task_clause = where_owned_user ("", "users.id", "task", &get, 1,
                                  "any", 0, permissions);

  permissions = make_array ();
  array_add (permissions, g_strdup ("get_schedules"));
  schedule_clause = where_owned_user ("", "users.id", "schedule", &get, 1,
                                      "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT tasks.id, tasks.uuid,"
                 " schedules.id, tasks.schedule_next_time,"
                 " schedules.period, schedules.period_months,"
                 " schedules.first_time,"
                 " schedules.duration,"
                 " users.uuid, users.name, schedules.owner,"
                 " schedules.timezone, schedules.initial_offset"
                 " FROM tasks, schedules, users"
                 " WHERE tasks.schedule = schedules.id"
                 " AND tasks.hidden = 0"
                 /* And a user may run the task. */
                 " AND %s"
                 /* And the same user has access to the schedule. */
                 " AND %s"
                 /* Sort by task and prefer owner of task or schedule as user */
                 " ORDER BY tasks.id,"
                 "          (users.id = tasks.owner) DESC,"
                 "          (users.id = schedules.owner) DESC;",
                 task_clause,
                 schedule_clause);

  g_free (task_clause);
  g_free (schedule_clause);
  return 0;
}

/**
 * @brief Cleanup a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 */
void
cleanup_task_schedule_iterator (iterator_t* iterator)
{
  cleanup_iterator (iterator);
  sql ("COMMIT;");
}

/**
 * @brief Get the task from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return task.
 */
task_t
task_schedule_iterator_task (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (task_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the task UUID from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_schedule_iterator_task_uuid, 1);

/**
 * @brief Get the next time from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Next time.
 */
time_t
task_schedule_iterator_next_time (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 3);
}

/**
 * @brief Get the period from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return period.
 */
time_t
task_schedule_iterator_period (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 4);
}

/**
 * @brief Get the period months from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Period months.
 */
time_t
task_schedule_iterator_period_months (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 5);
}

/**
 * @brief Get the first time from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return First time.
 */
time_t
task_schedule_iterator_first_time (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 6);
}

/**
 * @brief Get the duration from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Duration.
 */
time_t
task_schedule_iterator_duration (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 7);
}

/**
 * @brief Get the task owner uuid from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Owner UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_schedule_iterator_owner_uuid, 8);

/**
 * @brief Get the task owner name from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Owner name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_schedule_iterator_owner_name, 9);

/**
 * @brief Get the schedule owner from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Owner name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_schedule_iterator_owner, 10);

/**
 * @brief Get the timezone from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Timezone, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (task_schedule_iterator_timezone, 11);

/**
 * @brief Get the initial offset from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Initial offset.
 */
time_t
task_schedule_iterator_initial_offset (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 12);
}

/**
 * @brief Get the start due state from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Start due flag.
 */
gboolean
task_schedule_iterator_start_due (iterator_t* iterator)
{
  task_status_t run_status;
  time_t start_time;

  if (iterator->done) return FALSE;

  if (task_schedule_iterator_next_time (iterator) == 0)
    return FALSE;

  run_status = task_run_status (task_schedule_iterator_task (iterator));
  start_time = task_schedule_iterator_next_time (iterator)
                + task_schedule_iterator_initial_offset (iterator)
                - current_offset (task_schedule_iterator_timezone (iterator));

  if ((run_status == TASK_STATUS_DONE
       || run_status == TASK_STATUS_INTERNAL_ERROR
       || run_status == TASK_STATUS_NEW
       || run_status == TASK_STATUS_STOPPED)
      && (start_time > 0)
      && (start_time <= time (NULL)))
    return TRUE;

  return FALSE;
}

/**
 * @brief Get the stop due state from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Stop due flag.
 */
gboolean
task_schedule_iterator_stop_due (iterator_t* iterator)
{
  time_t period, period_months, duration;

  if (iterator->done) return FALSE;

  period = task_schedule_iterator_period (iterator);
  period_months = task_schedule_iterator_period_months (iterator);
  duration = task_schedule_iterator_duration (iterator);

  if (period && duration)
    {
      task_status_t run_status;

      run_status = task_run_status (task_schedule_iterator_task (iterator));

      if (run_status == TASK_STATUS_RUNNING
          || run_status == TASK_STATUS_REQUESTED)
        {
          time_t now, first, start;

          now = time (NULL);
          first = task_schedule_iterator_first_time (iterator)
                   + task_schedule_iterator_initial_offset (iterator)
                   - current_offset (task_schedule_iterator_timezone
                                      (iterator));
          start = first + (((now - first) / period) * period);
          if ((start + duration) < now)
            return TRUE;
        }
    }
  else if (period_months && duration)
    {
      task_status_t run_status;

      run_status = task_run_status (task_schedule_iterator_task (iterator));

      if (run_status == TASK_STATUS_RUNNING
          || run_status == TASK_STATUS_REQUESTED)
        {
          time_t now, first, start;

          now = time (NULL);
          first = task_schedule_iterator_first_time (iterator)
                   + task_schedule_iterator_initial_offset (iterator)
                   - current_offset (task_schedule_iterator_timezone
                                      (iterator));
          start = add_months (first, months_between (first, now));
          if ((start + duration) < now)
            return TRUE;
        }
    }
  else if (duration)
    {
      task_status_t run_status;

      run_status = task_run_status (task_schedule_iterator_task (iterator));

      if (run_status == TASK_STATUS_RUNNING
          || run_status == TASK_STATUS_REQUESTED)
        {
          time_t now, start;

          now = time (NULL);
          start = task_schedule_iterator_first_time (iterator)
                   + task_schedule_iterator_initial_offset (iterator)
                   - current_offset (task_schedule_iterator_timezone
                                      (iterator));
          if ((start + duration) < now)
            return TRUE;
        }
    }

  return FALSE;
}

/**
 * @brief Initialise a schedule task iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  schedule  Schedule.
 */
void
init_schedule_task_iterator (iterator_t* iterator, schedule_t schedule)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (current_credentials.uuid);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);
  init_iterator (iterator,
                 "SELECT id, uuid, name, %s FROM tasks"
                 " WHERE schedule = %llu AND hidden = 0"
                 " ORDER BY name ASC;",
                 available,
                 schedule,
                 current_credentials.uuid);
  g_free (available);
}

/**
 * @brief Get the UUID from a schedule task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (schedule_task_iterator_uuid, 1);

/**
 * @brief Get the name from a schedule task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (schedule_task_iterator_name, 2);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
schedule_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 3);
}

/**
 * @brief Modify a schedule.
 *
 * @param[in]   schedule_id  UUID of schedule.
 * @param[in]   name         Name of schedule.
 * @param[in]   comment      Comment on schedule.
 * @param[in]   first_time   First time action will run.
 * @param[in]   period       How often the action will run in seconds.  0 means
 *                           once.
 * @param[in]   period_months  The months part of the period.
 * @param[in]   duration     The length of the time window the action will run
 *                           in.  0 means entire duration of action.
 * @param[in]   timezone     Timezone.
 *
 * @return 0 success, 1 failed to find schedule, 2 schedule with new name exists,
 *         3 error in type name, 4 schedule_id required, 99 permission denied,
 *         -1 internal error.
 */
int
modify_schedule (const char *schedule_id, const char *name, const char *comment,
                 time_t first_time, time_t period, time_t period_months,
                 time_t duration, const char *timezone)
{
  gchar *quoted_name, *quoted_comment, *quoted_timezone;
  gchar *first_time_string, *duration_string, *period_string;
  gchar *period_months_string, *offset_string;
  schedule_t schedule;

  if (schedule_id == NULL)
    return 4;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_schedule") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  schedule = 0;
  if (find_schedule_with_permission (schedule_id, &schedule, "modify_schedule"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (schedule == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a schedule with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "schedule", schedule))
        {
          sql ("ROLLBACK;");
          return 2;
        }
      quoted_name = sql_quote (name);
    }
  else
    quoted_name = NULL;

  quoted_comment = comment ? sql_quote (comment) : NULL;

  if (duration == -1)
    duration_string = NULL;
  else
    duration_string = g_strdup_printf ("%li", duration);

  if (first_time == -1)
    first_time_string = NULL;
  else
    first_time_string = g_strdup_printf ("%li", first_time);

  if ((period_months == -1) || (period_months == 0))
    {
      if (period == -1)
        {
          period_months_string = NULL;
          period_string = NULL;
        }
      else
        {
          period_months_string = g_strdup ("0");
          period_string = g_strdup_printf ("%li", period);
        }
    }
  else
    {
      period_months_string = g_strdup_printf ("%li", period_months);
      period_string = g_strdup ("0");
    }

  if (first_time)
    {
      if (timezone)
        offset_string = g_strdup_printf ("%li", current_offset (timezone));
      else
        offset_string = NULL;
    }
  else
    offset_string = NULL;

  quoted_timezone = timezone ? sql_quote (timezone) : NULL;

  sql ("UPDATE schedules SET"
       " name = %s%s%s,"
       " comment = %s%s%s,"
       " timezone = %s%s%s,"
       " first_time = %s,"
       " period = %s,"
       " period_months = %s,"
       " duration = %s,"
       " initial_offset = %s,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name ? "'" : "",
       quoted_name ? quoted_name : "name",
       quoted_name ? "'" : "",
       quoted_comment ? "'" : "",
       quoted_comment ? quoted_comment : "comment",
       quoted_comment ? "'" : "",
       quoted_timezone ? "'" : "",
       quoted_timezone ? quoted_timezone : "timezone",
       quoted_timezone ? "'" : "",
       first_time ? first_time_string : "first_time",
       period_string ? period_string : "period",
       period_months_string ? period_months_string : "period_months",
       (duration >= 0) ? duration_string : "duration",
       offset_string ? offset_string : "initial_offset",
       schedule);

  sql ("UPDATE tasks SET schedule_next_time = "
       " (SELECT first_time FROM schedules WHERE id = %llu)"
       " WHERE schedule = %llu;",
       schedule,
       schedule);

  g_free (duration_string);
  g_free (first_time_string);
  g_free (offset_string);
  g_free (period_string);
  g_free (period_months_string);
  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_timezone);

  sql ("COMMIT;");

  return 0;
}


/* Report Formats. */

/**
 * @brief Possible port types.
 */
typedef enum
{
  REPORT_FORMAT_FLAG_ACTIVE = 1
} report_format_flag_t;

/**
 * @brief Find a reportformat for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of report format.
 * @param[out]  report_format  Report format return, 0 if succesfully failed to
 *                             find report_format.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find report_format), TRUE
 *         on error.
 */
gboolean
find_report_format_with_permission (const char* uuid,
                                    report_format_t* report_format,
                                    const char *permission)
{
  return find_resource_with_permission ("report_format", uuid, report_format,
                                        permission, 0);
}

/**
 * @brief Find a report format given a name.
 *
 * @param[in]   name           Name of report_format.
 * @param[out]  report_format  Report format return, 0 if succesfully failed to
 *                             find report_format.
 *
 * @return FALSE on success (including if failed to find report format), TRUE
 *         on error.
 */
gboolean
lookup_report_format (const char* name, report_format_t* report_format)
{
  gchar *quoted_name;

  assert (current_credentials.uuid);

  if (user_owns_name ("report_format", name) == 0)
    {
      *report_format = 0;
      return FALSE;
    }
  quoted_name = sql_quote (name);
  switch (sql_int64 (report_format,
                     "SELECT id FROM report_formats"
                     " WHERE name = '%s'"
                     " AND ((owner IS NULL) OR (owner ="
                     " (SELECT users.id FROM users WHERE users.uuid = '%s')));",
                     quoted_name,
                     current_credentials.uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *report_format = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_name);
        return TRUE;
        break;
    }

  g_free (quoted_name);
  return FALSE;
}

/**
 * @brief Compare files for create_report_format.
 *
 * @param[in]  one  First.
 * @param[in]  two  Second.
 */
static gint
compare_files (gconstpointer one, gconstpointer two)
{
  gchar *file_one, *file_two;
  file_one = *((gchar**) one);
  file_two = *((gchar**) two);
  if (file_one == NULL)
    {
      if (file_two == NULL)
        return 0;
      return 1;
    }
  else if (file_two == NULL)
    return -1;
  return strcoll (file_one, file_two);
}

/**
 * @brief Create a report format.
 *
 * @param[in]   uuid           UUID of format.
 * @param[in]   name           Name of format.
 * @param[in]   content_type   Content type of format.
 * @param[in]   extension      File extension of format.
 * @param[in]   summary        Summary of format.
 * @param[in]   description    Description of format.
 * @param[in]   global         Whether the report is global.
 * @param[in]   files          Array of memory.  Each item is a file name
 *                             string, a terminating NULL, the file contents
 *                             in base64 and a terminating NULL.
 * @param[in]   params         Array of params.
 * @param[in]   params_options Array.  Each item is an array corresponding to
 *                             params.  Each item of an inner array is a string,
 *                             the text of an option in a selection.
 * @param[in]   signature      Signature.
 * @param[out]  report_format  Created report format.
 *
 * @return 0 success, 1 report format exists, 2 empty file name, 3 param value
 *         validation failed, 4 param value validation failed, 5 param default
 *         missing, 6 param min or max out of range, 7 param type missing,
 *         8 duplicate param name, 9 bogus param type name, 99 permission
 *         denied, -1 error.
 */
int
create_report_format (const char *uuid, const char *name,
                      const char *content_type, const char *extension,
                      const char *summary, const char *description, int global,
                      array_t *files, array_t *params, array_t *params_options,
                      const char *signature, report_format_t *report_format)
{
  gchar *quoted_name, *quoted_summary, *quoted_description, *quoted_extension;
  gchar *quoted_content_type, *quoted_signature, *file_name, *dir;
  gchar *candidate_name, *new_uuid, *uuid_actual;
  report_format_t report_format_rowid;
  int index, num;
  gchar *format_signature = NULL;
  gsize format_signature_size;
  int format_trust = TRUST_UNKNOWN;
  create_report_format_param_t *param;

  assert (current_credentials.uuid);
  assert (uuid);
  assert (name);
  assert (files);
  assert (params);

  /* Verify the signature. */

  if ((find_signature ("report_formats", uuid, &format_signature,
                       &format_signature_size, &uuid_actual)
       == 0)
      || signature)
    {
      char *locale;
      GString *format;

      format = g_string_new ("");

      g_string_append_printf (format,
                              "%s%s%s%i",
                              uuid_actual ? uuid_actual : uuid,
                              extension,
                              content_type,
                              global & 1);

      index = 0;
      locale = setlocale (LC_ALL, "C");
      g_ptr_array_sort (files, compare_files);
      setlocale (LC_ALL, locale);
      while ((file_name = (gchar*) g_ptr_array_index (files, index++)))
        g_string_append_printf (format,
                                "%s%s",
                                file_name,
                                file_name + strlen (file_name) + 1);

      index = 0;
      while ((param
               = (create_report_format_param_t*) g_ptr_array_index (params,
                                                                    index++)))
        {
          g_string_append_printf (format,
                                  "%s%s",
                                  param->name,
                                  param->type);

          if (param->type_min)
            {
              long long int min;
              min = strtoll (param->type_min, NULL, 0);
              if (min == LLONG_MIN)
                return 6;
              g_string_append_printf (format, "%lli", min);
            }

          if (param->type_max)
            {
              long long int max;
              max = strtoll (param->type_max, NULL, 0);
              if (max == LLONG_MAX)
                return 6;
              g_string_append_printf (format, "%lli", max);
            }

          g_string_append_printf (format,
                                  "%s",
                                  param->fallback);

          {
            array_t *options;
            int option_index;
            gchar *option_value;

            options = (array_t*) g_ptr_array_index (params_options, index - 1);
            if (options == NULL)
              return -1;
            option_index = 0;
            while ((option_value = (gchar*) g_ptr_array_index (options,
                                                               option_index++)))
              g_string_append_printf (format, "%s", option_value);
          }
        }

      g_string_append_printf (format, "\n");

      if (format_signature)
        signature = (const char*) format_signature;

      if (verify_signature (format->str, format->len, signature,
                            strlen (signature), &format_trust))
        {
          g_free (format_signature);
          g_string_free (format, TRUE);
          return -1;
        }
      g_string_free (format, TRUE);
    }

  sql_begin_immediate ();

  if (user_may ("create_report_format") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (sql_int ("SELECT COUNT(*) FROM report_formats WHERE uuid = '%s';",
               uuid)
      || sql_int ("SELECT COUNT(*) FROM report_formats_trash"
                  " WHERE original_uuid = '%s';",
                  uuid))
    {
      gchar *base, *new, *old;
      char *real_old;

      /* Make a new UUID, because a report format exists with the given UUID. */

      new_uuid = openvas_uuid_make ();
      if (new_uuid == NULL)
        {
          sql ("ROLLBACK;");
          return -1;
        }

      /* Setup a private/report_formats/ link to the signature of the existing
       * report format in the feed.  This allows the signature to be shared. */

      base = g_strdup_printf ("%s.asc", uuid);
      old = g_build_filename (OPENVAS_NVT_DIR, "report_formats", base, NULL);
      real_old = realpath (old, NULL);
      if (real_old)
        {
          /* Signature exists in regular directory. */

          g_free (old);
          old = g_strdup (real_old);
          free (real_old);
        }
      else
        {
          struct stat state;

          /* Signature may be in private directory. */

          g_free (old);
          old = g_build_filename (OPENVAS_NVT_DIR, "private", "report_formats",
                                  base, NULL);
          if (lstat (old, &state))
            {
              /* No.  Signature may not exist in the feed yet. */
              g_free (old);
              old = g_build_filename (OPENVAS_NVT_DIR, "report_formats", base,
                                      NULL);
              tracef ("using standard old: %s\n", old);
            }
          else
            {
              int count;

              /* Yes.  Use the path it links to. */

              real_old = g_malloc (state.st_size + 1);
              count = readlink (old, real_old, state.st_size + 1);
              if (count < 0 || count > state.st_size)
                {
                  g_warning ("%s: readlink failed", __FUNCTION__);
                  sql ("ROLLBACK;");
                  return -1;
                }

              real_old[state.st_size] = '\0';
              g_free (old);
              old = real_old;
              tracef ("using linked old: %s\n", old);
            }
        }
      g_free (base);

      base = g_strdup_printf ("%s.asc", new_uuid);
      new = g_build_filename (OPENVAS_NVT_DIR, "private", "report_formats",
                              base, NULL);
      g_free (base);
      if (symlink (old, new))
        {
          g_warning ("%s: symlink failed: %s", __FUNCTION__, strerror (errno));
          sql ("ROLLBACK;");
          return -1;
        }
    }
  else
    new_uuid = NULL;

  candidate_name = g_strdup (name);
  quoted_name = sql_quote (candidate_name);

  num = 1;
  while (1)
    {
      if (!resource_with_name_exists (quoted_name, "report_format", 0))
        break;
      g_free (candidate_name);
      g_free (quoted_name);
      candidate_name = g_strdup_printf ("%s %u", name, ++num);
      quoted_name = sql_quote (candidate_name);
    }
  g_free (candidate_name);

  /* Write files to disk. */

  if (global)
    dir = g_build_filename (OPENVAS_DATA_DIR,
                            "openvasmd",
                            "global_report_formats",
                            new_uuid ? new_uuid : uuid,
                            NULL);
  else
    {
      assert (current_credentials.uuid);
      dir = g_build_filename (OPENVAS_STATE_DIR,
                              "openvasmd",
                              "report_formats",
                              current_credentials.uuid,
                              new_uuid ? new_uuid : uuid,
                              NULL);
    }

  if (g_file_test (dir, G_FILE_TEST_EXISTS) && openvas_file_remove_recurse (dir))
    {
      g_warning ("%s: failed to remove dir %s", __FUNCTION__, dir);
      g_free (dir);
      g_free (quoted_name);
      g_free (new_uuid);
      sql ("ROLLBACK;");
      return -1;
    }

  if (g_mkdir_with_parents (dir, 0755 /* "rwxr-xr-x" */))
    {
      g_warning ("%s: failed to create dir %s", __FUNCTION__, dir);
      g_free (dir);
      g_free (quoted_name);
      g_free (new_uuid);
      sql ("ROLLBACK;");
      return -1;
    }

  if (global == 0)
    {
      gchar *report_dir;

      /* glib seems to apply the mode to the first dir only. */

      report_dir = g_build_filename (OPENVAS_STATE_DIR,
                                     "openvasmd",
                                     "report_formats",
                                     current_credentials.uuid,
                                     NULL);

      if (chmod (report_dir, 0755 /* rwxr-xr-x */))
        {
          g_warning ("%s: chmod failed: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (dir);
          g_free (report_dir);
          g_free (quoted_name);
          g_free (new_uuid);
          sql ("ROLLBACK;");
          return -1;
        }

      g_free (report_dir);
    }

  /* glib seems to apply the mode to the first dir only. */
  if (chmod (dir, 0755 /* rwxr-xr-x */))
    {
      g_warning ("%s: chmod failed: %s\n",
                 __FUNCTION__,
                 strerror (errno));
      g_free (dir);
      g_free (quoted_name);
      g_free (new_uuid);
      sql ("ROLLBACK;");
      return -1;
    }

  index = 0;
  while ((file_name = (gchar*) g_ptr_array_index (files, index++)))
    {
      gchar *contents, *file, *full_file_name;
      gsize contents_size;
      GError *error;
      int ret;

      if (strlen (file_name) == 0)
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          g_free (quoted_name);
          g_free (new_uuid);
          sql ("ROLLBACK;");
          return 2;
        }

      file = file_name + strlen (file_name) + 1;
      if (strlen (file))
        contents = (gchar*) g_base64_decode (file, &contents_size);
      else
        {
          contents = g_strdup ("");
          contents_size = 0;
        }

      full_file_name = g_build_filename (dir, file_name, NULL);

      error = NULL;
      g_file_set_contents (full_file_name, contents, contents_size, &error);
      g_free (contents);
      if (error)
        {
          g_warning ("%s: %s", __FUNCTION__, error->message);
          g_error_free (error);
          openvas_file_remove_recurse (dir);
          g_free (full_file_name);
          g_free (dir);
          g_free (quoted_name);
          g_free (new_uuid);
          sql ("ROLLBACK;");
          return -1;
        }

      if (strcmp (file_name, "generate") == 0)
        ret = chmod (full_file_name, 0755 /* rwxr-xr-x */);
      else
        ret = chmod (full_file_name, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
      if (ret)
        {
          g_warning ("%s: chmod failed: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          openvas_file_remove_recurse (dir);
          g_free (full_file_name);
          g_free (dir);
          g_free (quoted_name);
          g_free (new_uuid);
          sql ("ROLLBACK;");
          return -1;
        }

      g_free (full_file_name);
    }

  /* Add format to database. */

  quoted_summary = summary ? sql_quote (summary) : NULL;
  quoted_description = description ? sql_quote (description) : NULL;
  quoted_extension = extension ? sql_quote (extension) : NULL;
  quoted_content_type = content_type ? sql_quote (content_type) : NULL;
  quoted_signature = signature ? sql_quote (signature) : NULL;
  g_free (format_signature);

  if (global)
    sql ("INSERT INTO report_formats"
         " (uuid, name, owner, summary, description, extension, content_type,"
         "  signature, trust, trust_time, flags, creation_time,"
         "  modification_time)"
         " VALUES ('%s', '%s', NULL, '%s', '%s', '%s', '%s', '%s', %i, %i, 0,"
         "         m_now (), m_now ());",
         new_uuid ? new_uuid : uuid,
         quoted_name,
         quoted_summary ? quoted_summary : "",
         quoted_description ? quoted_description : "",
         quoted_extension ? quoted_extension : "",
         quoted_content_type ? quoted_content_type : "",
         quoted_signature ? quoted_signature : "",
         format_trust,
         time (NULL));
  else
    sql ("INSERT INTO report_formats"
         " (uuid, name, owner, summary, description, extension, content_type,"
         "  signature, trust, trust_time, flags, creation_time,"
         "  modification_time)"
         " VALUES ('%s', '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '%s', '%s', '%s', '%s', '%s', %i, %i, 0, m_now (), m_now ());",
         new_uuid ? new_uuid : uuid,
         quoted_name,
         current_credentials.uuid,
         quoted_summary ? quoted_summary : "",
         quoted_description ? quoted_description : "",
         quoted_extension ? quoted_extension : "",
         quoted_content_type ? quoted_content_type : "",
         quoted_signature ? quoted_signature : "",
         format_trust,
         time (NULL));

  g_free (new_uuid);
  g_free (quoted_summary);
  g_free (quoted_description);
  g_free (quoted_extension);
  g_free (quoted_content_type);
  g_free (quoted_signature);
  g_free (quoted_name);

  /* Add params to database. */

  report_format_rowid = sql_last_insert_id ();
  index = 0;
  while ((param = (create_report_format_param_t*) g_ptr_array_index (params,
                                                                     index++)))
    {
      gchar *quoted_param_name, *quoted_param_value, *quoted_param_fallback;
      rowid_t param_rowid;
      long long int min, max;

      if (param->type == NULL)
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 7;
        }

      if (report_format_param_type_from_name (param->type)
          == REPORT_FORMAT_PARAM_TYPE_ERROR)
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 9;
        }

      /* Param min and max are optional.  LLONG_MIN and LLONG_MAX mark in the db
       * that they were missing, so if the user gives LLONG_MIN or LLONG_MAX it
       * is an error.  This ensures that GPG verification works, because the
       * verification knows when to leave out min and max. */

      if (param->type_min)
        {
          min = strtoll (param->type_min, NULL, 0);
          if (min == LLONG_MIN)
            {
              openvas_file_remove_recurse (dir);
              g_free (dir);
              sql ("ROLLBACK;");
              return 6;
            }
        }
      else
        min = LLONG_MIN;

      if (param->type_max)
        {
          max = strtoll (param->type_max, NULL, 0);
          if (max == LLONG_MAX)
            {
              openvas_file_remove_recurse (dir);
              g_free (dir);
              sql ("ROLLBACK;");
              return 6;
            }
        }
      else
        max = LLONG_MAX;

      if (param->fallback == NULL)
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 5;
        }

      quoted_param_name = sql_quote (param->name);

      if (sql_int ("SELECT count(*) FROM report_format_params"
                   " WHERE name = '%s' AND report_format = %llu;",
                   quoted_param_name,
                   report_format_rowid))
        {
          g_free (quoted_param_name);
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 8;
        }

      quoted_param_value = sql_quote (param->value);
      quoted_param_fallback = sql_quote (param->fallback);

      sql ("INSERT INTO report_format_params"
           " (report_format, name, type, value, type_min, type_max, type_regex,"
           "  fallback)"
           " VALUES (%llu, '%s', %u, '%s', %lli, %lli, '', '%s');",
           report_format_rowid,
           quoted_param_name,
           report_format_param_type_from_name (param->type),
           quoted_param_value,
           min,
           max,
           quoted_param_fallback);

      g_free (quoted_param_name);
      g_free (quoted_param_value);
      g_free (quoted_param_fallback);

      param_rowid = sql_last_insert_id ();

      {
        array_t *options;
        int option_index;
        gchar *option_value;

        options = (array_t*) g_ptr_array_index (params_options, index - 1);
        if (options == NULL)
          {
            g_warning ("%s: options was NULL", __FUNCTION__);
            openvas_file_remove_recurse (dir);
            g_free (dir);
            sql ("ROLLBACK;");
            return -1;
          }
        option_index = 0;
        while ((option_value = (gchar*) g_ptr_array_index (options,
                                                           option_index++)))
          {
            gchar *quoted_option_value = sql_quote (option_value);
            sql ("INSERT INTO report_format_param_options"
                 " (report_format_param, value)"
                 " VALUES (%llu, '%s');",
                 param_rowid,
                 quoted_option_value);
            g_free (quoted_option_value);
          }
      }

      if (validate_param_value (report_format_rowid, param_rowid, param->name,
                                param->value))
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 3;
        }

      if (validate_param_value (report_format_rowid, param_rowid, param->name,
                                param->fallback))
        {
          openvas_file_remove_recurse (dir);
          g_free (dir);
          sql ("ROLLBACK;");
          return 4;
        }
    }

  if (report_format)
    *report_format = report_format_rowid;

  g_free (dir);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create Report Format from an existing Report Format.
 *
 * @param[in]  name                 Name of new Report Format. NULL to copy
 *                                  from existing.
 * @param[in]  source_uuid          UUID of existing Report Format.
 * @param[out] new_report_format    New Report Format.
 *
 * @return 0 success, 1 Report Format exists already, 2 failed to find existing
 *         Report Format, 99 permission denied, -1 error.
 */
int
copy_report_format (const char* name, const char* source_uuid,
                    report_format_t* new_report_format)
{
  report_format_t new, old;
  gchar *copy_uuid, *source_dir, *copy_dir;
  gchar *tmp_dir;
  int global, ret;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  ret = copy_resource_lock ("report_format", name, NULL, source_uuid,
                            "extension, content_type, summary, description,"
                            " signature, trust, trust_time, flags",
                            1, &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  /* Copy report format parameters. */

  sql ("INSERT INTO report_format_params "
       " (report_format, name, type, value, type_min, type_max,"
       "  type_regex, fallback)"
       " SELECT %llu, name, type, value, type_min, type_max,"
       "  type_regex, fallback"
       "  FROM report_format_params WHERE report_format = %llu;",
       new,
       old);

  /* Copy files on disk. */

  global = report_format_global (old);
  if (global)
    source_dir = g_build_filename (OPENVAS_DATA_DIR,
                                   "openvasmd",
                                   "global_report_formats",
                                   source_uuid,
                                   NULL);
  else
    {
      gchar *owner_uuid;
      owner_uuid = report_format_owner_uuid (old);
      assert (owner_uuid);
      source_dir = g_build_filename (OPENVAS_STATE_DIR,
                                     "openvasmd",
                                     "report_formats",
                                     owner_uuid,
                                     source_uuid,
                                     NULL);
      g_free (owner_uuid);
    }

  /* Check that the source directory exists. */

  if (!g_file_test (source_dir, G_FILE_TEST_EXISTS))
    {
      g_warning ("%s: report format directory %s not found",
                 __FUNCTION__, source_dir);
      g_free (source_dir);
      sql ("ROLLBACK;");
      return -1;
    }

  copy_uuid = report_format_uuid (new);
  if (copy_uuid == NULL)
    {
      sql ("ROLLBACK;");
      return -1;
    }

  /* Prepare directory to copy into. */

  copy_dir = g_build_filename (OPENVAS_STATE_DIR,
                               "openvasmd",
                               "report_formats",
                               current_credentials.uuid,
                               copy_uuid,
                               NULL);

  if (g_file_test (copy_dir, G_FILE_TEST_EXISTS)
      && openvas_file_remove_recurse (copy_dir))
    {
      g_warning ("%s: failed to remove dir %s", __FUNCTION__, copy_dir);
      g_free (source_dir);
      g_free (copy_dir);
      g_free (copy_uuid);
      sql ("ROLLBACK;");
      return -1;
    }

  if (g_mkdir_with_parents (copy_dir, 0755 /* "rwxr-xr-x" */))
    {
      g_warning ("%s: failed to create dir %s", __FUNCTION__, copy_dir);
      g_free (source_dir);
      g_free (copy_dir);
      g_free (copy_uuid);
      sql ("ROLLBACK;");
      return -1;
    }

  /* Correct permissions as glib doesn't seem to do so. */

  tmp_dir = g_build_filename (OPENVAS_STATE_DIR,
                              "openvasmd",
                              "report_formats",
                              current_credentials.uuid,
                              NULL);

  if (chmod (tmp_dir, 0755 /* rwxr-xr-x */))
    {
      g_warning ("%s: chmod %s failed: %s\n",
                 __FUNCTION__,
                 tmp_dir,
                 strerror (errno));
      g_free (source_dir);
      g_free (copy_dir);
      g_free (copy_uuid);
      g_free (tmp_dir);
      sql ("ROLLBACK;");
      return -1;
    }
  g_free (tmp_dir);

  tmp_dir = g_build_filename (OPENVAS_STATE_DIR,
                              "openvasmd",
                              "report_formats",
                              current_credentials.uuid,
                              copy_uuid,
                              NULL);

  if (chmod (tmp_dir, 0755 /* rwxr-xr-x */))
    {
      g_warning ("%s: chmod %s failed: %s\n",
                 __FUNCTION__,
                 tmp_dir,
                 strerror (errno));
      g_free (source_dir);
      g_free (copy_dir);
      g_free (copy_uuid);
      g_free (tmp_dir);
      sql ("ROLLBACK;");
      return -1;
    }
  g_free (tmp_dir);
  g_free (copy_uuid);

  /* Copy files into new directory. */
  {
    GDir *directory;
    GError *error;

    error = NULL;
    directory = g_dir_open (source_dir, 0, &error);
    if (directory == NULL)
      {
        if (error)
          {
            g_warning ("g_dir_open(%s) failed - %s\n",
                       source_dir, error->message);
            g_error_free (error);
          }
        g_free (source_dir);
        g_free (copy_dir);
        sql ("ROLLBACK;");
        return -1;
      }
    else
      {
        gchar *source_file, *copy_file;
        const gchar *filename;

        filename = g_dir_read_name (directory);
        while (filename)
          {
            source_file = g_build_filename (source_dir, filename, NULL);
            copy_file = g_build_filename (copy_dir, filename, NULL);

            if (openvas_file_copy (source_file, copy_file) == FALSE)
              {
                g_warning ("%s: copy of %s to %s failed.\n",
                           __FUNCTION__, source_file, copy_file);
                g_free (source_file);
                g_free (copy_file);
                g_free (source_dir);
                g_free (copy_dir);
                sql ("ROLLBACK");
                return -1;
              }
            g_free (source_file);
            g_free (copy_file);
            filename = g_dir_read_name (directory);
          }
      }
  }

  sql ("COMMIT");
  g_free (source_dir);
  g_free (copy_dir);
  if (new_report_format) *new_report_format = new;
  return 0;
}

/**
 * @brief Modify a report format.
 *
 * @param[in]  report_format_id  UUID of report format.
 * @param[in]  name              Name of report format.
 * @param[in]  summary           Summary of report format.
 * @param[in]  active            Active flag.
 * @param[in]  param_name        Parameter to modify.
 * @param[in]  param_value       Value of parameter.
 *
 * @return 0 success, 1 failed to find report format, 2 report format_id
 * required, 3 failed to find report format parameter, 4 parameter value
 * validation failed, 99 permission denied, -1 internal error.
 */
int
modify_report_format (const char *report_format_id, const char *name,
                      const char *summary, const char *active,
                      const char *param_name, const char *param_value)
{
  report_format_t report_format;
  int ret = 0;

  if (report_format_id == NULL)
    return 2;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_report_format") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  report_format = 0;
  if (find_report_format_with_permission (report_format_id, &report_format,
                                          "modify_report_format"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (report_format == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Update values */
  if (name)
    set_report_format_name (report_format, name);

  if (summary)
    set_report_format_summary (report_format, summary);

  if (active)
    set_report_format_active (report_format, strcmp (active, "0"));

  sql ("COMMIT;");

  /* Update format params if set */
  if (param_name)
    {
      ret = set_report_format_param (report_format, param_name, param_value);
      if (ret == 1)
        ret = 3;
      if (ret == 2)
        ret = 4;
    }

  return ret;
}

/**
 * @brief Move a report format directory.
 *
 * @param[in]  dir      Old dir.
 * @param[in]  new_dir  New dir.
 *
 * @return 0 success, -1 error.
 */
static int
move_report_format_dir (const char *dir, const char *new_dir)
{
  if (g_file_test (dir, G_FILE_TEST_EXISTS)
      && openvas_file_check_is_dir (dir))
    {
      if (rename (dir, new_dir))
        {
          GError *error;
          GDir *directory;
          const gchar *entry;

          if (errno == EXDEV)
            {
              /* Across devices, move by hand. */

              if (g_mkdir_with_parents (new_dir, 0755 /* "rwxr-xr-x" */))
                {
                  g_warning ("%s: failed to create dir %s", __FUNCTION__,
                             new_dir);
                  return -1;
                }

              error = NULL;
              directory = g_dir_open (dir, 0, &error);

              if (directory == NULL)
                {
                  g_warning ("%s: failed to g_dir_open %s: %s\n",
                             __FUNCTION__, dir, error->message);
                  g_error_free (error);
                  return -1;
                }

              entry = NULL;
              while ((entry = g_dir_read_name (directory)))
                {
                  gchar *entry_path, *new_path;
                  entry_path = g_build_filename (dir, entry, NULL);
                  new_path = g_build_filename (new_dir, entry, NULL);
                  if (openvas_file_move (entry_path, new_path) == FALSE)
                    {
                      g_warning ("%s: failed to move %s to %s",
                                 __FUNCTION__, entry_path, new_path);
                      g_free (entry_path);
                      g_free (new_path);
                      g_dir_close (directory);
                      return -1;
                    }
                  g_free (entry_path);
                  g_free (new_path);
                }

              g_dir_close (directory);

              openvas_file_remove_recurse (dir);
            }
          else
            {
              g_warning ("%s: rename %s to %s: %s\n",
                         __FUNCTION__, dir, new_dir, strerror (errno));
              return -1;
            }
        }
    }
  else
    {
      g_warning ("%s: report dir missing: %s\n",
                 __FUNCTION__, dir);
      return -1;
    }
  return 0;
}

/**
 * @brief Delete a report format.
 *
 * @param[in]  report_format_id  UUID of Report format.
 * @param[in]  ultimate          Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 report format in use, 2 failed to find report format,
 *         3 predefined report format, 99 permission denied, -1 error.
 */
int
delete_report_format (const char *report_format_id, int ultimate)
{
  gchar *dir;
  report_format_t report_format, trash_report_format;

  /* This is complicated in two ways
   *
   *   - the UUID of a report format is the same every time it is
   *     imported, so to prevent multiple deletes from producing
   *     duplicate UUIDs in the trashcan, each report format in the
   *     trashcan gets a new UUID,
   *
   *   - the report format has information on disk on top of the
   *     info in the db, so the disk information has to be held
   *     in a special trashcan directory. */

  sql_begin_immediate ();

  if (user_may ("delete_report_format") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  /* Look in the "real" table. */

  if (find_report_format_with_permission (report_format_id, &report_format,
                                          "delete_report_format"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (report_format == 0)
    {
      gchar *report_format_string, *base;

      /* Look in the trashcan. */

      if (find_trash ("report_format", report_format_id, &report_format))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (report_format == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a trash alert. */

      if (trash_report_format_in_use (report_format))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      /* Remove entirely. */

      permissions_set_orphans ("report_format", report_format, LOCATION_TRASH);
      tags_set_orphans ("report_format", report_format, LOCATION_TRASH);

      base = sql_string ("SELECT original_uuid || '.asc'"
                         " FROM report_formats_trash"
                         " WHERE id = %llu;",
                         report_format);
      sql ("DELETE FROM report_format_param_options_trash"
           " WHERE report_format_param"
           " IN (SELECT id from report_format_params_trash"
           "     WHERE report_format = %llu);",
           report_format);
      sql ("DELETE FROM report_format_params_trash WHERE report_format = %llu;",
           report_format);
      sql ("DELETE FROM report_formats_trash WHERE id = %llu;",
           report_format);

      /* Remove the dirs last, in case any SQL rolls back. */

      /* Trash files. */
      report_format_string = g_strdup_printf ("%llu", report_format);
      dir = g_build_filename (OPENVAS_DATA_DIR,
                              "openvasmd",
                              "report_formats_trash",
                              report_format_string,
                              NULL);
      g_free (report_format_string);
      if (g_file_test (dir, G_FILE_TEST_EXISTS) && openvas_file_remove_recurse (dir))
        {
          g_free (dir);
          g_free (base);
          sql ("ROLLBACK;");
          return -1;
        }
      g_free (dir);

      /* Signature links in the feed. */
      dir = g_build_filename (OPENVAS_NVT_DIR, "private", "report_formats",
                              base, NULL);
      g_free (base);
      unlink (dir);
      g_free (dir);
      sql ("COMMIT;");

      return 0;
    }

  if (report_format_predefined (report_format))
    {
      sql ("ROLLBACK;");
      return 3;
    }

  if (report_format_global (report_format))
    dir = g_build_filename (OPENVAS_DATA_DIR,
                            "openvasmd",
                            "global_report_formats",
                            report_format_id,
                            NULL);
  else
    dir = g_build_filename (OPENVAS_STATE_DIR,
                            "openvasmd",
                            "report_formats",
                            current_credentials.uuid,
                            report_format_id,
                            NULL);

  if (ultimate)
    {
      permissions_set_orphans ("report_format", report_format, LOCATION_TABLE);
      tags_set_orphans ("report_format", report_format, LOCATION_TABLE);

      /* Check if it's in use by a trash or regular alert. */

      if (sql_int ("SELECT count(*) FROM alert_method_data_trash"
                   " WHERE data = (SELECT uuid FROM report_formats"
                   "               WHERE id = %llu)"
                   " AND (name = 'notice_attach_format'"
                   "      OR name = 'notice_report_format');",
                   report_format))
        {
          g_free (dir);
          sql ("ROLLBACK;");
          return 1;
        }

      if (report_format_in_use (report_format))
        {
          g_free (dir);
          sql ("ROLLBACK;");
          return 1;
        }

      /* Remove directory. */

      if (g_file_test (dir, G_FILE_TEST_EXISTS) && openvas_file_remove_recurse (dir))
        {
          g_free (dir);
          sql ("ROLLBACK;");
          return -1;
        }
    }
  else
    {
      iterator_t params;
      gchar *trash_dir;

      /* Check if it's in use by a regular alert. */

      if (report_format_in_use (report_format))
        {
          g_free (dir);
          sql ("ROLLBACK;");
          return 1;
        }

      /* Move to trash. */

      trash_dir = g_build_filename (OPENVAS_DATA_DIR,
                                    "openvasmd",
                                    "report_formats_trash",
                                    NULL);
      if (g_mkdir_with_parents (trash_dir, 0755 /* "rwxr-xr-x" */))
        {
          g_warning ("%s: failed to create dir %s", __FUNCTION__, trash_dir);
          g_free (trash_dir);
          sql ("ROLLBACK;");
          return -1;
        }
      g_free (trash_dir);

      sql ("INSERT INTO report_formats_trash"
           " (uuid, owner, name, extension, content_type, summary,"
           "  description, signature, trust, trust_time, flags, original_uuid,"
           "  creation_time, modification_time)"
           " SELECT"
           "  make_uuid (), owner, name, extension, content_type, summary,"
           "  description, signature, trust, trust_time, flags, uuid,"
           "  creation_time, modification_time"
           " FROM report_formats"
           " WHERE id = %llu;",
           report_format);

      trash_report_format = sql_last_insert_id ();

      init_report_format_param_iterator (&params, report_format, 0, 1, NULL);
      while (next (&params))
        {
          report_format_param_t param, trash_param;

          param = report_format_param_iterator_param (&params);

          sql ("INSERT INTO report_format_params_trash"
               " (report_format, name, type, value, type_min, type_max,"
               "  type_regex, fallback)"
               " SELECT"
               "  %llu, name, type, value, type_min, type_max,"
               "  type_regex, fallback"
               " FROM report_format_params"
               " WHERE id = %llu;",
               trash_report_format,
               param);

          trash_param = sql_last_insert_id ();

          sql ("INSERT INTO report_format_param_options_trash"
               " (report_format_param, value)"
               " SELECT %llu, value"
               " FROM report_format_param_options"
               " WHERE report_format_param = %llu;",
               trash_param,
               param);
        }
      cleanup_iterator (&params);

      permissions_set_locations ("report_format", report_format,
                                 trash_report_format, LOCATION_TRASH);
      tags_set_locations ("report_format", report_format,
                          trash_report_format, LOCATION_TRASH);
    }

  /* Remove from "real" tables. */

  sql ("DELETE FROM report_format_param_options WHERE report_format_param"
       " IN (SELECT id from report_format_params WHERE report_format = %llu);",
       report_format);
  sql ("DELETE FROM report_format_params WHERE report_format = %llu;",
       report_format);
  sql ("DELETE FROM report_formats WHERE id = %llu;", report_format);

  /* Move the dir last, in case any SQL rolls back. */

  if (ultimate == 0)
    {
      gchar *new_dir, *report_format_string;

      report_format_string = g_strdup_printf ("%llu", trash_report_format);
      new_dir = g_build_filename (OPENVAS_DATA_DIR,
                                  "openvasmd",
                                  "report_formats_trash",
                                  report_format_string,
                                  NULL);
      g_free (report_format_string);
      if (move_report_format_dir (dir, new_dir))
        {
          g_free (dir);
          g_free (new_dir);
          sql ("ROLLBACK;");
          return -1;
        }
      g_free (new_dir);
    }
  g_free (dir);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Verify a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 0 success, -1 error.
 */
int
verify_report_format_internal (report_format_t report_format)
{
  int format_trust = TRUST_UNKNOWN;
  iterator_t formats;
  get_data_t get;
  gchar *uuid;

  memset(&get, '\0', sizeof (get));
  get.id = report_format_uuid (report_format);
  init_report_format_iterator (&formats, &get);
  if (next (&formats))
    {
      const char *signature;
      gchar *format_signature = NULL;
      gsize format_signature_size;

      signature = report_format_iterator_signature (&formats);

      find_signature ("report_formats", get_iterator_uuid (&formats),
                      &format_signature, &format_signature_size, &uuid);

      if ((signature && strlen (signature))
          || format_signature)
        {
          GString *format;
          file_iterator_t files;
          iterator_t params;
          report_format_t report_format;

          format = g_string_new ("");

          report_format = get_iterator_resource (&formats);
          g_string_append_printf
           (format, "%s%s%s%i", uuid ? uuid : get_iterator_uuid (&formats),
            report_format_iterator_extension (&formats),
            report_format_iterator_content_type (&formats),
            report_format_global (report_format) & 1);
          g_free (uuid);

          init_report_format_file_iterator (&files, report_format);
          while (next_file (&files))
            {
              gchar *content = file_iterator_content_64 (&files);
              g_string_append_printf (format,
                                      "%s%s",
                                      file_iterator_name (&files),
                                      content);
              g_free (content);
            }
          cleanup_file_iterator (&files);

          init_report_format_param_iterator (&params,
                                             report_format,
                                             0,
                                             1,
                                             NULL);
          while (next (&params))
            {
              g_string_append_printf
               (format,
                "%s%s",
                report_format_param_iterator_name (&params),
                report_format_param_iterator_type_name (&params));

              if (report_format_param_iterator_type_min (&params) > LLONG_MIN)
                g_string_append_printf
                 (format,
                  "%lli",
                  report_format_param_iterator_type_min (&params));

              if (report_format_param_iterator_type_max (&params) < LLONG_MAX)
                g_string_append_printf
                 (format,
                  "%lli",
                  report_format_param_iterator_type_max (&params));

              g_string_append_printf
               (format,
                "%s%s",
                report_format_param_iterator_type_regex (&params),
                report_format_param_iterator_fallback (&params));

              {
                iterator_t options;
                init_param_option_iterator
                 (&options,
                  report_format_param_iterator_param (&params),
                  1,
                  NULL);
                while (next (&options))
                  if (param_option_iterator_value (&options))
                    g_string_append_printf
                     (format,
                      "%s",
                      param_option_iterator_value (&options));
              }
            }
          cleanup_iterator (&params);

          g_string_append_printf (format, "\n");

          if (format_signature)
            {
              /* Try the feed signature. */
              if (verify_signature (format->str, format->len, format_signature,
                                    strlen (format_signature), &format_trust))
                {
                  cleanup_iterator (&formats);
                  g_free (format_signature);
                  g_string_free (format, TRUE);
                  return -1;
                }
            }
          else if (signature && strlen (signature))
            {
              /* Try the signature from the database. */
              if (verify_signature (format->str, format->len, signature,
                                    strlen (signature), &format_trust))
                {
                  cleanup_iterator (&formats);
                  g_free (format_signature);
                  g_string_free (format, TRUE);
                  return -1;
                }
            }

          g_free (format_signature);
          g_string_free (format, TRUE);
        }
    }
  else
    {
      return -1;
    }
  cleanup_iterator (&formats);

  sql ("UPDATE report_formats SET trust = %i, trust_time = %i,"
       "                          modification_time = m_now ()"
       " WHERE id = %llu;",
       format_trust,
       time (NULL),
       report_format);

  return 0;
}

/**
 * @brief Verify a report format.
 *
 * @param[in]  report_format_id  Report format UUID.
 *
 * @return 0 success, 1 failed to find report format, 99 permission denied,
 *         -1 error.
 */
int
verify_report_format (const char *report_format_id)
{
  int ret;
  report_format_t report_format;

  sql_begin_immediate ();

  if (user_may ("verify_report_format") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  report_format = 0;
  if (find_report_format_with_permission (report_format_id, &report_format,
                                          "verify_report_format"))
    {
      sql ("ROLLBACK;");
      return -1;
    }
  if (report_format == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  ret = verify_report_format_internal (report_format);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Verify a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 0 success, 99 permission denied, -1 error.
 */
static int
report_format_verify (report_format_t report_format)
{
  int ret;

  sql_begin_immediate ();
  ret = verify_report_format_internal (report_format);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return the UUID of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Newly allocated UUID.
 */
char *
report_format_uuid (report_format_t report_format)
{
  return sql_string ("SELECT uuid FROM report_formats WHERE id = %llu;",
                     report_format);
}

/**
 * @brief Return the UUID of the owner of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Newly allocated owner UUID if there is an owner, else NULL.
 */
char *
report_format_owner_uuid (report_format_t report_format)
{
  if (sql_int ("SELECT owner IS NULL FROM report_formats"
               " WHERE id = %llu;",
               report_format))
    return NULL;
  return sql_string ("SELECT uuid FROM users"
                     " WHERE id = (SELECT owner FROM report_formats"
                     "             WHERE id = %llu);",
                     report_format);
}

/**
 * @brief Set the active flag of a report format.
 *
 * @param[in]  report_format  The report format.
 * @param[in]  active         Active flag.
 */
void
set_report_format_active (report_format_t report_format, int active)
{
  if (active)
    sql ("UPDATE report_formats SET flags = (flags | %llu), "
         "                          modification_time = m_now ()"
         " WHERE id = %llu;",
         (long long int) REPORT_FORMAT_FLAG_ACTIVE,
         report_format);
  else
    sql ("UPDATE report_formats SET flags = (flags & ~ %llu), "
         "                          modification_time = m_now ()"
         " WHERE id = %llu;",
         (long long int) REPORT_FORMAT_FLAG_ACTIVE,
         report_format);
}

/**
 * @brief Return the name of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Newly allocated name.
 */
char *
report_format_name (report_format_t report_format)
{
  return sql_string ("SELECT name FROM report_formats WHERE id = %llu;",
                     report_format);
}

/**
 * @brief Return the content type of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Newly allocated content type.
 */
char *
report_format_content_type (report_format_t report_format)
{
  return sql_string ("SELECT content_type FROM report_formats"
                     " WHERE id = %llu;",
                     report_format);
}

/**
 * @brief Return whether a report format is referenced by an alert.
 *
 * @param[in]  report_format  Report Format.
 *
 * @return 1 if in use, else 0.
 */
int
report_format_in_use (report_format_t report_format)
{
  return !!sql_int ("SELECT count(*) FROM alert_method_data"
                    " WHERE data = (SELECT uuid FROM report_formats"
                    "               WHERE id = %llu)"
                    " AND (name = 'notice_attach_format'"
                    "      OR name = 'notice_report_format');",
                    report_format);
}

/**
 * @brief Return whether a report format in trash is referenced by an alert.
 *
 * @param[in]  report_format  Report Format.
 *
 * @return 1 if in use, else 0.
 */
int
trash_report_format_in_use (report_format_t report_format)
{
  return !!sql_int ("SELECT count(*) FROM alert_method_data_trash"
                    " WHERE data = (SELECT original_uuid"
                    "               FROM report_formats_trash"
                    "               WHERE id = %llu)"
                    " AND (name = 'notice_attach_format'"
                    "      OR name = 'notice_report_format');",
                    report_format);
}

/**
 * @brief Return whether a report format is writable.
 *
 * @param[in]  report_format Report Format.
 *
 * @return 1 if writable, else 0.
 */
int
report_format_writable (report_format_t report_format)
{
  return (report_format_in_use (report_format) == 0
          && report_format_global (report_format) == 0);
}

/**
 * @brief Return whether a trashcan report_format is writable.
 *
 * @param[in]  report_format  Report Format.
 *
 * @return 1 if writable, else 0.
 */
int
trash_report_format_writable (report_format_t report_format)
{
  return (trash_report_format_in_use (report_format) == 0
          && trash_report_format_global (report_format) == 0);
}

/**
 * @brief Return the extension of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Newly allocated extension.
 */
char *
report_format_extension (report_format_t report_format)
{
  return sql_string ("SELECT extension FROM report_formats WHERE id = %llu;",
                     report_format);
}

/**
 * @brief Set the name of the report format.
 *
 * @param[in]  report_format  The report format.
 * @param[in]  name           Name.
 */
void
set_report_format_name (report_format_t report_format, const char *name)
{
  gchar *quoted_name = sql_quote (name);
  sql ("UPDATE report_formats SET name = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       report_format);
  g_free (quoted_name);
}

/**
 * @brief Return whether a report format is global.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 1 if global, else 0.
 */
int
report_format_global (report_format_t report_format)
{
  return sql_int ("SELECT owner is NULL FROM report_formats"
                  " WHERE id = %llu;",
                  report_format);
}

/**
 * @brief Return whether a report format is global.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 1 if global, else 0.
 */
int
trash_report_format_global (report_format_t report_format)
{
  return sql_int ("SELECT owner is NULL FROM report_formats_trash"
                  " WHERE id = %llu;",
                  report_format);
}

/**
 * @brief Return whether a report format is predefined.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 1 if predefined, else 0.
 */
int
report_format_predefined (report_format_t report_format)
{
  return sql_int ("SELECT uuid = '5057e5cc-b825-11e4-9d0e-28d24461215b'"
                  " OR uuid = '910200ca-dc05-11e1-954f-406186ea4fc5'"
                  " OR uuid = '5ceff8ba-1f62-11e1-ab9f-406186ea4fc5'"
                  " OR uuid = 'c1645568-627a-11e3-a660-406186ea4fc5'"
                  " OR uuid = '9087b18c-626c-11e3-8892-406186ea4fc5'"
                  " OR uuid = '6c248850-1f62-11e1-b082-406186ea4fc5'"
                  " OR uuid = '77bd6c4a-1f62-11e1-abf0-406186ea4fc5'"
                  " OR uuid = 'a684c02c-b531-11e1-bdc2-406186ea4fc5'"
                  " OR uuid = '9ca6fe72-1f62-11e1-9e7c-406186ea4fc5'"
                  " OR uuid = 'c402cc3e-b531-11e1-9163-406186ea4fc5'"
                  " OR uuid = 'a3810a62-1f62-11e1-9219-406186ea4fc5'"
                  " OR uuid = 'a994b278-1f62-11e1-96ac-406186ea4fc5'"
                  " OR uuid = '9e5e5deb-879e-4ecc-8be6-a71cd0875cdd'"
                  " OR uuid = 'c15ad349-bd8d-457a-880a-c7056532ee15'"
                  " OR uuid = '50c9950a-f326-11e4-800c-28d24461215b'"
                  " FROM report_formats"
                  " WHERE id = %llu;",
                  report_format);
}

/**
 * @brief Return whether a report format is global.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 1 if global, else 0.
 */
static int
report_format_trash_global (report_format_t report_format)
{
  return sql_int ("SELECT owner is NULL FROM report_formats_trash"
                  " WHERE id = %llu;",
                  report_format);
}

/**
 * @brief Return whether a report format is active.
 *
 * @param[in]  report_format  Report format.
 *
 * @return -1 on error, 1 if active, else 0.
 */
int
report_format_active (report_format_t report_format)
{
  long long int flag;
  switch (sql_int64 (&flag,
                     "SELECT flags & %llu FROM report_formats"
                     " WHERE id = %llu;",
                     (long long int) REPORT_FORMAT_FLAG_ACTIVE,
                     report_format))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }
  return flag ? 1 : 0;
}

/**
 * @brief Set the summary of the report format.
 *
 * @param[in]  report_format  The report format.
 * @param[in]  summary        Summary.
 */
void
set_report_format_summary (report_format_t report_format, const char *summary)
{
  gchar *quoted_summary = sql_quote (summary);
  sql ("UPDATE report_formats SET summary = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_summary,
       report_format);
  g_free (quoted_summary);
}

/**
 * @brief Return the type max of a report format param.
 *
 * @param[in]  report_format  Report format.
 * @param[in]  name           Name of param.
 *
 * @return Param type.
 */
static report_format_param_type_t
report_format_param_type (report_format_t report_format, const char *name)
{
  report_format_param_type_t type;
  gchar *quoted_name = sql_quote (name);
  type = (report_format_param_type_t)
         sql_int ("SELECT type FROM report_format_params"
                  " WHERE report_format = %llu AND name = '%s';",
                  report_format,
                  quoted_name);
  g_free (quoted_name);
  return type;
}

/**
 * @brief Return the type max of a report format param.
 *
 * @param[in]  report_format  Report format.
 * @param[in]  name           Name of param.
 *
 * @return Max.
 */
static long long int
report_format_param_type_max (report_format_t report_format, const char *name)
{
  long long int max = 0;
  gchar *quoted_name = sql_quote (name);
  /* Assume it's there. */
  sql_int64 (&max,
             "SELECT type_max FROM report_format_params"
             " WHERE report_format = %llu AND name = '%s';",
             report_format,
             quoted_name);
  g_free (quoted_name);
  return max;
}

/**
 * @brief Return the type min of a report format param.
 *
 * @param[in]  report_format  Report format.
 * @param[in]  name           Name of param.
 *
 * @return Min.
 */
static long long int
report_format_param_type_min (report_format_t report_format, const char *name)
{
  long long int min = 0;
  gchar *quoted_name = sql_quote (name);
  /* Assume it's there. */
  sql_int64 (&min,
             "SELECT type_min FROM report_format_params"
             " WHERE report_format = %llu AND name = '%s';",
             report_format,
             quoted_name);
  g_free (quoted_name);
  return min;
}


/**
 * @brief Validate a value for a report format param.
 *
 * @param[in]  report_format  Report format.
 * @param[in]  param          Param.
 * @param[in]  name           Name of param.
 * @param[in]  value          Potential value of param.
 *
 * @return 0 success, 1 fail.
 */
static int
validate_param_value (report_format_t report_format,
                      report_format_param_t param, const char *name,
                      const char *value)
{
  switch (report_format_param_type (report_format, name))
    {
      case REPORT_FORMAT_PARAM_TYPE_INTEGER:
        {
          long long int min, max, actual;
          min = report_format_param_type_min (report_format, name);
          /* Simply truncate out of range values. */
          actual = strtoll (value, NULL, 0);
          if (actual < min)
            return 1;
          max = report_format_param_type_max (report_format, name);
          if (actual > max)
            return 1;
        }
        break;
      case REPORT_FORMAT_PARAM_TYPE_SELECTION:
        {
          iterator_t options;
          int found = 0;

          init_param_option_iterator (&options, param, 1, NULL);
          while (next (&options))
            if (param_option_iterator_value (&options)
                && (strcmp (param_option_iterator_value (&options), value)
                    == 0))
              {
                found = 1;
                break;
              }
          cleanup_iterator (&options);
          if (found)
            break;
          return 1;
        }
      case REPORT_FORMAT_PARAM_TYPE_STRING:
      case REPORT_FORMAT_PARAM_TYPE_TEXT:
        {
          long long int min, max, actual;
          min = report_format_param_type_min (report_format, name);
          actual = strlen (value);
          if (actual < min)
            return 1;
          max = report_format_param_type_max (report_format, name);
          if (actual > max)
            return 1;
        }
        break;
      default:
        break;
    }
  return 0;
}

/**
 * @brief Set the value of the report format param.
 *
 * @param[in]  report_format  The report format.
 * @param[in]  name           Param name.
 * @param[in]  value_64       Param value in base64.
 *
 * @return 0 success, 1 failed to find param, 2 validation of value failed,
 *         -1 error.
 */
int
set_report_format_param (report_format_t report_format, const char *name,
                         const char *value_64)
{
  gchar *quoted_name, *quoted_value, *value;
  gsize value_size;
  report_format_param_t param;

  quoted_name = sql_quote (name);

  sql_begin_immediate ();

  /* Ensure the param exists. */

  switch (sql_int64 (&param,
                     "SELECT id FROM report_format_params"
                     " WHERE name = '%s';",
                     quoted_name))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        g_free (quoted_name);
        sql ("ROLLBACK;");
        return 1;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_name);
        sql ("ROLLBACK;");
        return -1;
        break;
    }

  /* Translate the value. */

  if (value_64 && strlen (value_64))
    value = (gchar*) g_base64_decode (value_64, &value_size);
  else
    {
      value = g_strdup ("");
      value_size = 0;
    }

  /* Validate the value. */

  if (validate_param_value (report_format, param, name, value))
    {
      sql ("ROLLBACK;");
      g_free (quoted_name);
      return 2;
    }

  quoted_value = sql_quote (value);
  g_free (value);

  /* Update the database. */

  sql ("UPDATE report_format_params SET value = '%s'"
       " WHERE report_format = %llu AND name = '%s';",
       quoted_value,
       report_format,
       quoted_name);

  g_free (quoted_name);
  g_free (quoted_value);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Return the trust of a report format.
 *
 * @param[in]  report_format  Report format.
 *
 * @return Trust: 1 yes, 2 no, 3 unknown.
 */
int
report_format_trust (report_format_t report_format)
{
  return sql_int ("SELECT trust FROM report_formats WHERE id = %llu;",
                  report_format);
}

/**
 * @brief Filter columns for Report Format iterator.
 */
#define REPORT_FORMAT_ITERATOR_FILTER_COLUMNS                                 \
 { ANON_GET_ITERATOR_FILTER_COLUMNS, "name", "extension", "content_type",     \
   "summary", "description", "trust", "trust_time", "active", NULL }

/**
 * @brief Report Format iterator columns.
 */
#define REPORT_FORMAT_ITERATOR_COLUMNS                                  \
 {                                                                      \
   { "id", NULL },                                                      \
   { "uuid", NULL },                                                    \
   { "name", NULL },                                                    \
   { "''", NULL },                                                      \
   { "iso_time (creation_time)", NULL },                                \
   { "iso_time (modification_time)", NULL },                            \
   { "creation_time", "created" },                                      \
   { "modification_time", "modified" },                                 \
   {                                                                    \
     "(SELECT name FROM users WHERE users.id = report_formats.owner)",  \
     "_owner"                                                           \
   },                                                                   \
   { "owner", NULL },                                                   \
   { "extension", NULL },                                               \
   { "content_type", NULL },                                            \
   { "summary", NULL },                                                 \
   { "description", NULL },                                             \
   { "signature", NULL },                                               \
   { "trust", NULL },                                                   \
   { "trust_time", NULL },                                              \
   { "flags & 1", "active" },                                           \
   { NULL, NULL }                                                       \
 }

/**
 * @brief Report Format iterator columns for trash case.
 */
#define REPORT_FORMAT_ITERATOR_TRASH_COLUMNS                            \
 {                                                                      \
   { "id", NULL },                                                      \
   { "uuid", NULL },                                                    \
   { "name", NULL },                                                    \
   { "''", NULL },                                                      \
   { "iso_time (creation_time)", NULL },                                \
   { "iso_time (modification_time)", NULL },                            \
   { "creation_time", "created" },                                      \
   { "modification_time", "modified" },                                 \
   {                                                                    \
     "(SELECT name FROM users"                                          \
     " WHERE users.id = report_formats_trash.owner)",                   \
     "_owner"                                                           \
   },                                                                   \
   { "owner", NULL },                                                   \
   { "extension", NULL },                                               \
   { "content_type", NULL },                                            \
   { "summary", NULL },                                                 \
   { "description", NULL },                                             \
   { "signature", NULL },                                               \
   { "trust", NULL },                                                   \
   { "trust_time", NULL },                                              \
   { "flags & 1", "active" },                                           \
   { NULL, NULL }                                                       \
 }

/**
 * @brief Count the number of Report Formats.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of Report Formats filtered set.
 */
int
report_format_count (const get_data_t *get)
{
  static const char *filter_columns[] = REPORT_FORMAT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = REPORT_FORMAT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = REPORT_FORMAT_ITERATOR_TRASH_COLUMNS;
  return count ("report_format", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a Report Format iterator, including observed Report
 *        Formats.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find Report Format, 2 failed to find filter,
 *         -1 error.
 */
int
init_report_format_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = REPORT_FORMAT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = REPORT_FORMAT_ITERATOR_COLUMNS;
  static column_t trash_columns[] = REPORT_FORMAT_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "report_format",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the extension from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Extension, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_iterator_extension, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the content type from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Content type, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_iterator_content_type, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the summary from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Summary, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_iterator_summary, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the description from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Description, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_iterator_description, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the signature from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Signature, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_iterator_signature, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the trust value from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Trust value.
 */
const char*
report_format_iterator_trust (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  switch (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5))
    {
      case 1:  return "yes";
      case 2:  return "no";
      case 3:  return "unknown";
      default: return NULL;
    }
}

/**
 * @brief Get the trust time from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time report format was verified.
 */
time_t
report_format_iterator_trust_time (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
  return ret;
}

/**
 * @brief Get the active flag from a report format iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Active flag, or -1 if iteration is complete.
 */
int
report_format_iterator_active (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return (iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7)
          & REPORT_FORMAT_FLAG_ACTIVE) ? 1 : 0;
}

/**
 * @brief Initialise a Report Format alert iterator.
 *
 * Iterates over all alerts that use the Report Format.
 *
 * @param[in]  iterator          Iterator.
 * @param[in]  report_format     Report Format.
 */
void
init_report_format_alert_iterator (iterator_t* iterator,
                                   report_format_t report_format)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (report_format);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_alerts"));
  available = where_owned ("alert", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT DISTINCT alerts.name, alerts.uuid, %s"
                 " FROM alerts, alert_method_data"
                 " WHERE alert_method_data.data = '%s'"
                 " AND alert_method_data.alert = alerts.id"
                 " ORDER BY alerts.name ASC;",
                 available,
                 report_format_uuid (report_format));

  g_free (available);
}

/**
 * @brief Get the name from a report_format_alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the Report Format, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (report_format_alert_iterator_name, 0);

/**
 * @brief Get the UUID from a report_format_alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the Report Format, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (report_format_alert_iterator_uuid, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
report_format_alert_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}

/**
 * @brief Initialise a report format iterator.
 *
 * @param[in]  iterator       Iterator.
 * @param[in]  report_format  Single report_format to iterate over, or 0 for all.
 * @param[in]  trash          Whether to iterate over trashcan report formats.
 * @param[in]  ascending      Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "id".
 */
void
init_report_format_param_iterator (iterator_t* iterator,
                                   report_format_t report_format,
                                   int trash,
                                   int ascending,
                                   const char* sort_field)
{
  if (report_format)
    init_iterator (iterator,
                   "SELECT id, name, value, type, type_min, type_max,"
                   " type_regex, fallback"
                   " FROM report_format_params%s"
                   " WHERE report_format = %llu"
                   " ORDER BY %s %s;",
                   trash ? "_trash" : "",
                   report_format,
                   sort_field ? sort_field : "id",
                   ascending ? "ASC" : "DESC");
  else
    init_iterator (iterator,
                   "SELECT id, name, value, type, type_min, type_max,"
                   " type_regex, fallback"
                   " FROM report_format_params%s"
                   " ORDER BY %s %s;",
                   trash ? "_trash" : "",
                   sort_field ? sort_field : "id",
                   ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the report format param from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Report format param.
 */
report_format_param_t
report_format_param_iterator_param (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (report_format_param_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the name from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_param_iterator_name, 1);

/**
 * @brief Get the value from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_param_iterator_value, 2);

/**
 * @brief Get the name of the type of a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Static string naming type, or NULL if iteration is complete.
 */
const char *
report_format_param_iterator_type_name (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  return report_format_param_type_name (iterator_int (iterator, 3));
}

/**
 * @brief Get the type from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type.
 */
report_format_param_type_t
report_format_param_iterator_type (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, 3);
}

/**
 * @brief Get the type min from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type min.
 */
long long int
report_format_param_iterator_type_min (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int64 (iterator, 4);
}

/**
 * @brief Get the type max from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type max.
 */
long long int
report_format_param_iterator_type_max (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int64 (iterator, 5);
}

/**
 * @brief Get the type regex from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type regex, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_param_iterator_type_regex, 6);

/**
 * @brief Get the default from a report format param iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Default, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (report_format_param_iterator_fallback, 7);

/**
 * @brief Initialise a report format param option iterator.
 *
 * @param[in]  iterator             Iterator.
 * @param[in]  report_format_param  Param whose options to iterate over.
 * @param[in]  ascending            Whether to sort ascending or descending.
 * @param[in]  sort_field           Field to sort on, or NULL for "id".
 */
void
init_param_option_iterator (iterator_t* iterator,
                            report_format_param_t report_format_param,
                            int ascending, const char *sort_field)
{
  init_iterator (iterator,
                 "SELECT id, value"
                 " FROM report_format_param_options"
                 " WHERE report_format_param = %llu"
                 " ORDER BY %s %s;",
                 report_format_param,
                 sort_field ? sort_field : "id",
                 ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the value from a report format param option iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (param_option_iterator_value, 1);


/* Slaves. */

/**
 * @brief Find a slave given a UUID.
 *
 * @param[in]   uuid   UUID of slave.
 * @param[out]  slave  Slave return, 0 if succesfully failed to find slave.
 *
 * @return FALSE on success (including if failed to find slave), TRUE on error.
 */
gboolean
find_slave (const char* uuid, slave_t* slave)
{
  return find_resource ("slave", uuid, slave);
}

/**
 * @brief Find a slave for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of slave.
 * @param[out]  slave       Slave return, 0 if succesfully failed to find slave.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find slave), TRUE on error.
 */
gboolean
find_slave_with_permission (const char* uuid, slave_t* slave,
                            const char *permission)
{
  return find_resource_with_permission ("slave", uuid, slave, permission, 0);
}

/**
 * @brief Create a slave.
 *
 * @param[in]   name            Name of slave.
 * @param[in]   comment         Comment on slave.
 * @param[in]   host            Host of slave.
 * @param[in]   port            Port on host.
 * @param[in]   login           Host login name.
 * @param[in]   password        Password for \p login.
 * @param[out]  slave           NULL, or address for created slave.
 *
 * @return 0 success, 1 slave exists already, 99 permission denied, -1 error.
 */
int
create_slave (const char* name, const char* comment, const char* host,
              const char* port, const char* login, const char* password,
              slave_t* slave)
{
  gchar *quoted_name, *quoted_host, *quoted_port, *quoted_login;
  gchar *quoted_password;

  assert (name);
  assert (host);
  assert (port);
  assert (login);
  assert (password);
  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (user_may ("create_slave") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }


  /* Check whether a slave with the same name exists already. */
  if (resource_with_name_exists (name, "slave", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_name = sql_quote (name);
  quoted_host = sql_quote (host);
  quoted_port = sql_quote (port);
  quoted_login = sql_quote (login);
  quoted_password = sql_quote (password);

  if (comment)
    {
      gchar *quoted_comment = sql_quote (comment);
      sql ("INSERT INTO slaves"
           " (uuid, name, owner, comment, host, port, login, password,"
           "  creation_time, modification_time)"
           " VALUES (make_uuid (), '%s',"
           " (SELECT id FROM users WHERE users.uuid = '%s'),"
           " '%s', '%s', '%s', '%s', '%s', m_now (), m_now ());",
           quoted_name, current_credentials.uuid, quoted_comment, quoted_host,
           quoted_port, quoted_login, quoted_password);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO slaves"
         " (uuid, name, owner, comment, host, port, login, password,"
         " creation_time, modification_time)"
         " VALUES (make_uuid (), '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '%s', '', '%s', '%s', '%s', m_now (), m_now ());",
         quoted_name, current_credentials.uuid, quoted_host, quoted_port,
         quoted_login, quoted_password);

  if (slave)
    *slave = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_host);
  g_free (quoted_port);
  g_free (quoted_login);
  g_free (quoted_password);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a slave from an existing slave.
 *
 * @param[in]  name          Name of new slave. NULL to copy from existing.
 * @param[in]  comment       Comment on new slave. NULL to copy from
 *                           existing.
 * @param[in]  slave_id      UUID of existing slave.
 * @param[out] new_slave     New slave.
 *
 * @return 0 success, 1 slave exists already, 2 failed to find existing slave,
 *         -1 error.
 */
int
copy_slave (const char* name, const char* comment, const char *slave_id,
             slave_t* new_slave)
{
  return copy_resource ("slave", name, comment, slave_id,
                        "host, port, login, password",
                        1, new_slave);
}

/**
 * @brief Modify a slave.
 *
 * @param[in]   slave_id        UUID of slave.
 * @param[in]   name            Name of slave.
 * @param[in]   comment         Comment on slave.
 * @param[in]   host            Host of slave.
 * @param[in]   port            Port on host.
 * @param[in]   login           Host login name.
 * @param[in]   password        Password for \p login.
 *
 * @return 0 success, 1 failed to find slave, 2 slave with new name exists,
 *         3 slave_id required, 99 permission denied, -1 internal error.
 */
int
modify_slave (const char *slave_id, const char *name, const char *comment,
              const char *host, const char *port, const char *login,
              const char *password)
{
  gchar *quoted_name, *quoted_comment, *quoted_host, *quoted_port;
  gchar *quoted_login, *quoted_password;
  slave_t slave;

  if (slave_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_slave") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  slave = 0;
  if (find_slave_with_permission (slave_id, &slave, "modify_slave"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (slave == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a slave with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "slave", slave))
        {
          sql ("ROLLBACK;");
          return 2;
        }
    }

  quoted_name = sql_quote(name ?: "");
  quoted_comment = sql_quote (comment ? comment : "");
  quoted_host = sql_quote (host ? host : "");
  quoted_port = sql_quote (port ? port : "");
  quoted_login = sql_quote (login ? login : "");
  quoted_password = sql_quote (password ? password : "");

  sql ("UPDATE slaves SET"
       " name = '%s',"
       " comment = '%s',"
       " host = '%s',"
       " port = '%s',"
       " login = '%s',"
       " password = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       quoted_host,
       quoted_port,
       quoted_login,
       quoted_password,
       slave);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_host);
  g_free (quoted_port);
  g_free (quoted_login);
  g_free (quoted_password);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete a slave.
 *
 * @param[in]  slave_id  UUID of slave.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the slave, 2 failed to
 *         find agent, 99 permission denied, -1 error.
 */
int
delete_slave (const char *slave_id, int ultimate)
{
  slave_t slave = 0;

  sql_begin_immediate ();

  if (user_may ("delete_slave") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_slave_with_permission (slave_id, &slave, "delete_slave"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (slave == 0)
    {
      if (find_trash ("slave", slave_id, &slave))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (slave == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE slave = %llu"
                   " AND slave_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   slave))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("slave", slave, LOCATION_TRASH);
      tags_set_orphans ("slave", slave, LOCATION_TRASH);

      sql ("DELETE FROM slaves_trash WHERE id = %llu;", slave);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE slave = %llu"
                   " AND slave_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (hidden = 0 OR hidden = 1);",
                   slave))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO slaves_trash"
           "  (uuid, owner, name, comment, host, port, login, password,"
           "   creation_time, modification_time)"
           " SELECT"
           "  uuid, owner, name, comment, host, port, login, password,"
           "  creation_time, modification_time"
           " FROM slaves WHERE id = %llu;",
           slave);

      /* Update the location of the slave in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET slave = %llu,"
           "     slave_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE slave = %llu"
           " AND slave_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           slave);

      permissions_set_locations ("slave", slave,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("slave", slave,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else if (sql_int ("SELECT count(*) FROM tasks"
                    " WHERE slave = %llu"
                    " AND slave_location = " G_STRINGIFY (LOCATION_TABLE),
                    slave))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  else
    {
      permissions_set_orphans ("slave", slave, LOCATION_TABLE);
      tags_set_orphans ("slave", slave, LOCATION_TABLE);
    }

  sql ("DELETE FROM slaves WHERE id = %llu;", slave);
  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return whether a slave is writable.
 *
 * @param[in]  slave  Slave.
 *
 * @return 1 if writable, else 0.
 */
int
slave_writable (slave_t slave)
{
  return 1;
}

/**
 * @brief Return whether a trashcan slave is writable.
 *
 * @param[in]  slave  Slave.
 *
 * @return 1 if writable, else 0.
 */
int
trash_slave_writable (slave_t slave)
{
  return 1;
}

/**
 * @brief Return whether a trashcan slave is readable.
 *
 * @param[in]  slave  Slave.
 *
 * @return 1 if readable, else 0.
 */
int
trash_slave_readable (slave_t slave)
{
  char *uuid;
  slave_t found;

  if (slave == 0)
    return 0;
  uuid = slave_uuid (slave);
  if (find_trash ("slave", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Filter columns for slave iterator.
 */
#define SLAVE_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, "host", "port", "login", NULL }

/**
 * @brief Slave iterator columns.
 */
#define SLAVE_ITERATOR_COLUMNS                                          \
 {                                                                      \
   GET_ITERATOR_COLUMNS (slaves),                                       \
   { "host", NULL },                                                    \
   { "port", NULL },                                                    \
   { "login", NULL },                                                   \
   { NULL, NULL }                                                       \
 }

/**
 * @brief Slave iterator columns for trash case.
 */
#define SLAVE_ITERATOR_TRASH_COLUMNS                                    \
 {                                                                      \
   GET_ITERATOR_COLUMNS (slaves_trash),                                 \
   { "host", NULL },                                                    \
   { "port", NULL },                                                    \
   { "login", NULL },                                                   \
   { NULL, NULL }                                                       \
 }

/**
 * @brief Count the number of slaves.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of slaves filtered set.
 */
int
slave_count (const get_data_t *get)
{
  static const char *filter_columns[] = SLAVE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SLAVE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SLAVE_ITERATOR_TRASH_COLUMNS;

  return count ("slave", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a slave iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find slave, failed to find filter,
 *         -1 error.
 */
int
init_slave_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = SLAVE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SLAVE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SLAVE_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "slave",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the host of the slave from a slave iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Host of the slave or NULL if iteration is complete.
 */
DEF_ACCESS (slave_iterator_host, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the port of the slave from a slave iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Port of the slave or NULL if iteration is complete.
 */
DEF_ACCESS (slave_iterator_port, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the login of the slave from a slave iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Login of the slave or NULL if iteration is complete.
 */
DEF_ACCESS (slave_iterator_login, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Return the UUID of a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
slave_uuid (slave_t slave)
{
  return sql_string ("SELECT uuid FROM slaves WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the UUID of a slave in the trashcan.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
trash_slave_uuid (slave_t slave)
{
  return sql_string ("SELECT uuid FROM slaves_trash WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the name of a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
slave_name (slave_t slave)
{
  return sql_string ("SELECT name FROM slaves WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the name of a slave in the trashcan.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
trash_slave_name (slave_t slave)
{
  return sql_string ("SELECT name FROM slaves_trash WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the host associated with a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated host if available, else NULL.
 */
char*
slave_host (slave_t slave)
{
  return sql_string ("SELECT host FROM slaves WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the login associated with a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated login if available, else NULL.
 */
char*
slave_login (slave_t slave)
{
  return sql_string ("SELECT login FROM slaves WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the password associated with a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Newly allocated password if available, else NULL.
 */
char*
slave_password (slave_t slave)
{
  return sql_string ("SELECT password FROM slaves WHERE id = %llu;",
                     slave);
}

/**
 * @brief Return the port associated with a slave.
 *
 * @param[in]  slave  Slave.
 *
 * @return Port number on success; -1 on error.
 */
int
slave_port (slave_t slave)
{
  int ret;
  char *port = sql_string ("SELECT port FROM slaves WHERE id = %llu;",
                           slave);
  if (port == NULL)
    return -1;
  ret = atoi (port);
  free (port);
  return ret;
}

/**
 * @brief Return whether a slave is referenced by a task
 *
 * @param[in]  slave  Slave.
 *
 * @return 1 if in use, else 0.
 */
int
slave_in_use (slave_t slave)
{
  return !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE slave = %llu AND hidden = 0;",
                    slave);
}

/**
 * @brief Return whether a slave is referenced by a task
 *
 * @param[in]  slave  Slave.
 *
 * @return 1 if in use, else 0.
 */
int
trash_slave_in_use (slave_t slave)
{
  return !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE slave = %llu"
                    " AND slave_location = " G_STRINGIFY (LOCATION_TRASH),
                    slave);
}

/**
 * @brief Initialise a slave task iterator.
 *
 * Iterates over all tasks that use the slave.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  slave      Slave.
 */
void
init_slave_task_iterator (iterator_t* iterator, slave_t slave)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (current_credentials.uuid);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_tasks"));
  available = where_owned ("task", &get, 1, "any", 0, permissions);
  array_free (permissions);
  init_iterator (iterator,
                 "SELECT id, uuid, name, %s FROM tasks"
                 " WHERE slave = %llu AND hidden = 0"
                 " ORDER BY name ASC;",
                 available,
                 slave,
                 current_credentials.uuid);
  g_free (available);
}

/**
 * @brief Get the name from a slave task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (slave_task_iterator_name, 2);

/**
 * @brief Get the uuid from a slave task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The uuid of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (slave_task_iterator_uuid, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
slave_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 3);
}

/**
 * @brief Update the local task from the slave task.
 *
 * @param[in]   task         The local task.
 * @param[in]   get_report   Slave GET_REPORT response.
 * @param[out]  report       Report from get_report.
 * @param[out]  next_result  Next result counter.
 *
 * @return 0 success, -1 error.
 */
int
update_from_slave (task_t task, entity_t get_report, entity_t *report,
                   int *next_result)
{
  entity_t entity, host_start, start;
  entities_t results, hosts, entities;

  entity = entity_child (get_report, "report");
  if (entity == NULL)
    return -1;

  *report = entity_child (entity, "report");
  if (*report == NULL)
    return -1;

  /* Set the scan start time. */

  entities = (*report)->entities;
  while ((start = first_entity (entities)))
    {
      if (strcmp (entity_name (start), "scan_start") == 0)
        {
          set_task_start_time (current_scanner_task,
                               g_strdup (entity_text (start)));
          set_scan_start_time (current_report, entity_text (start));
          break;
        }
      entities = next_entities (entities);
    }

  /* Get any new results and hosts from the slave. */

  hosts = (*report)->entities;
  while ((host_start = first_entity (hosts)))
    {
      if (strcmp (entity_name (host_start), "host_start") == 0)
        {
          entity_t host;

          host = entity_child (host_start, "host");
          if (host == NULL)
            return -1;

          set_scan_host_start_time (current_report,
                                    entity_text (host),
                                    entity_text (host_start));
        }
      hosts = next_entities (hosts);
    }

  entity = entity_child (*report, "results");
  if (entity == NULL)
    return -1;

  assert (current_report);

  sql_begin_immediate ();
  results = entity->entities;
  while ((entity = first_entity (results)))
    {
      if (strcmp (entity_name (entity), "result") == 0)
        {
          entity_t host, port, nvt, threat, description;
          const char *oid;

          host = entity_child (entity, "host");
          if (host == NULL)
            goto rollback_fail;

          port = entity_child (entity, "port");
          if (port == NULL)
            goto rollback_fail;

          nvt = entity_child (entity, "nvt");
          if (nvt == NULL)
            goto rollback_fail;
          oid = entity_attribute (nvt, "oid");
          if ((oid == NULL) || (strlen (oid) == 0))
            goto rollback_fail;

          threat = entity_child (entity, "threat");
          if (threat == NULL)
            goto rollback_fail;

          description = entity_child (entity, "description");
          if (description == NULL)
            goto rollback_fail;

          {
            result_t result;

            result = make_result (task,
                                  entity_text (host),
                                  entity_text (port),
                                  oid,
                                  threat_message_type (entity_text (threat)),
                                  entity_text (description));
            if (current_report) report_add_result (current_report, result);
          }

          (*next_result)++;
        }
      results = next_entities (results);
    }
  sql ("COMMIT;");
  return 0;

 rollback_fail:
  sql ("ROLLBACK;");
  return -1;
}


/* Groups. */

/**
 * @brief Find a group given a UUID.
 *
 * @param[in]   uuid   UUID of group.
 * @param[out]  group  Group return, 0 if succesfully failed to find group.
 *
 * @return FALSE on success (including if failed to find group), TRUE on
 *         error.
 */
gboolean
find_group (const char* uuid, group_t* group)
{
  return find_resource ("group", uuid, group);
}

/**
 * @brief Find a group for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of group.
 * @param[out]  group       Group return, 0 if succesfully failed to find group.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find group), TRUE on error.
 */
gboolean
find_group_with_permission (const char* uuid, group_t* group,
                           const char *permission)
{
  return find_resource_with_permission ("group", uuid, group, permission, 0);
}

/**
 * @brief Create a group from an existing group.
 *
 * @param[in]  name       Name of new group.  NULL to copy from existing.
 * @param[in]  comment    Comment on new group.  NULL to copy from existing.
 * @param[in]  group_id   UUID of existing group.
 * @param[out] new_group_return  New group.
 *
 * @return 0 success, 1 group exists already, 2 failed to find existing
 *         group, 99 permission denied, -1 error.
 */
int
copy_group (const char *name, const char *comment, const char *group_id,
            group_t *new_group_return)
{
  int ret;
  group_t new, old;

  sql_begin_immediate ();

  ret = copy_resource_lock ("group", name, comment, group_id, NULL, 1, &new,
                            &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("INSERT INTO group_users (\"group\", \"user\")"
       " SELECT %llu, \"user\" FROM group_users"
       " WHERE \"group\" = %llu;",
       new,
       old);

  sql ("COMMIT;");
  if (new_group_return)
    *new_group_return = new;
  return 0;
}

/**
 * @brief Add users to a group.
 *
 * Caller must take care of transaction.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Group or role.
 * @param[in]  users     List of users.
 *
 * @return 0 success, 2 failed to find user, 4 user name validation failed,
 *         99 permission denied, -1 error.
 */
static int
add_users (const gchar *type, resource_t resource, const char *users)
{
  if (users)
    {
      gchar **split, **point;
      GList *added;

      /* Add each user. */

      added = NULL;
      split = g_strsplit_set (users, " ,", 0);
      point = split;

      while (*point)
        {
          user_t user;
          gchar *name;

          name = *point;

          g_strstrip (name);

          if (strcmp (name, "") == 0)
            {
              point++;
              continue;
            }

          if (g_list_find_custom (added, name, (GCompareFunc) strcmp))
            {
              point++;
              continue;
            }

          added = g_list_prepend (added, name);

          if (openvas_user_exists (name) == 0)
            {
              g_list_free (added);
              g_strfreev (split);
              return 2;
            }

          if (find_user_by_name (name, &user))
            {
              g_list_free (added);
              g_strfreev (split);
              return -1;
            }

          if (user == 0)
            {
              gchar *uuid;

              /** @todo Similar to validate_user in openvas-administrator. */
              if (g_regex_match_simple ("^[[:alnum:]-_]+$", name, 0, 0) == 0)
                {
                  g_list_free (added);
                  g_strfreev (split);
                  return 4;
                }

              uuid = openvas_user_uuid (name);

              if (uuid == NULL)
                {
                  g_list_free (added);
                  g_strfreev (split);
                  return -1;
                }

              if (sql_int ("SELECT count(*) FROM users WHERE uuid = '%s';",
                           uuid)
                  == 0)
                {
                  gchar *quoted_name;
                  quoted_name = sql_quote (name);
                  sql ("INSERT INTO users"
                       " (uuid, name, creation_time, modification_time)"
                       " VALUES"
                       " ('%s', '%s', m_now (), m_now ());",
                       uuid,
                       quoted_name);
                  g_free (quoted_name);

                  user = sql_last_insert_id ();
                }
              else
                {
                  /* find_user_by_name should have found it. */
                  assert (0);
                  g_free (uuid);
                  g_list_free (added);
                  g_strfreev (split);
                  return -1;
                }

              g_free (uuid);
            }

          if (find_user_by_name_with_permission (name, &user, "get_users"))
            {
              g_list_free (added);
              g_strfreev (split);
              return -1;
            }

          if (user == 0)
            {
              g_list_free (added);
              g_strfreev (split);
              return 99;
            }

          sql ("INSERT INTO %s_users (\"%s\", \"user\") VALUES (%llu, %llu);",
               type,
               type,
               resource,
               user);

          point++;
        }

      g_list_free (added);
      g_strfreev (split);
    }

  return 0;
}

/**
 * @brief Create a group.
 *
 * @param[in]   group_name       Group name.
 * @param[in]   comment          Comment on group.
 * @param[in]   users            Users group applies to.
 * @param[out]  group            Group return.
 *
 * @return 0 success, 1 group exists already, 2 failed to find user, 4 user
 *         name validation failed, 99 permission denied, -1 error.
 */
int
create_group (const char *group_name, const char *comment, const char *users,
              group_t* group)
{
  int ret;
  gchar *quoted_group_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (group_name);
  assert (group);

  sql_begin_immediate ();

  if (user_may ("create_group") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (resource_with_name_exists (group_name, "group", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }
  quoted_group_name = sql_quote (group_name);
  quoted_comment = comment ? sql_quote (comment) : g_strdup ("");
  sql ("INSERT INTO groups"
       " (uuid, name, owner, comment, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE uuid = '%s'),"
       "  '%s', m_now (), m_now ());",
       quoted_group_name,
       current_credentials.uuid,
       quoted_comment);
  g_free (quoted_comment);
  g_free (quoted_group_name);

  *group = sql_last_insert_id ();
  ret = add_users ("group", *group, users);

  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");

  return ret;
}

/**
 * @brief Delete a group.
 *
 * @param[in]  group_id  UUID of group.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a permission refers to the group, 2 failed
 *         to find group, 3 predefined group, 99 permission denied, -1 error.
 */
int
delete_group (const char *group_id, int ultimate)
{
  group_t group = 0;

  sql_begin_immediate ();

  if (user_may ("delete_group") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_group_with_permission (group_id, &group, "delete_group"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (group == 0)
    {
      if (find_trash ("group", group_id, &group))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (group == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      if (trash_group_in_use (group))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);

      tags_set_orphans ("group", group, LOCATION_TRASH);

      sql ("DELETE FROM group_users_trash WHERE \"group\" = %llu;", group);
      sql ("DELETE FROM groups_trash WHERE id = %llu;", group);
      sql ("COMMIT;");
      return 0;
    }

  if (group_in_use (group))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  if (ultimate == 0)
    {
      group_t trash_group;

      sql ("INSERT INTO groups_trash"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "  modification_time"
           " FROM groups WHERE id = %llu;",
           group);

      trash_group = sql_last_insert_id ();

      sql ("INSERT INTO group_users_trash"
           " (\"group\", \"user\")"
           " SELECT %llu, \"user\""
           " FROM group_users WHERE \"group\" = %llu;",
           trash_group,
           group);

      permissions_set_locations ("group", group, trash_group, LOCATION_TRASH);
      tags_set_locations ("group", group, trash_group, LOCATION_TRASH);
      permissions_set_subjects ("group", group, trash_group, LOCATION_TRASH);
    }
  else
    {
      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           group);
    }

  tags_set_orphans ("group", group, LOCATION_TABLE);

  sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group);
  sql ("DELETE FROM groups WHERE id = %llu;", group);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Return the UUID of a group.
 *
 * @param[in]  group  Group.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
group_uuid (group_t group)
{
  return sql_string ("SELECT uuid FROM groups WHERE id = %llu;",
                     group);
}

/**
 * @brief Gets users of group as a string.
 *
 * @param[in]  group  Group.
 *
 * @return Users.
 */
gchar *
group_users (group_t group)
{
  return sql_string ("SELECT group_concat (name, ', ')"
                     " FROM (SELECT users.name FROM users, group_users"
                     "       WHERE group_users.\"group\" = %llu"
                     "       AND group_users.user = users.id"
                     "       GROUP BY users.name)"
                     "      AS sub;",
                     group);
}

/**
 * @brief Check whether a group is writable.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
group_writable (group_t group)
{
  return 1;
}

/**
 * @brief Check whether a trashcan group is writable.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
trash_group_writable (group_t group)
{
  return 1;
}

/**
 * @brief Check whether a group is in use.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
group_in_use (group_t group)
{
  return 0;
}

/**
 * @brief Check whether a trashcan group is in use.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
trash_group_in_use (group_t group)
{
  return 0;
}

/**
 * @brief Filter columns for group iterator.
 */
#define GROUP_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, NULL }

/**
 * @brief Group iterator columns.
 */
#define GROUP_ITERATOR_COLUMNS                                                \
 {                                                                            \
   GET_ITERATOR_COLUMNS (groups),                                             \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Group iterator columns for trash case.
 */
#define GROUP_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                            \
   GET_ITERATOR_COLUMNS (groups_trash),                                       \
   { NULL, NULL }                                                             \
 }

/**
 * @brief Count number of groups.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of groups in grouped set.
 */
int
group_count (const get_data_t *get)
{
  static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = GROUP_ITERATOR_COLUMNS;
  static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS;
  return count ("group", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a group iterator, including observed groups.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find group, failed to find group (filt_id),
 *         -1 error.
 */
int
init_group_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = GROUP_ITERATOR_COLUMNS;
  static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "group",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Modify a group.
 *
 * @param[in]   group_id       UUID of group.
 * @param[in]   name           Name of group.
 * @param[in]   comment        Comment on group.
 * @param[in]   users          Group users.
 *
 * @return 0 success, 1 failed to find group, 2 failed to find user, 3 group_id
 *         required, 4 user name validation failed, 5 group with new name
 *         exists, 99 permission denied, -1 internal error.
 */
int
modify_group (const char *group_id, const char *name, const char *comment,
              const char *users)
{
  int ret;
  gchar *quoted_name, *quoted_comment;
  group_t group;

  assert (current_credentials.uuid);

  if (group_id == NULL)
    return 3;

  sql_begin_immediate ();

  if (user_may ("modify_group") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  group = 0;

  if (find_group_with_permission (group_id, &group, "modify_group"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (group == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a group with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "group", group))
        {
          sql ("ROLLBACK;");
          return 5;
        }
    }

  quoted_name = sql_quote(name ?: "");
  quoted_comment = sql_quote (comment ? comment : "");

  sql ("UPDATE groups SET"
       " name = '%s',"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       group);

  g_free (quoted_comment);
  g_free (quoted_name);

  sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group);

  ret = add_users ("group", group, users);

  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");

  return ret;
}


/* Permissions. */

/**
 * @brief Adjust location of resource in permissions.
 *
 * @param[in]   type  Type.
 * @param[in]   old   Resource ID in old table.
 * @param[in]   new   Resource ID in new table.
 * @param[in]   to    Destination, trash or table.
 */
void
permissions_set_locations (const char *type, resource_t old, resource_t new,
                           int to)
{
  sql ("UPDATE permissions SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
  sql ("UPDATE permissions_trash SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
}

/**
 * @brief Set permissions to orphan.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource ID.
 * @param[in]  location  Location: table or trash.
 */
void
permissions_set_orphans (const char *type, resource_t resource, int location)
{
  sql ("UPDATE permissions SET resource = -1"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
  sql ("UPDATE permissions_trash SET resource = -1"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
}

/**
 * @brief Adjust subject in permissions.
 *
 * @param[in]   type  Subject type.
 * @param[in]   old   Resource ID in old table.
 * @param[in]   new   Resource ID in new table.
 * @param[in]   to    Destination, trash or table.
 */
void
permissions_set_subjects (const char *type, resource_t old, resource_t new,
                          int to)
{
  assert (type && (strcmp (type, "group") == 0 || strcmp (type, "role") == 0));

  sql ("UPDATE permissions"
       " SET subject_location = %i, subject = %llu"
       " WHERE subject_location = %i"
       " AND subject_type = '%s'"
       " AND subject = %llu;",
       to,
       new,
       to == LOCATION_TRASH ? LOCATION_TABLE : LOCATION_TRASH,
       type,
       old);

  sql ("UPDATE permissions_trash"
       " SET subject_location = %i, subject = %llu"
       " WHERE subject_location = %i"
       " AND subject_type = '%s'"
       " AND subject = %llu;",
       to,
       new,
       to == LOCATION_TRASH ? LOCATION_TABLE : LOCATION_TRASH,
       type,
       old);
}

/**
 * @brief Find a permission given a UUID.
 *
 * @param[in]   uuid        UUID of permission.
 * @param[out]  permission  Permission return, 0 if succesfully failed to find
 *                          permission.
 *
 * @return FALSE on success (including if failed to find permission), TRUE on
 *         error.
 */
gboolean
find_permission (const char* uuid, permission_t* permission)
{
  return find_resource ("permission", uuid, permission);
}

/**
 * @brief Check args for create_permission or modify_permission.
 *
 * @param[in]   name_arg        Name of permission.
 * @param[in]   resource_type_arg  Type of resource, for special permissions.
 * @param[in]   resource_id     UUID of resource.
 * @param[in]   subject_type    Type of subject.
 * @param[in]   subject_id      UUID of subject.
 * @param[out]  name            Name return.
 * @param[out]  resource        Resource return.
 * @param[out]  resource_type   Resource type return.
 * @param[out]  resource_id     Resource ID return.
 * @param[out]  subject         Subject return.
 *
 * @return 0 success, 2 failed to find subject, 3 failed to find resource,
 *         5 error in resource, 6 error in subject, 7 error in name,
 *         8 permission on permission, 9 permission does not accept resource,
 *         99 permission denied, -1 error.
 */
static int
check_permission_args (const char *name_arg, const char *resource_type_arg,
                       const char *resource_id_arg, const char *subject_type,
                       const char *subject_id, gchar **name,
                       resource_t *resource, char **resource_type,
                       const char **resource_id, resource_t *subject)
{
  if ((name_arg == NULL)
      || ((valid_omp_command (name_arg) == 0)
          && strcasecmp (name_arg, "super"))
      || (strcasecmp (name_arg, "get_version") == 0))
    return 7;

  if (resource_id_arg
      && strcmp (resource_id_arg, "")
      && strcmp (resource_id_arg, "0")
      && (((omp_command_takes_resource (name_arg) == 0)
           && strcasecmp (name_arg, "super"))))
    return 9;

  if (resource_type_arg
      && strcmp (resource_type_arg, "group")
      && strcmp (resource_type_arg, "role")
      && strcmp (resource_type_arg, "user"))
    return 5;

  if (subject_type
      && strcmp (subject_type, "group")
      && strcmp (subject_type, "role")
      && strcmp (subject_type, "user"))
    return 6;

  if (subject_id == NULL)
    /* For now a permission must have a subject. */
    return 6;

  if (subject_id && (subject_type == NULL))
    return 6;

  assert (subject_type);

  *name = strcasecmp (name_arg, "super")
           ? g_ascii_strdown (name_arg, -1)
           : g_strdup ("Super");
  *resource = 0;
  if (resource_id_arg
      && strcmp (resource_id_arg, "")
      && strcmp (resource_id_arg, "0"))
    {
      *resource_type = strcasecmp (*name, "super")
                        ? omp_command_type (*name)
                        : g_strdup (resource_type_arg);

      if (*resource_type == NULL)
        {
          g_free (*name);
          return 3;
        }

      if (find_resource (*resource_type, resource_id_arg, resource))
        {
          g_free (*name);
          g_free (*resource_type);
          return -1;
        }

      if (*resource == 0)
        {
          g_free (*name);
          g_free (*resource_type);
          return 3;
        }

      *resource_id = resource_id_arg;
    }
  else
    {
      *resource_id = NULL;
      *resource_type = NULL;
    }

  if (strcasecmp (*name, "super") == 0)
    {
      if (*resource == 0)
        {
          g_free (*name);
          g_free (*resource_type);
          return 3;
        }

      if ((user_is_owner (*resource_type, *resource_id) == 0)
          && (user_can_super_everyone (current_credentials.uuid) == 0))
        {
          g_free (*name);
          g_free (*resource_type);
          return 99;
        }
    }

  /* For simplicity refuse to make permissions on permissions. */
  if (*resource && strcasestr (*name, "permission"))
    {
      g_free (*name);
      g_free (*resource_type);
      return 8;
    }

  /* Ensure the user may grant this permission. */

  if (((*resource == 0) || strcasecmp (*name, "super") == 0)
      && (user_can_everything (current_credentials.uuid) == 0))
    {
      g_free (*name);
      g_free (*resource_type);
      return 99;
    }

  *subject = 0;
  assert (subject_id);
  if (*resource)
    {
      /* Permission on a particular resource.  Only need read access to the
       * subject. */

      if (find_resource_with_permission (subject_type,
                                         subject_id,
                                         subject,
                                         NULL, /* GET permission. */
                                         0))   /* Trash. */
        {
          g_free (*name);
          g_free (*resource_type);
          return -1;
        }
    }
  else
    {
      gchar *permission;

      /* Command level permission.  Must have write access to the subject. */

      /* However, modification of the predefined roles is forbidden. */
      if (subject_id
          && strcmp (subject_type, "role") == 0
          && role_is_predefined_id (subject_id))
        return 99;

      permission = g_strdup_printf ("modify_%s", subject_type);
      if (find_resource_with_permission (subject_type,
                                         subject_id,
                                         subject,
                                         permission,
                                         0)) /* Trash. */
        {
          g_free (*name);
          g_free (*resource_type);
          g_free (permission);
          return -1;
        }
      g_free (permission);
    }

  if (*subject == 0)
    {
      g_free (*name);
      g_free (*resource_type);
      return 2;
    }

  return 0;
}

/**
 * @brief Create a permission.
 *
 * @param[in]   name_arg        Name of permission.
 * @param[in]   comment         Comment on permission.
 * @param[in]   resource_type_arg  Type of resource, for special permissions.
 * @param[in]   resource_id_arg    UUID of resource.
 * @param[in]   subject_type    Type of subject.
 * @param[in]   subject_id      UUID of subject.
 * @param[out]  permission      Permission.
 *
 * @return 0 success, 2 failed to find subject, 3 failed to find resource,
 *         5 error in resource, 6 error in subject, 7 error in name,
 *         8 permission on permission, 9 permission does not accept resource,
 *         99 permission denied, -1 internal error.
 */
int
create_permission (const char *name_arg, const char *comment,
                   const char *resource_type_arg, const char *resource_id_arg,
                   const char *subject_type, const char *subject_id,
                   permission_t *permission)
{
  int ret;
  gchar *name, *quoted_name, *quoted_comment, *resource_type;
  resource_t resource, subject;
  const char *resource_id;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (user_may ("create_permission") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  ret = check_permission_args (name_arg, resource_type_arg, resource_id_arg,
                               subject_type, subject_id, &name, &resource,
                               &resource_type, &resource_id, &subject);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  assert (subject);
  assert ((resource_id == resource_id_arg) || (resource_id == NULL));

  quoted_name = sql_quote (name);
  g_free (name);
  quoted_comment = sql_quote (comment ? comment : "");

  sql ("INSERT INTO permissions"
       " (uuid, owner, name, comment, resource_type, resource_uuid, resource,"
       "  resource_location, subject_type, subject, subject_location,"
       "  creation_time, modification_time)"
       " VALUES"
       " (make_uuid (),"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '%s', '%s', '%s', %llu, " G_STRINGIFY (LOCATION_TABLE) ","
       "  %s%s%s, %llu, " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());",
       current_credentials.uuid,
       quoted_name,
       quoted_comment,
       resource_id ? resource_type : "",
       resource_id ? resource_id : "",
       resource,
       subject_id ? "'" : "",
       subject_id ? subject_type : "NULL",
       subject_id ? "'" : "",
       subject);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (resource_type);

  if (permission)
    *permission = sql_last_insert_id ();

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a permission from an existing permission.
 *
 * @param[in]  comment     Comment on new permission.  NULL to copy from existing.
 * @param[in]  permission_id   UUID of existing permission.
 * @param[out] new_permission  New permission.
 *
 * @return 0 success, 1 permission exists already, 2 failed to find existing
 *         permission, 99 permission denied, -1 error.
 */
int
copy_permission (const char* comment, const char *permission_id,
                 permission_t* new_permission)
{
  int ret;
  permission_t permission, new, old;
  char *subject_type, *name;
  resource_t subject;

  sql_begin_immediate ();

  permission = 0;
  if (find_permission (permission_id, &permission))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (permission == 0)
    {
      sql ("ROLLBACK;");
      return 2;
    }

  /* Check if the subject is a predefined role. */
  subject_type = permission_subject_type (permission);
  subject = permission_subject (permission);
  if (subject_type
      && strcmp (subject_type, "role") == 0
      && subject
      && role_is_predefined (subject))
    {
      free (subject_type);
      sql ("ROLLBACK;");
      return 99;
    }
  free (subject_type);

  /* Refuse to copy Super On Everyone. */
  name = permission_name (permission);
  if ((strcmp (name, "Super") == 0)
      && (permission_resource (permission) == 0))
    {
      free (name);
      sql ("ROLLBACK;");
      return 99;
    }
  free (name);

  ret = copy_resource_lock ("permission", NULL, comment, permission_id,
                            "resource_type, resource, resource_uuid,"
                            " resource_location, subject_type, subject,"
                            " subject_location",
                            0, &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("COMMIT;");
  if (new_permission) *new_permission = new;
  return 0;

}

/**
 * @brief Return the UUID of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
permission_uuid (permission_t permission)
{
  return sql_string ("SELECT uuid FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return the resource of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Resource if there is one, else 0.
 */
static resource_t
permission_resource (permission_t permission)
{
  resource_t resource;
  sql_int64 (&resource,
             "SELECT resource FROM permissions WHERE id = %llu;",
             permission);
  return resource;
}

/**
 * @brief Return the name of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated name if available, else NULL.
 */
char *
permission_name (permission_t permission)
{
  return sql_string ("SELECT name FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return the subject type of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated subject type if available, else NULL.
 */
char *
permission_subject_type (permission_t permission)
{
  return sql_string ("SELECT subject_type FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return the subject of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Subject if there is one, else 0.
 */
resource_t
permission_subject (permission_t permission)
{
  resource_t subject;
  sql_int64 (&subject,
             "SELECT subject FROM permissions WHERE id = %llu;",
             permission);
  return subject;
}

/**
 * @brief Return the UUID of the subject of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated subject ID if available, else NULL.
 */
char *
permission_subject_id (permission_t permission)
{
  return sql_string ("SELECT subject_id FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return the resource type of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated resource type if available, else NULL.
 */
char *
permission_resource_type (permission_t permission)
{
  return sql_string ("SELECT resource_type FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return the UUID of the resource of a permission.
 *
 * @param[in]  permission  Permission.
 *
 * @return Newly allocated resource ID if available, else NULL.
 */
char *
permission_resource_id (permission_t permission)
{
  return sql_string ("SELECT resource_id FROM permissions WHERE id = %llu;",
                     permission);
}

/**
 * @brief Return whether a permission is predefined.
 *
 * @param[in]  permission  Permission.
 *
 * @return 1 if predefined, else 0.
 */
int
permission_is_predefined (permission_t permission)
{
  return !!sql_int ("SELECT COUNT (*) FROM permissions"
                    " WHERE id = %llu"
                    " AND (uuid = '" PERMISSION_UUID_ADMIN_EVERYTHING "'"
                    "      OR (subject_type = 'role'"
                    "          AND resource = 0"
                    "          AND subject"
                    "              IN (SELECT id FROM roles"
                    "                  WHERE uuid = '" ROLE_UUID_ADMIN "'"
                    "                  OR uuid = '" ROLE_UUID_GUEST "'"
                    "                  OR uuid = '" ROLE_UUID_INFO "'"
                    "                  OR uuid = '" ROLE_UUID_MONITOR "'"
                    "                  OR uuid = '" ROLE_UUID_USER "'"
                    "                  OR uuid = '" ROLE_UUID_SUPER_ADMIN "'"
                    "                  OR uuid = '" ROLE_UUID_OBSERVER "')))",
                    permission);
}

/**
 * @brief Return whether a permission is in use.
 *
 * @param[in]  permission  Permission.
 *
 * @return 1 if in use, else 0.
 */
int
permission_in_use (permission_t permission)
{
  return 0;
}

/**
 * @brief Return whether a trashcan permission is referenced by a task.
 *
 * @param[in]  permission  Permission.
 *
 * @return 1 if in use, else 0.
 */
int
trash_permission_in_use (permission_t permission)
{
  return 0;
}

/**
 * @brief Return whether a permission is writable.
 *
 * @param[in]  permission  Permission.
 *
 * @return 1 if writable, else 0.
 */
int
permission_writable (permission_t permission)
{
  if (permission_is_predefined (permission))
    return 0;
  return 1;
}

/**
 * @brief Return whether a trashcan permission is writable.
 *
 * @param[in]  permission  Permission.
 *
 * @return 1 if writable, else 0.
 */
int
trash_permission_writable (permission_t permission)
{
  return 1;
}

/**
 * @brief Filter columns for permission iterator.
 */
#define PERMISSION_ITERATOR_FILTER_COLUMNS                               \
 { GET_ITERATOR_FILTER_COLUMNS, "type", "resource_uuid", "subject_type", \
   "_subject", "_resource", "subject_uuid", NULL }

/**
 * @brief Permission iterator columns.
 */
#define PERMISSION_ITERATOR_COLUMNS                                          \
 {                                                                           \
   GET_ITERATOR_COLUMNS (permissions),                                       \
   { "resource_type", "type" },                                              \
   { "resource_uuid", NULL },                                                \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN resource_type = '' OR resource_type IS NULL"                     \
     " THEN ''"                                                              \
     " ELSE resource_name (resource_type, resource_uuid, resource_location)" \
     " END)",                                                                \
     "_resource"                                                             \
   },                                                                        \
   { "resource_location = " G_STRINGIFY (LOCATION_TRASH), NULL },            \
   { "resource = -1", NULL },                                                \
   { "subject_type", NULL },                                                 \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN subject_type = 'user'"                                           \
     " THEN (SELECT uuid FROM users WHERE users.id = subject)"               \
     " WHEN subject_type = 'group'"                                          \
     "      AND subject_location = " G_STRINGIFY (LOCATION_TRASH)            \
     " THEN (SELECT uuid FROM groups_trash"                                  \
     "       WHERE groups_trash.id = subject)"                               \
     " WHEN subject_type = 'group'"                                          \
     " THEN (SELECT uuid FROM groups WHERE groups.id = subject)"             \
     " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH)                \
     " THEN (SELECT uuid FROM roles_trash"                                   \
     "       WHERE roles_trash.id = subject)"                                \
     " ELSE (SELECT uuid FROM roles WHERE roles.id = subject)"               \
     " END)",                                                                \
     "subject_uuid"                                                          \
   },                                                                        \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN subject_type = 'user'"                                           \
     " THEN (SELECT name FROM users WHERE users.id = subject)"               \
     " WHEN subject_type = 'group'"                                          \
     "      AND subject_location = " G_STRINGIFY (LOCATION_TRASH)            \
     " THEN (SELECT name FROM groups_trash"                                  \
     "       WHERE groups_trash.id = subject)"                               \
     " WHEN subject_type = 'group'"                                          \
     " THEN (SELECT name FROM groups WHERE groups.id = subject)"             \
     " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH)                \
     " THEN (SELECT name FROM roles_trash"                                   \
     "       WHERE roles_trash.id = subject)"                                \
     " ELSE (SELECT name FROM roles WHERE roles.id = subject)"               \
     " END)",                                                                \
     "_subject"                                                              \
   },                                                                        \
   { "subject_location = " G_STRINGIFY (LOCATION_TRASH), NULL },             \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Permission iterator columns for trash case.
 */
#define PERMISSION_ITERATOR_TRASH_COLUMNS                                    \
 {                                                                           \
   GET_ITERATOR_COLUMNS (permissions_trash),                                 \
   { "resource_type", "type" },                                              \
   { "resource_uuid", NULL },                                                \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN resource_type = '' OR resource_type IS NULL"                     \
     " THEN ''"                                                              \
     " ELSE resource_name (resource_type, resource_uuid, resource_location)" \
     " END)",                                                                \
     "_resource"                                                             \
   },                                                                        \
   { "resource_location = " G_STRINGIFY (LOCATION_TRASH), NULL },            \
   { "resource = -1", NULL },                                                \
   { "subject_type", NULL },                                                 \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN subject_type = 'user'"                                           \
     " THEN (SELECT uuid FROM users WHERE users.id = subject)"               \
     " WHEN subject_type = 'group'"                                          \
     "      AND subject_location = " G_STRINGIFY (LOCATION_TRASH)            \
     " THEN (SELECT uuid FROM groups_trash"                                  \
     "       WHERE groups_trash.id = subject)"                               \
     " WHEN subject_type = 'group'"                                          \
     " THEN (SELECT uuid FROM groups WHERE groups.id = subject)"             \
     " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH)                \
     " THEN (SELECT uuid FROM roles_trash"                                   \
     "       WHERE roles_trash.id = subject)"                                \
     " ELSE (SELECT uuid FROM roles WHERE roles.id = subject)"               \
     " END)",                                                                \
     "subject_uuid"                                                          \
   },                                                                        \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN subject_type = 'user'"                                           \
     " THEN (SELECT name FROM users WHERE users.id = subject)"               \
     " WHEN subject_type = 'group'"                                          \
     "      AND subject_location = " G_STRINGIFY (LOCATION_TRASH)            \
     " THEN (SELECT name FROM groups_trash"                                  \
     "       WHERE groups_trash.id = subject)"                               \
     " WHEN subject_type = 'group'"                                          \
     " THEN (SELECT name FROM groups WHERE groups.id = subject)"             \
     " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH)                \
     " THEN (SELECT name FROM roles_trash"                                   \
     "       WHERE roles_trash.id = subject)"                                \
     " ELSE (SELECT name FROM roles WHERE roles.id = subject)"               \
     " END)",                                                                \
     "_subject"                                                              \
   },                                                                        \
   { "subject_location = " G_STRINGIFY (LOCATION_TRASH), NULL },             \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Count number of permissions.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of permissions in filtered set.
 */
int
permission_count (const get_data_t *get)
{
  static const char *filter_columns[] = PERMISSION_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = PERMISSION_ITERATOR_COLUMNS;
  static column_t trash_columns[] = PERMISSION_ITERATOR_TRASH_COLUMNS;

  return count ("permission", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a permission iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_permission_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = PERMISSION_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = PERMISSION_ITERATOR_COLUMNS;
  static column_t trash_columns[] = PERMISSION_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "permission",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the type of resource from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_resource_type, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the UUID of the resource from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_resource_uuid, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the name of the resource from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_resource_name, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Return the permission resource location.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether the resource is in the trashcan
 */
int
permission_iterator_resource_in_trash (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
}

/**
 * @brief Check if the permission resource has been deleted.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether the resource has been deleted.
 */
int
permission_iterator_resource_orphan (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the type of subject from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Type, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_subject_type, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the subject UUID from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_subject_uuid, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the subject name from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.
 */
DEF_ACCESS (permission_iterator_subject_name, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Return the permission subject location.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether the subject is in the trashcan
 */
int
permission_iterator_subject_in_trash (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
}

/**
 * @brief Find a permission with a given permission, given a UUID.
 *
 * @param[in]   uuid        UUID of permission.
 * @param[out]  resource    Permission return, 0 if succesfully failed to find
 *                          permission.
 * @param[in]   permission  Required permission, for example "delete".
 *
 * @return FALSE on success (including if failed to find permission), TRUE on
 *         error.
 */
gboolean
find_permission_with_permission (const char *uuid, permission_t *resource,
                                 const char *permission)
{
  gchar *quoted_uuid = sql_quote (uuid);
  if (user_has_access_uuid ("permission", quoted_uuid, permission, 0) == 0)
    {
      g_free (quoted_uuid);
      *resource = 0;
      return FALSE;
    }
  switch (sql_int64 (resource,
                     "SELECT id FROM permissions WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Delete a permission.
 *
 * @param[in]  permission_id  UUID of permission.
 * @param[in]  ultimate       Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 2 failed to find permission, 3 predefined permission,
 *         99 permission denied, -1 error.
 */
int
delete_permission (const char *permission_id, int ultimate)
{
  permission_t permission = 0;
  char *subject_type;
  resource_t subject;

  if (strcasecmp (permission_id, PERMISSION_UUID_ADMIN_EVERYTHING) == 0)
    return 3;

  sql_begin_immediate ();

  if (user_may ("delete_permission") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_permission_with_permission (permission_id, &permission,
                                       "delete_permission"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (permission == 0)
    {
      if (find_trash ("permission", permission_id, &permission))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (permission == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      tags_set_orphans ("permission", permission, LOCATION_TRASH);
      sql ("DELETE FROM permissions_trash WHERE id = %llu;", permission);
      sql ("COMMIT;");
      return 0;
    }

  /* Check if the subject is a predefined role. */
  subject_type = permission_subject_type (permission);
  subject = permission_subject (permission);
  if (subject_type
      && strcmp (subject_type, "role") == 0
      && subject
      && role_is_predefined (subject))
    {
      free (subject_type);
      sql ("ROLLBACK;");
      return 99;
    }
  free (subject_type);

  if (ultimate == 0)
    {
      sql ("INSERT INTO permissions_trash"
          " (uuid, owner, name, comment, resource_type, resource,"
          "  resource_uuid, resource_location, subject_type, subject,"
          "  subject_location, creation_time, modification_time)"
          " SELECT uuid, owner, name, comment, resource_type, resource,"
          "  resource_uuid, resource_location, subject_type, subject,"
          "  subject_location, creation_time, modification_time"
          " FROM permissions"
          " WHERE id = %llu;",
          permission);
      tags_set_locations ("permission", permission,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    tags_set_orphans ("permission", permission, LOCATION_TABLE);

  sql ("DELETE FROM permissions WHERE id = %llu;", permission);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Downcase a string in place.
 *
 * @param[in]   string  String.
 *
 * @return String, downcased.
 */
gchar *
strdown (gchar *string)
{
  gchar *point;
  if (string && *string)
    for (point = string; *point; point++) *point = g_ascii_tolower (*point);
  return string;
}

/**
 * @brief Modify a permission.
 *
 * @param[in]   permission_id   UUID of permission.
 * @param[in]   name_arg        Name of permission.
 * @param[in]   comment         Comment on permission.
 * @param[in]   resource_id_arg UUID of resource.
 * @param[in]   resource_type   Type of resource, for Super permissions.
 * @param[in]   subject_type    Type of subject.
 * @param[in]   subject_id      UUID of subject.
 *
 * @return 0 success, 1 failed to find permission, 2 failed to find subject,
 *         3 failed to find resource, 4 permission_id required, 5 error in
 *         resource, 6 error in subject, 7 error in name, 8 name required to
 *         find resource, 9 permission does not accept resource, 99 permission
 *         denied, -1 internal error.
 */
int
modify_permission (const char *permission_id, const char *name_arg,
                   const char *comment, const char *resource_id_arg,
                   const char *resource_type_arg, const char *subject_type,
                   const char *subject_id)
{
  int ret;
  permission_t permission;
  resource_t resource, subject;
  char *existing_subject_type, *new_name, *new_resource_type;
  char *new_resource_id, *new_subject_type, *new_subject_id;
  gchar *name, *quoted_name, *resource_type;
  const char *resource_id;

  if (permission_id == NULL)
    return 4;

  sql_begin_immediate ();

  if (user_may ("modify_permission") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  permission = 0;
  if (find_permission (permission_id, &permission))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (permission == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check if it's a command-level permission on a predefined role. */
  existing_subject_type = permission_subject_type (permission);
  resource = permission_resource (permission);
  subject = permission_subject (permission);
  if (resource == 0
      && existing_subject_type
      && strcmp (existing_subject_type, "role") == 0
      && subject
      && role_is_predefined (subject))
    {
      free (existing_subject_type);
      sql ("ROLLBACK;");
      return 99;
    }

  if (comment)
    {
      gchar *quoted_comment;

      quoted_comment = sql_quote (comment);
      sql ("UPDATE permissions SET"
           " comment = '%s',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           quoted_comment,
           permission);
      g_free (quoted_comment);
    }

  new_name = name_arg ? NULL : permission_name (permission);
  if (resource_type_arg && resource_id_arg && strcmp (resource_id_arg, "0"))
    /* Given a resource. */
    new_resource_type = NULL;
  else if (resource_id_arg && (strcmp (resource_id_arg, "0") == 0))
    /* User wants to clear the resource. */
    new_resource_type = NULL;
  else
    {
      new_resource_type = permission_resource_type (permission);
      if (new_resource_type
          && strcmp (new_resource_type, "group")
          && strcmp (new_resource_type, "role")
          && strcmp (new_resource_type, "user"))
        /* Type will come from command name. */
        new_resource_type = NULL;
    }

  new_resource_id = (resource_id_arg && strcmp (resource_id_arg, ""))
                     ? NULL
                     : permission_resource_id (permission);
  new_subject_type = subject_type ? NULL : existing_subject_type;
  new_subject_id = subject_id ? NULL : permission_subject_id (permission);

  ret = check_permission_args
         (new_name ? new_name : name_arg,
          new_resource_type ? new_resource_type : resource_type_arg,
          new_resource_id ? new_resource_id : resource_id_arg,
          new_subject_type ? new_subject_type : subject_type,
          new_subject_id ? new_subject_id : subject_id,
          &name,
          &resource,
          &resource_type,
          &resource_id,
          &subject);

  free (new_name);

  if (ret)
    {
      free (new_resource_type);
      free (new_resource_id);
      free (existing_subject_type);
      free (new_subject_id);
      sql ("ROLLBACK;");
      return ret;
    }

  assert (subject);
  assert ((resource_id == new_resource_id)
          || (resource_id == resource_id_arg)
          || (resource_id == NULL));

  quoted_name = sql_quote (name);
  g_free (name);

  sql ("UPDATE permissions SET"
       " name = '%s',"
       " resource_type = '%s',"
       " resource_uuid = '%s',"
       " resource = %llu,"
       " resource_location = " G_STRINGIFY (LOCATION_TABLE) ","
       " subject_type = '%s',"
       " subject = %llu,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       (resource_id && resource_type) ? resource_type : "",
       resource_id ? resource_id : "",
       resource,
       new_subject_type ? new_subject_type : subject_type,
       subject,
       permission);

  g_free (quoted_name);
  free (new_resource_type);
  free (new_resource_id);
  free (existing_subject_type);
  free (new_subject_id);

  sql ("COMMIT;");

  return 0;
}


/* Port lists. */

/**
 * @brief Find a port list given a UUID.
 *
 * @param[in]   uuid       UUID of port_list.
 * @param[out]  port_list  Port_List return, 0 if succesfully failed to find
 *                         port_list.
 *
 * @return FALSE on success (including if failed to find port_list), TRUE on error.
 */
gboolean
find_port_list (const char* uuid, port_list_t* port_list)
{
  gchar *quoted_uuid = sql_quote (uuid);
  if (user_owns_uuid ("port_list", quoted_uuid, 0) == 0)
    {
      g_free (quoted_uuid);
      *port_list = 0;
      return FALSE;
    }
  switch (sql_int64 (port_list,
                     "SELECT id FROM port_lists WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *port_list = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Find a port list for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of port list.
 * @param[out]  port_list   Port list return, 0 if succesfully failed to find
 *                          port list.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find port_list), TRUE on
 *         error.
 */
gboolean
find_port_list_with_permission (const char* uuid, port_list_t* port_list,
                                const char *permission)
{
  return find_resource_with_permission ("port_list", uuid, port_list,
                                        permission, 0);
}

/**
 * @brief Return the UUID of the port list of a port_range.
 *
 * @param[in]  port_range  Port Range UUID.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
port_range_port_list_uuid (const char *port_range)
{
  gchar *quoted_port_range;
  char *ret;

  quoted_port_range = sql_quote (port_range);
  if (sql_int ("SELECT count (*) FROM port_ranges WHERE uuid = '%s';",
               quoted_port_range))
    ret = sql_string ("SELECT uuid FROM port_lists"
                      " WHERE id = (SELECT port_list FROM port_ranges"
                      "             WHERE uuid = '%s');",
                      quoted_port_range);
  else
    ret = NULL;
  g_free (quoted_port_range);
  return ret;
}

/**
 * @brief Find a port range given a UUID.
 *
 * @param[in]   uuid        UUID of port_range.
 * @param[out]  port_range  Port range return, 0 if succesfully failed to find
 *                          port range.
 * @param[in]   permission  UUID of port_range.
 *
 * @return FALSE on success (including if failed to find port range), TRUE on
 *         error.
 */
gboolean
find_port_range_with_permission (const char *uuid, port_range_t *port_range,
                                 const char *permission)
{
  char *port_list_uuid;
  gchar *quoted_uuid;
  port_list_t port_list;

  assert (current_credentials.uuid);

  *port_range = 0;

  if (uuid == NULL)
    return TRUE;

  port_list_uuid = port_range_port_list_uuid (uuid);
  if (port_list_uuid == NULL)
    return TRUE;

  if (find_port_list_with_permission (port_list_uuid, &port_list, permission)
      || port_list == 0)
    {
      g_free (port_list_uuid);
      return TRUE;
    }
  g_free (port_list_uuid);

  quoted_uuid = sql_quote (uuid);
  switch (sql_int64 (port_range,
                     "SELECT id FROM port_ranges WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *port_range = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_uuid);
        return TRUE;
        break;
    }

  g_free (quoted_uuid);
  return FALSE;
}

/**
 * @brief Compare two ranges by type then start.
 *
 * @param[in]  one  First range.
 * @param[in]  two  Second range.
 */
static int
range_compare (gconstpointer one, gconstpointer two)
{
  range_t *range_one, *range_two;

  range_one = *((range_t**) one);
  range_two = *((range_t**) two);

  if (range_one->type > range_two->type)
    return 1;

  if (range_one->type < range_two->type)
    return -1;

  if (range_one->start > range_two->start)
    return 1;

  if (range_one->start < range_two->start)
    return -1;

  return 0;
}

/**
 * @brief Sort and merge ranges.
 *
 * @param[in]  ranges  Array of port ranges of type range_t.
 */
static void
ranges_sort_merge (array_t *ranges)
{
  if (ranges->len > 1)
    {
      int index;
      range_t *last_range;

      /* Sort by type then start. */

      g_ptr_array_sort (ranges, range_compare);

      /* Merge overlaps. */

      last_range = (range_t*) g_ptr_array_index (ranges, 0);
      for (index = 1; index < ranges->len; )
        {
          range_t *range;

          range = (range_t*) g_ptr_array_index (ranges, index);
          if (range == NULL)
            break;

          if (range->type == last_range->type
              && range->start <= last_range->end)
            {
              if (range->end > last_range->end)
                last_range->end = range->end;
              /* This moves everything else up into the space. */
              g_ptr_array_remove_index (ranges, index);
            }
          else
            {
              index++;
              last_range = range;
            }
        }
    }
}

/**
 * @brief Create a port list, with database locked.
 *
 * Caller must lock the database.
 *
 * @param[in]   quoted_id       SQL quoted UUID, or NULL.
 * @param[in]   quoted_name     SQL quoted name of port list.
 * @param[in]   comment         Comment on port list.
 * @param[in]   ranges          Port ranges of port list.
 * @param[out]  port_list       Created port list.
 *
 * @return 0 success.
 */
int
create_port_list_lock (const char *quoted_id, const char *quoted_name,
                       const char *comment, array_t *ranges,
                       port_list_t* port_list)
{
  gchar *quoted_comment;
  range_t *range;
  int index;

  assert (comment);

  quoted_comment = sql_quote (comment);
  if (quoted_id)
    sql ("INSERT INTO port_lists"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " ('%s', (SELECT id FROM users WHERE uuid = '%s'), '%s',"
         "  '%s', m_now (), m_now ());",
         quoted_id,
         current_credentials.uuid,
         quoted_name,
         quoted_comment);
  else
    sql ("INSERT INTO port_lists"
         " (uuid, owner, name, comment, creation_time, modification_time)"
         " VALUES"
         " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s',"
         "  '%s', m_now (), m_now ());",
         current_credentials.uuid,
         quoted_name,
         quoted_comment);
  g_free (quoted_comment);

  *port_list = sql_last_insert_id ();

  ranges_sort_merge (ranges);
  array_terminate (ranges);
  index = 0;
  while ((range = (range_t*) g_ptr_array_index (ranges, index++)))
    sql ("INSERT INTO port_ranges"
         " (uuid, port_list, type, start, \"end\", comment, exclude)"
         " VALUES"
         " (make_uuid (), %llu, %i, %i, %i, '', %i);",
         *port_list,
         range->type,
         range->start,
         range->end,
         range->exclude);
  return 0;
}

/**
 * @brief Create a port list having a unique name.
 *
 * Caller must provide transaction.
 *
 * @param[in]   name            Name of port list.
 * @param[in]   comment         Comment on port list.
 * @param[in]   port_range      Traditional OTP style port range.  NULL for "default".
 * @param[out]  port_list       Created port list.
 *
 * @return 0 success, 4 error in port range.
 */
int
create_port_list_unique (const char *name, const char *comment,
                         const char* port_range, port_list_t* port_list)
{
  gchar *quoted_name;
  array_t *ranges;
  int suffix, ret;

  assert (current_credentials.uuid);

  if (port_range == NULL || (strcmp (port_range, "default") == 0))
    {
      if (find_port_list (PORT_LIST_UUID_DEFAULT, port_list)
          || (*port_list == 0))
        return -1;
      return 0;
    }

  if (validate_port_range (port_range))
    return 4;

  ranges = port_range_ranges (port_range);

  /* Check whether a port list with the same name exists already. */
  suffix = 1;
  quoted_name = sql_quote (name);
  while (resource_with_name_exists (quoted_name, "port_list", 0))
    {
      gchar *new_name;
      new_name = g_strdup_printf ("%s %i", name, suffix);
      g_free (quoted_name);
      quoted_name = sql_quote (new_name);
      g_free (new_name);
      suffix++;
    }

  ret = create_port_list_lock (NULL, quoted_name, comment, ranges, port_list);

  array_free (ranges);

  return ret;
}

/**
 * @brief Create a port_list.
 *
 * @param[in]   id                ID of port list.  Only used with \p ranges.
 * @param[in]   name              Name of port list.
 * @param[in]   comment           Comment on port list.
 * @param[in]   port_ranges       OMP port range string.
 * @param[in]   ranges            Array of port ranges of type range_t.
 *                                Overrides port_ranges.
 * @param[out]  port_list_return  Created port list.
 *
 * @return 0 success, 1 port list exists already, 4 error in port_ranges,
 *         99 permission denied, -1 error.
 */
int
create_port_list (const char* id, const char* name, const char* comment,
                  const char* port_ranges, array_t *ranges,
                  port_list_t* port_list_return)
{
  gchar *quoted_name;
  port_list_t port_list;
  int ret;

  assert (current_credentials.uuid);

  if (ranges)
    {
      int suffix;
      gchar *quoted_id;

      if (id == NULL)
        return -1;

      sql_begin_immediate ();

      if (user_may ("create_port_list") == 0)
        {
          sql ("ROLLBACK;");
          return 99;
        }

      /* Check whether this port list exists already. */

      quoted_id = sql_quote (id);
      if (sql_int ("SELECT COUNT(*) FROM port_lists"
                   " WHERE uuid = '%s'"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT users.id FROM users WHERE users.uuid = '%s')));",
                   quoted_id,
                   current_credentials.uuid))
        {
          g_free (quoted_id);
          sql ("ROLLBACK;");
          return 1;
        }

      if (sql_int ("SELECT COUNT(*) FROM port_lists_trash"
                   " WHERE uuid = '%s'"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT users.id FROM users WHERE users.uuid = '%s')));",
                   quoted_id,
                   current_credentials.uuid))
        {
          g_free (quoted_id);
          sql ("ROLLBACK;");
          return 2;
        }

      /* Ensure the name is unique. */
      quoted_name = sql_quote (name);
      suffix = 1;
      while (resource_with_name_exists (quoted_name, "port_list", 0))
        {
          gchar *new_name;
          g_free (quoted_name);
          new_name = g_strdup_printf ("%s %i", name, suffix++);
          quoted_name = sql_quote (new_name);
          g_free (new_name);
        }

      ret = create_port_list_lock (quoted_id, quoted_name,
                                   comment ? comment : "", ranges, &port_list);
      g_free (quoted_name);
      if (ret)
        {
          sql ("ROLLBACK;");
          return ret;
        }

      if (port_list_return)
        *port_list_return = port_list;

      sql ("COMMIT;");
      return 0;
    }

  if (port_ranges == NULL)
    port_ranges = "default";

  if (validate_port_range (port_ranges))
    return 4;

  sql_begin_immediate ();

  if (user_may ("create_port_list") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }


  /* Check whether a port_list with the same name exists already. */
  if (resource_with_name_exists (name, "port_list", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_name = sql_quote (name);
  if (port_ranges == NULL || (strcmp (port_ranges, "default") == 0))
    {
      gchar *quoted_comment, *quoted_name;

      quoted_name = sql_quote (name);
      quoted_comment = sql_quote (comment ? comment : "");
      sql ("INSERT INTO port_lists"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s',"
           "  '%s', m_now (), m_now ());",
           current_credentials.uuid,
           quoted_name,
           quoted_comment);
      g_free (quoted_comment);
      g_free (quoted_name);

      port_list = sql_last_insert_id ();
      make_port_ranges_openvas_default (port_list);
    }
  else
    {
      ranges = port_range_ranges (port_ranges);
      ret = create_port_list_lock (NULL, quoted_name, comment ? comment : "",
                                   ranges, &port_list);
      array_free (ranges);
      if (ret)
        {
          g_free (quoted_name);
          sql ("ROLLBACK;");
          return ret;
        }
    }

  if (port_list_return)
    *port_list_return = port_list;

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create Port List from an existing Port List.
 *
 * @param[in]  name             Name of new Port List. NULL to copy from
 *                              existing.
 * @param[in]  comment          Comment on new Port List. NULL to copy from
 *                              existing.
 * @param[in]  port_list_id     UUID of existing Port List.
 * @param[out] new_port_list    New Port List.
 *
 * @return 0 success, 1 Port List exists already, 2 failed to find existing
 *         Port List, 99 permission denied, -1 error.
 */
int
copy_port_list (const char* name, const char* comment,
                const char* port_list_id, port_list_t* new_port_list)
{
  int ret;
  port_list_t new, old;

  sql_begin_immediate ();

  ret = copy_resource_lock ("port_list", name, comment, port_list_id, NULL, 1,
                            &new, &old);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  /* Copy port ranges. */

  sql ("INSERT INTO port_ranges "
       " (uuid, port_list, type, start, \"end\", comment, exclude)"
       " SELECT make_uuid(), %llu, type, start, \"end\", comment, exclude"
       "  FROM port_ranges WHERE port_list = %llu;",
       new,
       old);

  sql ("COMMIT;");
  if (new_port_list) *new_port_list = new;
  return 0;
}

/**
 * @brief Modify a Port List.
 *
 * @param[in]   port_list_id    UUID of Port List.
 * @param[in]   name            Name of Port List.
 * @param[in]   comment         Comment on Port List.
 *
 * @return 0 success, 1 failed to find port list, 2 port list with new name,
 *         exists, 3 port_list_id required, 99 permission denied, -1 internal
 *         error.
 */
int
modify_port_list (const char *port_list_id, const char *name,
                  const char *comment)
{
  gchar *quoted_name, *quoted_comment;
  port_list_t port_list;

  if (port_list_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_port_list") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  port_list = 0;
  if (find_port_list_with_permission (port_list_id, &port_list,
                                      "modify_port_list"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (port_list == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a Port List with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "port_list", port_list))
        {
          sql ("ROLLBACK;");
          return 2;
        }
    }

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");

  sql ("UPDATE port_lists SET"
       " name = '%s',"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       port_list);

  g_free (quoted_comment);
  g_free (quoted_name);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a port range in a port list.
 *
 * @param[in]   port_list_id      Port list UUID.
 * @param[in]   type              Type.
 * @param[in]   start             Start port.
 * @param[in]   end               End port.
 * @param[in]   comment           Comment.
 * @param[out]  port_range_return  Created port range.
 *
 * @return 0 success, 1 syntax error in start, 2 syntax error in end, 3 failed
 *         to find port list, 4 syntax error in type, 5 port list in use,
 *         6 new range overlaps an existing range, 99 permission denied,
 *         -1 error.
 */
int
create_port_range (const char *port_list_id, const char *type,
                   const char *start, const char *end, const char *comment,
                   port_range_t *port_range_return)
{
  int first, last;
  port_list_t port_list;
  port_protocol_t port_type;
  gchar *quoted_comment;

  first = atoi (start);
  if (first < 1 || first > 65535)
    return 1;

  last = atoi (end);
  if (last < 1 || last > 65535)
    return 2;

  if (strcasecmp (type, "TCP") == 0)
    port_type = PORT_PROTOCOL_TCP;
  else if (strcasecmp (type, "UDP") == 0)
    port_type = PORT_PROTOCOL_UDP;
  else
    return 4;

  if (last < first)
    {
      int tem;
      tem = first;
      first = last;
      last = tem;
    }

  sql_begin_immediate ();

  if (user_may ("create_port_range") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  port_list = 0;

  if (find_port_list (port_list_id, &port_list))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (port_list == 0)
    {
      sql ("ROLLBACK;");
      return 3;
    }

  if (port_list_in_use (port_list))
    {
      sql ("ROLLBACK;");
      return 5;
    }

  if (sql_int ("SELECT count (*) FROM port_ranges"
               " WHERE port_list = %llu"
               " AND type = %i"
               " AND ((start <= %i AND end >= %i)"
               "      OR (start <= %i AND end >= %i)"
               "      OR (start >= %i AND start <= %i)"
               "      OR (end >= %i AND end <= %i))",
               port_list,
               port_type,
               first,
               first,
               last,
               last,
               first,
               last,
               first,
               last))
    {
      sql ("ROLLBACK;");
      return 6;
    }

  quoted_comment = comment ? sql_quote (comment) : g_strdup ("");
  sql ("INSERT INTO port_ranges"
       " (uuid, port_list, type, start, \"end\", comment, exclude)"
       " VALUES"
       " (make_uuid (), %llu, %i, %i, %i, '', 0);",
       port_list, port_type, first, last, quoted_comment);
  g_free (quoted_comment);

  if (port_range_return)
    *port_range_return = sql_last_insert_id ();

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete a port list.
 *
 * @param[in]  port_list_id  UUID of port_list.
 * @param[in]  ultimate      Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a target refers to the port list, 2 failed
 *         to find port list, 3 predefined port list, 99 permission denied,
 *         -1 error.
 */
int
delete_port_list (const char *port_list_id, int ultimate)
{
  port_list_t port_list = 0;

  sql_begin_immediate ();

  if (user_may ("delete_port_list") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_port_list_with_permission (port_list_id, &port_list,
                                      "delete_port_list"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (port_list
      && port_list_is_predefined (port_list))
    {
      sql ("ROLLBACK;");
      return 3;
    }

  if (port_list == 0)
    {
      if (find_trash ("port_list", port_list_id, &port_list))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (port_list == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by a target in the trashcan. */
      if (sql_int ("SELECT count(*) FROM targets_trash"
                   " WHERE port_range = %llu"
                   " AND port_list_location"
                   " = " G_STRINGIFY (LOCATION_TRASH) ";",
                   port_list))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("port_list", port_list, LOCATION_TRASH);
      tags_set_orphans ("port_list", port_list, LOCATION_TRASH);

      sql ("DELETE FROM port_ranges_trash WHERE port_list = %llu;", port_list);
      sql ("DELETE FROM port_lists_trash WHERE id = %llu;", port_list);
      sql ("COMMIT;");
      return 0;
    }

  if (sql_int ("SELECT count(*) FROM targets"
               " WHERE port_range = %llu;",
               port_list))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  if (ultimate == 0)
    {
      port_list_t trash_port_list;

      sql ("INSERT INTO port_lists_trash"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time"
           " FROM port_lists WHERE id = %llu;",
           port_list);

      trash_port_list = sql_last_insert_id ();

      sql ("INSERT INTO port_ranges_trash"
           " (uuid, port_list, type, start, \"end\", comment, exclude)"
           " SELECT uuid, %llu, type, start, \"end\", comment, exclude"
           " FROM port_ranges WHERE port_list = %llu;",
           trash_port_list,
           port_list);

      /* Update the location of the port_list in any trashcan targets. */
      sql ("UPDATE targets_trash"
           " SET port_range = %llu,"
           "     port_list_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE port_range = %llu"
           " AND port_list_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_port_list,
           port_list);

      permissions_set_locations ("port_list", port_list, trash_port_list,
                                 LOCATION_TRASH);
      tags_set_locations ("port_list", port_list, trash_port_list,
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("port_list", port_list, LOCATION_TABLE);
      tags_set_orphans ("port_list", port_list, LOCATION_TABLE);
    }

  sql ("DELETE FROM port_ranges WHERE port_list = %llu;", port_list);
  sql ("DELETE FROM port_lists WHERE id = %llu;", port_list);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Delete a port range.
 *
 * @param[in]  port_range_id  UUID of port_range.
 * @param[in]  dummy          Dummy arg to match other delete functions.
 *
 * @return 0 success, 2 failed to find port range, 3 port range is part of
 *         predefined port list, 99 permission denied, -1 error.
 */
int
delete_port_range (const char *port_range_id, int dummy)
{
  port_range_t port_range = 0;

  sql_begin_immediate ();

  if (user_may ("delete_port_range") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_port_range_with_permission (port_range_id, &port_range,
                                       "delete_port_range"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (port_range == 0)
    {
      sql ("ROLLBACK;");
      return 2;
    }

  if (sql_int
       ("SELECT count (*) FROM port_lists WHERE"
        " id = (SELECT port_list FROM port_ranges"
        "       WHERE port_ranges.id = %llu)"
        " AND"
        " (uuid = '" PORT_LIST_UUID_DEFAULT "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_100 "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_1000 "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP_UDP "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_2012 "'"
        "  OR uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_UDP_2012 "'"
        "  OR uuid = '" PORT_LIST_UUID_NMAP_5_51_TOP_2000_TOP_100 "');",
        port_range))
    return 3;

  sql ("DELETE FROM port_ranges WHERE id = %llu;", port_range);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Filter columns for Port List iterator.
 */
#define PORT_LIST_ITERATOR_FILTER_COLUMNS                           \
 { GET_ITERATOR_FILTER_COLUMNS,  "total", "tcp", "udp", NULL }

/**
 * @brief Port List iterator columns.
 */
#define PORT_LIST_ITERATOR_COLUMNS                                 \
 {                                                                 \
   GET_ITERATOR_COLUMNS (port_lists),                              \
   {                                                               \
     /* COUNT ALL ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges WHERE port_list = port_lists.id)",         \
     "total"                                                       \
   },                                                              \
   {                                                               \
     /* COUNT TCP ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges WHERE port_list = port_lists.id"           \
     "                  AND   type = 0)",                          \
     "tcp"                                                         \
   },                                                              \
   {                                                               \
     /* COUNT UDP ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges WHERE port_list = port_lists.id"           \
     "                  AND   type = 1)",                          \
     "udp"                                                         \
   },                                                              \
   { NULL, NULL }                                                  \
 }

/**
 * @brief Port List iterator columns for trash case.
 */
#define PORT_LIST_ITERATOR_TRASH_COLUMNS                           \
 {                                                                 \
   GET_ITERATOR_COLUMNS (port_lists_trash),                        \
   {                                                               \
     /* COUNT ALL ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges_trash"                                     \
     " WHERE port_list = port_lists_trash.id)",                    \
     "total"                                                       \
   },                                                              \
   {                                                               \
     /* COUNT TCP ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges_trash"                                     \
     " WHERE port_list = port_lists_trash.id AND type = 0)",       \
     "tcp"                                                         \
   },                                                              \
   {                                                               \
     /* COUNT UDP ports */                                         \
     "(SELECT"                                                     \
     " sum ((CASE"                                                 \
     "       WHEN \"end\" IS NULL THEN start ELSE \"end\""         \
     "       END)"                                                 \
     "      - start"                                               \
     "      + 1)"                                                  \
     " FROM port_ranges_trash"                                     \
     " WHERE port_list = port_lists_trash.id AND type = 1)",       \
     "udp"                                                         \
   },                                                              \
   { NULL, NULL }                                                  \
 }

/**
 * @brief Count the number of Port Lists.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of Port Lists filtered set.
 */
int
port_list_count (const get_data_t *get)
{
  static const char *filter_columns[] = PORT_LIST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = PORT_LIST_ITERATOR_COLUMNS;
  static column_t trash_columns[] = PORT_LIST_ITERATOR_TRASH_COLUMNS;

  return count ("port_list", get, columns, trash_columns, filter_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Initialise a Port List  iterator, including observed Port Lists.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find Port List, 2 failed to find filter,
 *         -1 error.
 */
int
init_port_list_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = PORT_LIST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = PORT_LIST_ITERATOR_COLUMNS;
  static column_t trash_columns[] = PORT_LIST_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "port_list",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the port count from a port_list iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Port count.
 */
int
port_list_iterator_count_all (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT);
}

/**
 * @brief Get the TCP port count from a port_list iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return TCP port count.
 */
int
port_list_iterator_count_tcp (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the UDP port count from a port_list iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UDP port count.
 */
int
port_list_iterator_count_udp (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
}

/**
 * @brief Return the UUID of a port_list.
 *
 * @param[in]  port_list  Port_List.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
port_list_uuid (port_list_t port_list)
{
  return sql_string ("SELECT uuid FROM port_lists WHERE id = %llu;",
                     port_list);
}

/**
 * @brief Return the UUID of a port_range.
 *
 * @param[in]  port_range  Port_Range.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
port_range_uuid (port_range_t port_range)
{
  return sql_string ("SELECT uuid FROM port_ranges WHERE id = %llu;",
                     port_range);
}

int
port_list_is_predefined (port_list_t port_list)
{
  return !!sql_int
            ("SELECT COUNT (*) FROM port_lists"
             " WHERE id = %llu AND"
             " (uuid = '" PORT_LIST_UUID_DEFAULT "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_TCP "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_100 "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_TCP_NMAP_5_51_TOP_1000 "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_PRIV_TCP_UDP "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_2012 "'"
             "  OR uuid = '" PORT_LIST_UUID_NMAP_5_51_TOP_2000_TOP_100 "'"
             "  OR uuid = '" PORT_LIST_UUID_ALL_IANA_TCP_UDP_2012 "');",
             port_list);
}

/**
 * @brief Return whether a port_list is in use by a task.
 *
 * @param[in]  port_list  Port_List.
 *
 * @return 1 if in use, else 0.
 */
int
port_list_in_use (port_list_t port_list)
{
  if (port_list_is_predefined (port_list))
    return 1;

  return sql_int ("SELECT count(*) FROM targets"
                  " WHERE port_range = %llu",
                  port_list);
}

/**
 * @brief Check whether a trashcan Port List is in use.
 *
 * @param[in]  port_list Port List.
 *
 * @return 1 yes, 0 no.
 */
int
trash_port_list_in_use (port_list_t port_list)
{
  return (sql_int ("SELECT count (*) FROM targets_trash"
                   " WHERE port_range = %llu"
                   " AND port_list_location = "
                   G_STRINGIFY (LOCATION_TRASH) ";",
                   port_list) > 0);
}

/**
 * @brief Check whether a Port List is writable.
 *
 * @param[in]  port_list  Port List.
 *
 * @return 1 yes, 0 no.
 */
int
port_list_writable (port_list_t port_list)
{
  if (port_list_is_predefined (port_list))
    return 0;
  return (port_list_in_use (port_list) == 0);
}

/**
 * @brief Check whether a trashcan Port List is writable.
 *
 * @param[in]  port_list  Port List.
 *
 * @return 1 yes, 0 no.
 */
int
trash_port_list_writable (port_list_t port_list)
{
  return (trash_port_list_in_use (port_list) == 0);
}

/**
 * @brief Return whether a trashcan port list is readable.
 *
 * @param[in]  port_list_id  Port list UUID.
 *
 * @return 1 if readable, else 0.
 */
int
trash_port_list_readable_uuid (const gchar *port_list_id)
{
  port_list_t found;

  if (port_list_id == NULL)
    return 0;
  if (find_trash ("port_list", port_list_id, &found))
    return 0;
  return found > 0;
}

/**
 * @brief Initialise a port_range iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  port_list   Port list.
 * @param[in]  trash       Whether port_list is in the trashcan.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for type then start.
 */
void
init_port_range_iterator (iterator_t* iterator, port_list_t port_list,
                          int trash, int ascending, const char* sort_field)
{
  assert (current_credentials.uuid);

  if (port_list)
    init_iterator (iterator,
                   "SELECT uuid, comment, start, \"end\", type, exclude"
                   " FROM port_ranges%s"
                   " WHERE port_list = %llu"
                   " AND"
                   " (((SELECT owner FROM port_lists%s WHERE id = port_list)"
                   "   IS NULL)"
                   "  OR ((SELECT owner FROM port_lists%s WHERE id = port_list)"
                   "      = (SELECT id FROM users WHERE users.uuid = '%s'))"
                   "  OR (CAST (%i AS boolean)"
                   "      AND (" USER_MAY ("port_list") ")))"
                   " ORDER BY %s %s;",
                   trash ? "_trash" : "",
                   port_list,
                   trash ? "_trash" : "",
                   trash ? "_trash" : "",
                   current_credentials.uuid,
                   trash ? 0 : 1,
                   current_credentials.uuid,
                   current_credentials.uuid,
                   current_credentials.uuid,
                   "get_port_lists",
                   "get_port_lists",
                   "get_port_lists",
                   "get_port_lists",
                   sort_field ? sort_field : "type, CAST (start AS INTEGER)",
                   ascending ? "ASC" : "DESC");
  else
    init_iterator (iterator,
                   "SELECT uuid, comment, start, end, type, exclude"
                   " FROM port_ranges%s"
                   " WHERE"
                   " (((SELECT owner FROM port_lists%s WHERE id = port_list)"
                   "   IS NULL)"
                   "  OR ((SELECT owner FROM port_lists%s WHERE id = port_list)"
                   "      = (SELECT id FROM users WHERE users.uuid = '%s'))"
                   "  OR (CAST (%i AS boolean)"
                   "      AND (" USER_MAY ("port_list") ")))"
                   " ORDER BY %s %s;",
                   trash ? "_trash" : "",
                   trash ? "_trash" : "",
                   trash ? "_trash" : "",
                   current_credentials.uuid,
                   trash ? 0 : 1,
                   current_credentials.uuid,
                   current_credentials.uuid,
                   current_credentials.uuid,
                   "get_port_lists",
                   "get_port_lists",
                   "get_port_lists",
                   "get_port_lists",
                   sort_field ? sort_field : "type, CAST (start AS INTEGER)",
                   ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the UUID from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (port_range_iterator_uuid, 0);

/**
 * @brief Get the comment from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The comment of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
port_range_iterator_comment (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return "";
  ret = iterator_string (iterator, 1);
  return ret ? ret : "";
}

/**
 * @brief Get the comment from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The comment of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (port_range_iterator_start, 2);

/**
 * @brief Get the comment from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The comment of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (port_range_iterator_end, 3);

/**
 * @brief Get the type from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
port_range_iterator_type (iterator_t* iterator)
{
  if (iterator->done) return "";
  switch ((port_protocol_t) iterator_int (iterator, 4))
    {
      case PORT_PROTOCOL_TCP:
        return "tcp";
      case PORT_PROTOCOL_UDP:
        return "udp";
      default:
        return "";
    }
}

/**
 * @brief Get the type from a port range iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the range, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
port_protocol_t
port_range_iterator_type_int (iterator_t* iterator)
{
  if (iterator->done) return PORT_PROTOCOL_OTHER;
  return (port_protocol_t) iterator_int (iterator, 4);
}

/**
 * @brief Initialise a port list target iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  port_list   Port list.
 * @param[in]  ascending   Whether to sort ascending or descending.
 */
void
init_port_list_target_iterator (iterator_t* iterator, port_list_t port_list,
                                int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (port_list);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_targets"));
  available = where_owned ("target", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT uuid, name, %s FROM targets"
                 " WHERE port_range = %llu"
                 " ORDER BY name %s;",
                 available,
                 port_list,
                 ascending ? "ASC" : "DESC");

  g_free (available);
}

/**
 * @brief Get the UUID from a port list target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the target, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (port_list_target_iterator_uuid, 0);

/**
 * @brief Get the name from a port list target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the target, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (port_list_target_iterator_name, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
port_list_target_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}


/* Roles. */

/**
 * @brief Create a role from an existing role.
 *
 * @param[in]  name       Name of new role.  NULL to copy from existing.
 * @param[in]  comment    Comment on new role.  NULL to copy from existing.
 * @param[in]  role_id    UUID of existing role.
 * @param[out] new_role_return  New role.
 *
 * @return 0 success, 1 role exists already, 2 failed to find existing
 *         role, 99 permission denied, -1 error.
 */
int
copy_role (const char *name, const char *comment, const char *role_id,
           role_t *new_role_return)
{
  int ret;
  role_t new_role, old_role;

  sql_begin_immediate ();

  if (user_may ("create_role") == 0)
    return 99;

  if (role_can_super_everyone (role_id))
    return 99;

  ret = copy_resource_lock ("role", name, comment, role_id, NULL, 1, &new_role,
                            &old_role);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("INSERT INTO permissions"
       " (uuid, owner, name, comment, resource_type, resource_uuid, resource,"
       "  resource_location, subject_type, subject, subject_location,"
       "  creation_time, modification_time)"
       " SELECT make_uuid (),"
       "        (SELECT id FROM users WHERE users.uuid = '%s'),"
       "        name, comment, resource_type,"
       "        resource_uuid, resource, resource_location, subject_type, %llu,"
       "        subject_location, m_now (), m_now ()"
       " FROM permissions"
       " WHERE subject_type = 'role'"
       " AND subject = %llu"
       " AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       " AND resource = 0;",
       current_credentials.uuid,
       new_role,
       old_role);

  sql ("COMMIT;");
  if (new_role_return)
    *new_role_return = new_role;
  return 0;
}

/**
 * @brief Create a role.
 *
 * @param[in]   role_name        Role name.
 * @param[in]   comment          Comment on role.
 * @param[in]   users            Users role applies to.
 * @param[in]   role             Role return.
 *
 * @return 0 success, 1 role exists already, 2 failed to find user, 4 user
 *         name validation failed, 99 permission denied, -1 error.
 */
int
create_role (const char *role_name, const char *comment, const char *users,
             role_t* role)
{
  int ret;
  gchar *quoted_role_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (role_name);
  assert (role);

  sql_begin_immediate ();

  if (user_may ("create_role") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (resource_with_name_exists (role_name, "role", 0))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_role_name = sql_quote (role_name);
  quoted_comment = comment ? sql_quote (comment) : g_strdup ("");
  sql ("INSERT INTO roles"
       " (uuid, name, owner, comment, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', m_now (), m_now ());",
       quoted_role_name,
       current_credentials.uuid,
       quoted_comment);
  g_free (quoted_comment);
  g_free (quoted_role_name);

  *role = sql_last_insert_id ();
  ret = add_users ("role", *role, users);

  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");

  return ret;
}

/**
 * @brief Return whether a role is predefined.
 *
 * @param[in]  role  Role.
 *
 * @return 1 if predefined, else 0.
 */
int
role_is_predefined (role_t role)
{
  return sql_int ("SELECT COUNT (*) FROM roles"
                  " WHERE id = %llu"
                  " AND (uuid = '" ROLE_UUID_ADMIN "'"
                  "      OR uuid = '" ROLE_UUID_GUEST "'"
                  "      OR uuid = '" ROLE_UUID_MONITOR "'"
                  "      OR uuid = '" ROLE_UUID_INFO "'"
                  "      OR uuid = '" ROLE_UUID_USER "'"
                  "      OR uuid = '" ROLE_UUID_SUPER_ADMIN "'"
                  "      OR uuid = '" ROLE_UUID_OBSERVER "');",
                  role)
         != 0;
}

/**
 * @brief Return whether a role is predefined.
 *
 * @param[in]  uuid  UUID of role.
 *
 * @return 1 if predefined, else 0.
 */
int
role_is_predefined_id (const char *uuid)
{
  return uuid && ((strcmp (uuid, ROLE_UUID_ADMIN) == 0)
                  || (strcmp (uuid, ROLE_UUID_GUEST) == 0)
                  || (strcmp (uuid, ROLE_UUID_MONITOR) == 0)
                  || (strcmp (uuid, ROLE_UUID_INFO) == 0)
                  || (strcmp (uuid, ROLE_UUID_USER) == 0)
                  || (strcmp (uuid, ROLE_UUID_SUPER_ADMIN) == 0)
                  || (strcmp (uuid, ROLE_UUID_OBSERVER) == 0));
}

/**
 * @brief Delete a role.
 *
 * @param[in]  role_id   UUID of role.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the role, 2 failed
 *         to find role, 3 predefined role, -1 error.
 */
int
delete_role (const char *role_id, int ultimate)
{
  role_t role = 0;

  sql_begin_immediate ();

  if (user_may ("delete_role") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_role_with_permission (role_id, &role, "delete_role"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (role == 0)
    {
      if (find_trash ("role", role_id, &role))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (role == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      if (trash_role_in_use (role))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);

      tags_set_orphans ("role", role, LOCATION_TRASH);

      sql ("DELETE FROM role_users_trash WHERE role = %llu;", role);
      sql ("DELETE FROM roles_trash WHERE id = %llu;", role);
      sql ("COMMIT;");
      return 0;
    }

  if (role_is_predefined (role))
    {
      sql ("ROLLBACK;");
      return 3;
    }

  if (role_in_use (role))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  if (ultimate == 0)
    {
      role_t trash_role;

      sql ("INSERT INTO roles_trash"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time"
           " FROM roles WHERE id = %llu;",
           role);

      trash_role = sql_last_insert_id ();

      sql ("INSERT INTO role_users_trash"
           " (\"role\", \"user\")"
           " SELECT \"role\", \"user\""
           " FROM role_users WHERE \"role\" = %llu;",
           role);

      permissions_set_locations ("role", role, trash_role, LOCATION_TRASH);
      tags_set_locations ("role", role, trash_role, LOCATION_TRASH);
      permissions_set_subjects ("role", role, trash_role, LOCATION_TRASH);
    }
  else
    {
      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           role);
      tags_set_orphans ("role", role, LOCATION_TABLE);
    }

  sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role);
  sql ("DELETE FROM roles WHERE id = %llu;", role);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Find a role given a UUID.
 *
 * @param[in]   uuid   UUID of role.
 * @param[out]  role  Role return, 0 if succesfully failed to find role.
 *
 * @return FALSE on success (including if failed to find role), TRUE on
 *         error.
 */
gboolean
find_role (const char* uuid, role_t* role)
{
  return find_resource ("role", uuid, role);
}

/**
 * @brief Find a role for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of role.
 * @param[out]  role        Role return, 0 if succesfully failed to find role.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find role), TRUE on error.
 */
gboolean
find_role_with_permission (const char* uuid, role_t* role,
                           const char *permission)
{
  return find_resource_with_permission ("role", uuid, role, permission, 0);
}

/**
 * @brief Find a role given a name.
 *
 * @param[in]   name  A role name.
 * @param[out]  role  Role return, 0 if succesfully failed to find role.
 *
 * @return FALSE on success (including if failed to find role), TRUE on error.
 */
static gboolean
find_role_by_name (const char* name, role_t *role)
{
  return find_resource_by_name ("role", name, role);
}

/**
 * @brief Gets UUID of role.
 *
 * @param[in]  role  Role.
 *
 * @return Users.
 */
gchar *
role_uuid (role_t role)
{
  return sql_string ("SELECT uuid FROM roles WHERE id = %llu;",
                     role);
}

/**
 * @brief Gets users of role as a string.
 *
 * @param[in]  role  Role.
 *
 * @return Users.
 */
gchar *
role_users (role_t role)
{
  return sql_string ("SELECT group_concat (name, ', ')"
                     " FROM (SELECT users.name FROM users, role_users"
                     "       WHERE role_users.role = %llu"
                     "       AND role_users.user = users.id"
                     "       GROUP BY users.name)"
                     "      AS sub;",
                     role);
}

/**
 * @brief Check whether a role is writable.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
role_writable (role_t role)
{
  if (role_is_predefined (role))
    return 0;
  return 1;
}

/**
 * @brief Check whether a trashcan role is writable.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
trash_role_writable (role_t role)
{
  return 1;
}

/**
 * @brief Check whether a role is in use.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
role_in_use (role_t role)
{
  return 0;
}

/**
 * @brief Check whether a trashcan role is in use.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
trash_role_in_use (role_t role)
{
  return 0;
}

/**
 * @brief Modify a role.
 *
 * @param[in]   role_id  UUID of role.
 * @param[in]   name     Name of role.
 * @param[in]   comment  Comment on role.
 * @param[in]   users    Role users.
 *
 * @return 0 success, 1 failed to find role, 2 failed to find user, 3 role_id
 *         required, 4 user name validation failed, 5 role with new name
 *         exists, 99 permission denied, -1 internal error.
 */
int
modify_role (const char *role_id, const char *name, const char *comment,
             const char *users)
{
  int ret;
  gchar *quoted_name, *quoted_comment;
  role_t role;

  assert (current_credentials.uuid);

  if (role_id == NULL)
    return 3;

  sql_begin_immediate ();

  if (user_may ("modify_role") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  role = 0;

  if (find_role_with_permission (role_id, &role, "modify_role"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (role == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* Check whether a role with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "role", role))
        {
          sql ("ROLLBACK;");
          return 5;
        }
    }

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");

  sql ("UPDATE roles SET"
       " name = '%s',"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       role);

  g_free (quoted_comment);
  g_free (quoted_name);

  sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role);

  ret = add_users ("role", role, users);

  if (ret)
    sql ("ROLLBACK;");
  else
    sql ("COMMIT;");

  return ret;
}

/**
 * @brief Filter columns for role iterator.
 */
#define ROLE_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, NULL }

/**
 * @brief Role iterator columns.
 */
#define ROLE_ITERATOR_COLUMNS                                                \
 {                                                                           \
   GET_ITERATOR_COLUMNS (roles),                                             \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Role iterator columns for trash case.
 */
#define ROLE_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                           \
   GET_ITERATOR_COLUMNS (roles_trash),                                       \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Count number of roles.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of roles in roleed set.
 */
int
role_count (const get_data_t *get)
{
  static const char *extra_columns[] = ROLE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ROLE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS;
  return count ("role", get, columns, trash_columns, extra_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a role iterator, including observed roles.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find role, failed to find role (filt_id),
 *         -1 error.
 */
int
init_role_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = ROLE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ROLE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "role",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}


/* Filters. */

/**
 * @brief Find a filter given a UUID.
 *
 * @param[in]   uuid    UUID of filter.
 * @param[out]  filter  Filter return, 0 if succesfully failed to find filter.
 *
 * @return FALSE on success (including if failed to find filter), TRUE on error.
 */
gboolean
find_filter (const char* uuid, filter_t* filter)
{
  return find_resource ("filter", uuid, filter);
}

/**
 * @brief Find a filter for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of filter.
 * @param[out]  filter      Filter return, 0 if succesfully failed to find
 *                          filter.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find filter), TRUE on error.
 */
gboolean
find_filter_with_permission (const char* uuid, filter_t* filter,
                             const char *permission)
{
  return find_resource_with_permission ("filter", uuid, filter, permission, 0);
}

/**
 * @brief Return the UUID of a filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
filter_uuid (filter_t filter)
{
  return sql_string ("SELECT uuid FROM filters WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the UUID of a trashcan filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
trash_filter_uuid (filter_t filter)
{
  return sql_string ("SELECT uuid FROM filters_trash WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the name of a filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return name of filter.
 */
char*
filter_name (filter_t filter)
{
  return sql_string ("SELECT name FROM filters WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the name of a trashcan filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return name of filter.
 */
char*
trash_filter_name (filter_t filter)
{
  return sql_string ("SELECT name FROM filters_trash WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the term of a filter.
 *
 * @param[in]  uuid  Filter UUID.
 *
 * @return Newly allocated term if available, else NULL.
 */
gchar*
filter_term (const char *uuid)
{
  gchar *quoted_uuid, *ret;
  quoted_uuid = sql_quote (uuid);
  ret = sql_string ("SELECT term FROM filters WHERE uuid = '%s';",
                    quoted_uuid);
  g_free (quoted_uuid);
  return ret;
}

/**
 * @brief Return the value of a column keyword of a filter term.
 *
 * @param[in]  term    Filter term.
 * @param[in]  column  Column name.
 *
 * @return Value of column keyword if one exists, else NULL.
 */
gchar*
filter_term_value (const char *term, const char *column)
{
  keyword_t **point;
  array_t *split;

  if (term == NULL)
    return NULL;

  split = split_filter (term);
  point = (keyword_t**) split->pdata;
  while (*point)
    {
      keyword_t *keyword;

      keyword = *point;
      if (keyword->column
          && ((strcasecmp (keyword->column, column) == 0)
              || (keyword->column[0] == '_'
                  && (strcasecmp (keyword->column + 1, column) == 0))))
        {
          gchar *ret = g_strdup (keyword->string);
          filter_free (split);
          return ret;
        }
      point++;
    }
  filter_free (split);
  return NULL;
}

/**
 * @brief Create a filter.
 *
 * @param[in]   name            Name of filter.
 * @param[in]   comment         Comment on filter.
 * @param[in]   type            Type of resource.
 * @param[in]   term            Filter term.
 * @param[in]   make_name_unique  Whether to make name unique.
 * @param[out]  filter          Created filter.
 *
 * @return 0 success, 1 filter exists already, 2 error in type, 99 permission
 *         denied.
 */
int
create_filter (const char *name, const char *comment, const char *type,
               const char *term, int make_name_unique, filter_t* filter)
{
  gchar *quoted_name, *quoted_comment, *quoted_term, *clean_term;

  assert (current_credentials.uuid);

  if (type && strlen (type))
    {
      type = type_db_name (type);
      if (type == NULL || !valid_type (type))
        return 2;
    }

  sql_begin_immediate ();

  if (user_may ("create_filter") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (make_name_unique)
    {
      int suffix;
      /* Ensure the name is unique. */
      quoted_name = sql_quote (name);
      suffix = 1;
      while (resource_with_name_exists (quoted_name, "filter", 0))
        {
          gchar *new_name;
          g_free (quoted_name);
          new_name = g_strdup_printf ("%s %i", name, suffix++);
          quoted_name = sql_quote (new_name);
          g_free (new_name);
        }
    }
  else
    {
      quoted_name = sql_quote (name ?: "");
      if (resource_with_name_exists (name, "filter", 0))
        {
          g_free (quoted_name);
          sql ("ROLLBACK;");
          return 1;
        }
    }

  clean_term = manage_clean_filter (term ? term : "");
  quoted_term = sql_quote (clean_term);
  g_free (clean_term);

  if (comment)
    {
      quoted_comment = sql_quote (comment);
      sql ("INSERT INTO filters"
           " (uuid, name, owner, comment, type, term, creation_time,"
           "  modification_time)"
           " VALUES (make_uuid (), '%s',"
           " (SELECT id FROM users WHERE users.uuid = '%s'),"
           " '%s', %s%s%s, '%s', m_now (), m_now ());",
           quoted_name,
           current_credentials.uuid,
           quoted_comment,
           type ? "lower ('" : "",
           type ? type : "''",
           type ? "')" : "",
           quoted_term);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO filters"
         " (uuid, name, owner, comment, type, term, creation_time,"
         "  modification_time)"
         " VALUES (make_uuid (), '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '', %s%s%s, '%s', m_now (), m_now ());",
         quoted_name,
         current_credentials.uuid,
         type ? "lower ('" : "",
         type ? type : "''",
         type ? "')" : "",
         quoted_term);

  if (filter)
    *filter = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_term);

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Create a filter from an existing filter.
 *
 * @param[in]  name        Name of new filter.  NULL to copy from existing.
 * @param[in]  comment     Comment on new filter.  NULL to copy from existing.
 * @param[in]  filter_id   UUID of existing filter.
 * @param[out] new_filter  New filter.
 *
 * @return 0 success, 1 filter exists already, 2 failed to find existing
 *         filter, -1 error.
 */
int
copy_filter (const char* name, const char* comment, const char *filter_id,
             filter_t* new_filter)
{
  return copy_resource ("filter", name, comment, filter_id, "term, type",
                        1, new_filter);
}

/**
 * @brief Delete a filter.
 *
 * @param[in]  filter_id  UUID of filter.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the filter, 2 failed
 *         to find filter, 3 predefined filter, 99 permission denied, -1 error.
 */
int
delete_filter (const char *filter_id, int ultimate)
{
  gchar *quoted_filter_id;
  filter_t filter = 0;

  sql_begin_immediate ();

  if (user_may ("delete_filter") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_filter_with_permission (filter_id, &filter, "delete_filter"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (filter == 0)
    {
      if (find_trash ("filter", filter_id, &filter))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (filter == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      /* Check if it's in use by an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alerts_trash"
                   " WHERE filter = %llu"
                   " AND filter_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   filter))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_orphans ("filter", filter, LOCATION_TRASH);
      tags_set_orphans ("filter", filter, LOCATION_TRASH);

      sql ("DELETE FROM filters_trash WHERE id = %llu;", filter);
      sql ("COMMIT;");
      return 0;
    }

  if (sql_int ("SELECT count(*) FROM alerts"
               " WHERE filter = %llu;",
               filter))
    {
      sql ("ROLLBACK;");
      return 1;
    }

  quoted_filter_id = sql_quote (filter_id);
  sql ("DELETE FROM settings WHERE name LIKE '%% Filter' AND value = '%s';",
       quoted_filter_id);
  g_free (quoted_filter_id);

  if (ultimate == 0)
    {
      sql ("INSERT INTO filters_trash"
           " (uuid, owner, name, comment, type, term, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, type, term, creation_time,"
           "  modification_time"
           " FROM filters WHERE id = %llu;",
           filter);

      /* Update the location of the filter in any trashcan alerts. */
      sql ("UPDATE alerts_trash"
           " SET filter = %llu,"
           "     filter_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE filter = %llu"
           " AND filter_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           filter);

      permissions_set_locations ("filter", filter,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("filter", filter,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("filter", filter, LOCATION_TABLE);
      tags_set_orphans ("filter", filter, LOCATION_TABLE);
    }

  sql ("DELETE FROM filters WHERE id = %llu;", filter);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Check whether a filter is in use.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
filter_in_use (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts WHERE filter = %llu;",
                    filter);
}

/**
 * @brief Check whether a trashcan filter is in use.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
trash_filter_in_use (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts_trash"
                    " WHERE filter = %llu"
                    " AND filter_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                    filter);
}

/**
 * @brief Check whether a filter is writable.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
filter_writable (filter_t filter)
{
  return 1;
}

/**
 * @brief Check whether a trashcan filter is writable.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
trash_filter_writable (filter_t filter)
{
  return 1;
}

/**
 * @brief Filter columns for filter iterator.
 */
#define FILTER_ITERATOR_FILTER_COLUMNS                        \
 { GET_ITERATOR_FILTER_COLUMNS, "type", "term", NULL }

/**
 * @brief Filter iterator columns.
 */
#define FILTER_ITERATOR_COLUMNS                               \
 {                                                            \
   GET_ITERATOR_COLUMNS (filters),                            \
   { "type" , NULL },                                         \
   { "term", NULL },                                          \
   { NULL, NULL }                                             \
 }

/**
 * @brief Filter iterator columns for trash case.
 */
#define FILTER_ITERATOR_TRASH_COLUMNS                         \
 {                                                            \
   GET_ITERATOR_COLUMNS (filters_trash),                      \
   { "type" , NULL },                                         \
   { "term", NULL },                                          \
   { NULL, NULL }                                             \
 }

/**
 * @brief Count number of filters.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of filters in filtered set.
 */
int
filter_count (const get_data_t *get)
{
  static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = FILTER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS;
  return count ("filter", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a filter iterator, including observed filters.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find filter, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_filter_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = FILTER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "filter",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the type from a filter iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the filter, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.  "" for any type.
 */
const char*
filter_iterator_type (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT);
  return ret ? type_pretty_name (ret) : "";
}

/**
 * @brief Get the term from a filter iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The term of the filter, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (filter_iterator_term, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Initialise a filter alert iterator.
 *
 * Iterates over all alerts that use the filter.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  filter     Filter.
 */
void
init_filter_alert_iterator (iterator_t* iterator, filter_t filter)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (filter);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_alerts"));
  available = where_owned ("alert", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT name, uuid, %s FROM alerts"
                 " WHERE filter = %llu"
                 " ORDER BY name ASC;",
                 available,
                 filter);

  g_free (available);
}

/**
 * @brief Get the name from a filter_alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (filter_alert_iterator_name, 0);

/**
 * @brief Get the UUID from a filter_alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (filter_alert_iterator_uuid, 1);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
filter_alert_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}

/**
 * @brief Modify a filter.
 *
 * @param[in]   filter_id       UUID of filter.
 * @param[in]   name            Name of filter.
 * @param[in]   comment         Comment on filter.
 * @param[in]   term            Filter term.
 * @param[in]   type            Type of filter.
 *
 * @return 0 success, 1 failed to find filter, 2 filter with new name exists,
 *         3 error in type name, 4 filter_id required, 5 filter is in use so
 *         type must be "result" if specified, 99 permission denied,
 *         -1 internal error.
 */
int
modify_filter (const char *filter_id, const char *name, const char *comment,
               const char *term, const char *type)
{
  gchar *quoted_name, *quoted_comment, *quoted_term, *quoted_type, *clean_term;
  filter_t filter;

  if (filter_id == NULL)
    return 4;

  type = type_db_name (type);
  if (type && !((strcmp (type, "") == 0) || valid_type (type)))
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_filter") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  filter = 0;
  if (find_filter_with_permission (filter_id, &filter, "modify_filter"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (filter == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  /* If the filter is linked to an alert, check that the type is valid. */
  if (filter_in_use (filter)
      && type
      && strcasecmp (type, "result"))
    {
      sql ("ROLLBACK;");
      return 5;
    }

  /* Check whether a filter with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "filter", filter))
        {
          sql ("ROLLBACK;");
          return 2;
        }
    }

  quoted_name = sql_quote(name ?: "");
  clean_term = manage_clean_filter (term ? term : "");
  quoted_term = sql_quote (clean_term);
  g_free (clean_term);
  quoted_comment = sql_quote (comment ? comment : "");
  quoted_type = sql_quote (type ? type : "");

  sql ("UPDATE filters SET"
       " name = '%s',"
       " comment = '%s',"
       " term = '%s',"
       " type = lower ('%s'),"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       quoted_term,
       quoted_type,
       filter);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_term);
  g_free (quoted_type);

  sql ("COMMIT;");

  return 0;
}


/* Schema. */

/**
 * @brief Generate the OMP schema.
 *
 * @param[in]  format         Name of schema format, "XML" or NULL for XML.
 * @param[out] output_return  NULL or location for output.
 * @param[out] output_length  NULL or location for length of output.
 * @param[out] extension      NULL or location for schema extension.
 * @param[out] content_type   NULL or location for schema content type.
 *
 * @return 0 success, 1 failed to find schema format, -1 error.
 */
int
manage_schema (gchar *format, gchar **output_return, gsize *output_length,
               gchar **extension, gchar **content_type)
{
  /* Pass the XML file to the schema generate script, sending the output
   * to a file. */

  {
    gchar *script, *script_dir;
    gchar *uuid_format;
    char output_dir[] = "/tmp/openvasmd_schema_XXXXXX";

    if (mkdtemp (output_dir) == NULL)
      {
        g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
        return -1;
      }

    /* Setup file names. */

    if (format == NULL)
      {
        if (extension)
          *extension = g_strdup ("xml");
        if (content_type)
          *content_type = g_strdup ("text/xml");
        uuid_format = "18e826fc-dab6-11df-b913-002264764cea";
      }
    else if (strcasecmp (format, "HTML") == 0)
      {
        if (extension)
          *extension = g_strdup ("html");
        if (content_type)
          *content_type = g_strdup ("text/html");
        uuid_format = "02052818-dab6-11df-9be4-002264764cea";
      }
    else if (strcasecmp (format, "RNC") == 0)
      {
        if (extension)
          *extension = g_strdup ("rnc");
        if (content_type)
          *content_type = g_strdup ("text/x-rnc");
        uuid_format = "787a4a18-dabc-11df-9486-002264764cea";
      }
    else if (strcasecmp (format, "XML") == 0)
      {
        if (extension)
          *extension = g_strdup ("xml");
        if (content_type)
          *content_type = g_strdup ("text/xml");
        uuid_format = "18e826fc-dab6-11df-b913-002264764cea";
      }
    else
      return 1;

    script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                   "openvasmd",
                                   "global_schema_formats",
                                   uuid_format,
                                   NULL);

    script = g_build_filename (script_dir, "generate", NULL);

    if (!g_file_test (script, G_FILE_TEST_EXISTS))
      {
        g_free (script);
        g_free (script_dir);
        if (extension) g_free (*extension);
        if (content_type) g_free (*content_type);
        return -1;
      }

    {
      gchar *output_file, *command;
      char *previous_dir;
      int ret;

      /* Change into the script directory. */

      /** @todo NULL arg is glibc extension. */
      previous_dir = getcwd (NULL, 0);
      if (previous_dir == NULL)
        {
          g_warning ("%s: Failed to getcwd: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          if (extension) g_free (*extension);
          if (content_type) g_free (*content_type);
          return -1;
        }

      if (chdir (script_dir))
        {
          g_warning ("%s: Failed to chdir: %s\n",
                     __FUNCTION__,
                     strerror (errno));
          g_free (previous_dir);
          g_free (script);
          g_free (script_dir);
          if (extension) g_free (*extension);
          if (content_type) g_free (*content_type);
          return -1;
        }
      g_free (script_dir);

      output_file = g_strdup_printf ("%s/report.out", output_dir);

      /* Call the script. */

      command = g_strdup_printf ("%s " OPENVAS_DATA_DIR
                                 "/openvasmd/global_schema_formats"
                                 "/18e826fc-dab6-11df-b913-002264764cea/OMP.xml"
                                 " > %s"
                                 " 2> /dev/null",
                                 script,
                                 output_file);
      g_free (script);

      g_debug ("   command: %s\n", command);

      /* RATS: ignore, command is defined above. */
      ret = system (command);
      if ((ret == -1)
          /* The schema "generate" script must exit with 0. */
          || WEXITSTATUS (ret))
        {
          g_warning ("%s: system failed with ret %i, %i, %s\n",
                     __FUNCTION__,
                     ret,
                     WEXITSTATUS (ret),
                     command);
          if (chdir (previous_dir))
            g_warning ("%s: and chdir failed\n",
                       __FUNCTION__);
          g_free (previous_dir);
          g_free (command);
          g_free (output_file);
          if (extension) g_free (*extension);
          if (content_type) g_free (*content_type);
          return -1;
        }

      {
        GError *get_error;
        gchar *output;
        gsize output_len;

        g_free (command);

        /* Change back to the previous directory. */

        if (chdir (previous_dir))
          {
            g_warning ("%s: Failed to chdir back: %s\n",
                       __FUNCTION__,
                       strerror (errno));
            g_free (previous_dir);
            if (extension) g_free (*extension);
            if (content_type) g_free (*content_type);
            return -1;
          }
        g_free (previous_dir);

        /* Read the script output from file. */

        get_error = NULL;
        g_file_get_contents (output_file,
                             &output,
                             &output_len,
                             &get_error);
        g_free (output_file);
        if (get_error)
          {
            g_warning ("%s: Failed to get output: %s\n",
                       __FUNCTION__,
                       get_error->message);
            g_error_free (get_error);
            if (extension) g_free (*extension);
            if (content_type) g_free (*content_type);
            return -1;
          }

        /* Remove the output directory. */

        openvas_file_remove_recurse (output_dir);

        /* Return the output. */

        if (output_length) *output_length = output_len;

        if (output_return) *output_return = output;
        return 0;
      }
    }
  }
}


/* Trashcan. */

/**
 * @brief Restore a resource from the trashcan.
 *
 * @param[in]  id  UUID of resource.
 *
 * @return 0 success, 1 fail because the resource refers to another resource
 *         in the trashcan, 2 failed to find resource in trashcan, 3 fail
 *         because resource with such name exists already, 4 fail because
 *         resource with UUID exists already, 99 permission denied, -1 error.
 */
int
manage_restore (const char *id)
{
  resource_t resource = 0;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (user_may ("restore") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  /* Agent. */

  if (find_trash ("agent", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM agents"
                   " WHERE name ="
                   " (SELECT name FROM agents_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO agents"
           " (uuid, owner, name, comment, installer, installer_64,"
           "  installer_filename, installer_signature_64, installer_trust,"
           "  installer_trust_time, howto_install, howto_use, creation_time,"
           "  modification_time)"
           " SELECT"
           "  uuid, owner, name, comment, installer, installer_64,"
           "  installer_filename, installer_signature_64, installer_trust,"
           "  installer_trust_time, howto_install, howto_use, creation_time,"
           "  modification_time"
           " FROM agents_trash WHERE id = %llu;",
           resource);

      permissions_set_locations ("agent", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("agent", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM agents_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Config. */

  if (find_trash ("config", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      config_t config;

      if (sql_int ("SELECT count(*) FROM configs"
                   " WHERE name ="
                   " (SELECT name FROM configs_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO configs"
           " (uuid, owner, name, nvt_selector, comment, family_count, nvt_count,"
           "  families_growing, nvts_growing, type, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, nvt_selector, comment, family_count,"
           "        nvt_count, families_growing, nvts_growing,"
           "        type, creation_time, modification_time"
           " FROM configs_trash WHERE id = %llu;",
           resource);

      config = sql_last_insert_id ();

      sql ("INSERT INTO config_preferences"
           " (config, type, name, value, default_value)"
           " SELECT %llu, type, name, value, default_value"
           " FROM config_preferences_trash WHERE config = %llu;",
           config,
           resource);

      /* Update the config in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET config = %llu,"
           "     config_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE config = %llu"
           " AND config_location = " G_STRINGIFY (LOCATION_TRASH),
           config,
           resource);

      permissions_set_locations ("config", resource, config,
                                 LOCATION_TABLE);
      tags_set_locations ("config", resource, config,
                          LOCATION_TABLE);

      sql ("DELETE FROM config_preferences_trash WHERE config = %llu;",
           resource);
      sql ("DELETE FROM configs_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Alert. */

  if (find_trash ("alert", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      alert_t alert;

      if (sql_int ("SELECT count(*) FROM alerts"
                   " WHERE name ="
                   " (SELECT name FROM alerts_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      /* Check if it uses a filter in the trashcan. */
      if (sql_int ("SELECT filter_location = " G_STRINGIFY (LOCATION_TRASH)
                   " FROM alerts_trash WHERE id = %llu;",
                   resource))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO alerts"
           " (uuid, owner, name, comment, event, condition, method, filter,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, event, condition, method,"
           "        filter, creation_time, modification_time"
           " FROM alerts_trash WHERE id = %llu;",
           resource);

      alert = sql_last_insert_id ();

      sql ("INSERT INTO alert_condition_data"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_condition_data_trash WHERE alert = %llu;",
           alert,
           resource);

      sql ("INSERT INTO alert_event_data"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_event_data_trash WHERE alert = %llu;",
           alert,
           resource);

      sql ("INSERT INTO alert_method_data"
           " (alert, name, data)"
           " SELECT %llu, name, data"
           " FROM alert_method_data_trash WHERE alert = %llu;",
           alert,
           resource);

      /* Update the alert in any trashcan tasks. */
      sql ("UPDATE task_alerts"
           " SET alert = %llu,"
           "     alert_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE alert = %llu"
           " AND alert_location = " G_STRINGIFY (LOCATION_TRASH),
           alert,
           resource);

      permissions_set_locations ("alert", resource, alert,
                                 LOCATION_TABLE);
      tags_set_locations ("alert", resource, alert,
                          LOCATION_TABLE);

      sql ("DELETE FROM alert_condition_data_trash WHERE alert = %llu;",
           resource);
      sql ("DELETE FROM alert_event_data_trash WHERE alert = %llu;",
           resource);
      sql ("DELETE FROM alert_method_data_trash WHERE alert = %llu;",
           resource);
      sql ("DELETE FROM alerts_trash WHERE id = %llu;",
           resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Filter. */

  if (find_trash ("filter", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM filters"
                   " WHERE name ="
                   " (SELECT name FROM filters_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO filters"
           " (uuid, owner, name, comment, type, term, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, type, term, creation_time,"
           "        modification_time"
           " FROM filters_trash WHERE id = %llu;",
           resource);

      /* Update the filter in any trashcan alerts. */
      sql ("UPDATE alerts_trash"
           " SET filter = %llu,"
           "     filter_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE filter = %llu"
           " AND filter_location = " G_STRINGIFY (LOCATION_TRASH),
           sql_last_insert_id (),
           resource);

      permissions_set_locations ("filter", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("filter", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM filters_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Group. */

  if (find_trash ("group", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      group_t group;

      if (sql_int ("SELECT count(*) FROM groups"
                   " WHERE name ="
                   " (SELECT name FROM groups_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO groups"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time"
           " FROM groups_trash WHERE id = %llu;",
           resource);

      group = sql_last_insert_id ();

      sql ("INSERT INTO group_users"
           " (\"group\", \"user\")"
           " SELECT %llu, \"user\""
           " FROM group_users_trash WHERE \"group\" = %llu;",
           group,
           resource);

      permissions_set_locations ("group", resource, group, LOCATION_TABLE);
      tags_set_locations ("group", resource, group, LOCATION_TABLE);

      permissions_set_subjects ("group", resource, group, LOCATION_TABLE);

      sql ("DELETE FROM group_users_trash WHERE \"group\" = %llu;", resource);
      sql ("DELETE FROM groups_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* LSC credential. */

  if (find_trash ("lsc_credential", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      lsc_credential_t credential;

      if (sql_int ("SELECT count(*) FROM lsc_credentials"
                   " WHERE name ="
                   " (SELECT name FROM lsc_credentials_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO lsc_credentials"
           " (uuid, owner, name, login, password, comment, "
           "  private_key, creation_time, modification_time)"
           " SELECT uuid, owner, name, login, password, comment, "
           "  private_key, creation_time, modification_time"
           " FROM lsc_credentials_trash WHERE id = %llu;",
           resource);

      credential = sql_last_insert_id ();

      /* Update the credentials in any trashcan targets. */
      sql ("UPDATE targets_trash"
           " SET ssh_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     lsc_credential = %llu"
           " WHERE lsc_credential = %llu"
           " AND ssh_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);
      sql ("UPDATE targets_trash"
           " SET smb_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     smb_lsc_credential = %llu"
           " WHERE smb_lsc_credential = %llu"
           " AND smb_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);
      sql ("UPDATE targets_trash"
           " SET esxi_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     esxi_lsc_credential = %llu"
           " WHERE esxi_lsc_credential = %llu"
           " AND esxi_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);

      permissions_set_locations ("lsc_credential", resource, credential,
                                 LOCATION_TABLE);
      tags_set_locations ("lsc_credential", resource, credential,
                          LOCATION_TABLE);

      sql ("DELETE FROM lsc_credentials_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Note. */

  if (find_trash ("note", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      sql ("INSERT INTO notes"
           " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
           "  port, severity, task, result, end_time)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, task, result, end_time"
           " FROM notes_trash WHERE id = %llu;",
           resource);

      permissions_set_locations ("note", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("note", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);

      sql ("DELETE FROM notes_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Override. */

  if (find_trash ("override", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      sql ("INSERT INTO overrides"
           " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
           "  port, severity, new_severity, task, result, end_time)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, new_severity, task,"
           "        result, end_time"
           " FROM overrides_trash WHERE id = %llu;",
           resource);

      permissions_set_locations ("override", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("override", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM overrides_trash WHERE id = %llu;", resource);
      reports_clear_count_cache (1);
      sql ("COMMIT;");
      return 0;
    }

  /* Permission. */

  if (find_trash ("permission", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      sql ("INSERT INTO permissions"
           " (uuid, owner, name, comment, resource_type, resource,"
           "  resource_uuid, resource_location, subject_type, subject,"
           "  subject_location, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, resource_type, resource,"
           "  resource_uuid, resource_location, subject_type, subject,"
           "  subject_location, creation_time, modification_time"
           " FROM permissions_trash"
           " WHERE id = %llu;",
           resource);

      tags_set_locations ("permission", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM permissions_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Port list. */

  if (find_trash ("port_list", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      port_list_t table_port_list;

      if (sql_int ("SELECT count(*) FROM port_lists"
                   " WHERE name ="
                   " (SELECT name FROM port_lists_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO port_lists"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time, modification_time"
           " FROM port_lists_trash WHERE id = %llu;",
           resource);

      table_port_list = sql_last_insert_id ();

      sql ("INSERT INTO port_ranges"
           " (uuid, port_list, type, start, \"end\", comment, exclude)"
           " SELECT uuid, %llu, type, start, \"end\", comment, exclude"
           " FROM port_ranges_trash WHERE port_list = %llu;",
           table_port_list,
           resource);

      /* Update the port_list in any trashcan targets. */
      sql ("UPDATE targets_trash"
           " SET port_range = %llu,"
           "     port_list_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE port_range = %llu"
           " AND port_list_location = " G_STRINGIFY (LOCATION_TRASH),
           table_port_list,
           resource);

      permissions_set_locations ("port_list", resource, table_port_list,
                                 LOCATION_TABLE);
      tags_set_locations ("port_list", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM port_ranges_trash WHERE port_list = %llu;", resource);
      sql ("DELETE FROM port_lists_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Report format. */

  if (find_trash ("report_format", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      iterator_t params;
      report_format_t report_format;
      gchar *dir, *trash_dir, *resource_string;
      int global;
      char *trash_uuid;

      if (sql_int ("SELECT count(*) FROM report_formats"
                   " WHERE name ="
                   " (SELECT name FROM report_formats_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      if (sql_int ("SELECT count(*) FROM report_formats"
                   " WHERE uuid = (SELECT original_uuid"
                   "               FROM report_formats_trash"
                   "               WHERE id = %llu);",
                   resource))
        {
          sql ("ROLLBACK;");
          return 4;
        }

      /* Move to "real" tables. */

      sql ("INSERT INTO report_formats"
           " (uuid, owner, name, extension, content_type, summary,"
           "  description, signature, trust, trust_time, flags,"
           "  creation_time, modification_time)"
           " SELECT"
           "  original_uuid, owner, name, extension, content_type, summary,"
           "  description, signature, trust, trust_time, flags,"
           "  creation_time, modification_time"
           " FROM report_formats_trash"
           " WHERE id = %llu;",
           resource);

      report_format = sql_last_insert_id ();

      init_report_format_param_iterator (&params, resource, 1, 1, NULL);
      while (next (&params))
        {
          report_format_param_t param, trash_param;

          trash_param = report_format_param_iterator_param (&params);

          sql ("INSERT INTO report_format_params"
               " (report_format, name, type, value, type_min, type_max,"
               "  type_regex, fallback)"
               " SELECT"
               "  %llu, name, type, value, type_min, type_max,"
               "  type_regex, fallback"
               " FROM report_format_params_trash"
               " WHERE id = %llu;",
               report_format,
               trash_param);

          param = sql_last_insert_id ();

          sql ("INSERT INTO report_format_param_options"
               " (report_format_param, value)"
               " SELECT %llu, value"
               " FROM report_format_param_options_trash"
               " WHERE report_format_param = %llu;",
               param,
               trash_param);
        }
      cleanup_iterator (&params);

      global = report_format_trash_global (resource);

      trash_uuid = sql_string ("SELECT original_uuid FROM report_formats_trash"
                               " WHERE id = %llu;",
                               resource);
      if (trash_uuid == NULL)
        abort ();

      permissions_set_locations ("report_format", resource, report_format,
                                 LOCATION_TABLE);
      tags_set_locations ("report_format", resource, report_format,
                          LOCATION_TABLE);

      /* Remove from trash tables. */

      sql ("DELETE FROM report_format_param_options_trash"
           " WHERE report_format_param"
           " IN (SELECT id from report_format_params_trash"
           "     WHERE report_format = %llu);",
           resource);
      sql ("DELETE FROM report_format_params_trash WHERE report_format = %llu;",
           resource);
      sql ("DELETE FROM report_formats_trash WHERE id = %llu;",
           resource);

      /* Move the dir last, in case any SQL rolls back. */

      if (global)
        dir = g_build_filename (OPENVAS_DATA_DIR,
                                "openvasmd",
                                "global_report_formats",
                                trash_uuid,
                                NULL);
      else
        dir = g_build_filename (OPENVAS_STATE_DIR,
                                "openvasmd",
                                "report_formats",
                                current_credentials.uuid,
                                trash_uuid,
                                NULL);
      free (trash_uuid);

      resource_string = g_strdup_printf ("%llu", resource);
      trash_dir = g_build_filename (OPENVAS_DATA_DIR,
                                    "openvasmd",
                                    "report_formats_trash",
                                    resource_string,
                                    NULL);
      g_free (resource_string);
      if (move_report_format_dir (trash_dir, dir))
        {
          g_free (dir);
          g_free (trash_dir);
          sql ("ROLLBACK;");
          return -1;
        }
      g_free (dir);
      g_free (trash_dir);

      sql ("COMMIT;");
      return 0;
    }

  /* Role. */

  if (find_trash ("role", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      role_t role;

      if (sql_int ("SELECT count(*) FROM roles"
                   " WHERE name ="
                   " (SELECT name FROM roles_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO roles"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time"
           " FROM roles_trash WHERE id = %llu;",
           resource);

      role = sql_last_insert_id ();

      sql ("INSERT INTO role_users"
           " (role, \"user\")"
           " SELECT role, \"user\""
           " FROM role_users_trash WHERE role = %llu;",
           resource);

      permissions_set_locations ("role", resource, role, LOCATION_TABLE);
      tags_set_locations ("role", resource, role, LOCATION_TABLE);

      permissions_set_subjects ("role", resource, role, LOCATION_TABLE);

      sql ("DELETE FROM role_users_trash WHERE role = %llu;", resource);
      sql ("DELETE FROM roles_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Scanner. */

  if (find_trash ("scanner", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM scanners"
                   " WHERE name ="
                   " (SELECT name FROM scanners_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource, current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO scanners"
           " (uuid, owner, name, comment, host, port, type, ca_pub,"
           "  key_pub, key_priv, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, host, port, type, ca_pub,"
           "        key_pub, key_priv, creation_time, modification_time"
           " FROM scanners_trash WHERE id = %llu;", resource);

      permissions_set_locations ("scanner", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("scanner", resource,
                          sql_last_insert_id (), LOCATION_TABLE);

      sql ("DELETE FROM scanners_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Schedule. */

  if (find_trash ("schedule", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM schedules"
                   " WHERE name ="
                   " (SELECT name FROM schedules_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO schedules"
           " (uuid, owner, name, comment, first_time, period, period_months,"
           "  duration, timezone, initial_offset, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, first_time, period,"
           "        period_months, duration, timezone, initial_offset,"
           "        creation_time, modification_time"
           " FROM schedules_trash WHERE id = %llu;",
           resource);

      /* Update the schedule in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET schedule = %llu,"
           "     schedule_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE schedule = %llu"
           " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH),
           sql_last_insert_id (),
           resource);

      permissions_set_locations ("schedule", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("schedule", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM schedules_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Slave. */

  if (find_trash ("slave", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM slaves"
                   " WHERE name ="
                   " (SELECT name FROM slaves_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      sql ("INSERT INTO slaves"
           "  (uuid, owner, name, comment, host, port, login, password,"
           "   creation_time, modification_time)"
           " SELECT"
           "  uuid, owner, name, comment, host, port, login, password,"
           "  creation_time, modification_time"
           " FROM slaves_trash WHERE id = %llu;",
           resource);

      /* Update the slave in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET slave = %llu,"
           "     slave_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE slave = %llu"
           " AND slave_location = " G_STRINGIFY (LOCATION_TRASH),
           sql_last_insert_id (),
           resource);

      permissions_set_locations ("slave", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("slave", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM slaves_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Tag */

  if (find_trash ("tag", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      sql ("INSERT INTO tags"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, resource_type, resource, resource_uuid,"
           "  resource_location, active, value)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, resource_type, resource, resource_uuid,"
           "        resource_location, active, value"
           " FROM tags_trash WHERE id = %llu;",
           resource);

      permissions_set_locations ("tag", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);

      sql ("DELETE FROM tags_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Target. */

  if (find_trash ("target", id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      if (sql_int ("SELECT count(*) FROM targets"
                   " WHERE name ="
                   " (SELECT name FROM targets_trash WHERE id = %llu)"
                   " AND ((owner IS NULL) OR (owner ="
                   " (SELECT id FROM users WHERE users.uuid = '%s')));",
                   resource,
                   current_credentials.uuid))
        {
          sql ("ROLLBACK;");
          return 3;
        }

      /* Check if it uses a credential or port list in the trashcan. */
      if (sql_int ("SELECT ssh_location = " G_STRINGIFY (LOCATION_TRASH)
                   " OR smb_location = " G_STRINGIFY (LOCATION_TRASH)
                   " OR port_list_location = " G_STRINGIFY (LOCATION_TRASH)
                   " FROM targets_trash WHERE id = %llu;",
                   resource))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      sql ("INSERT INTO targets"
           " (uuid, owner, name, hosts, exclude_hosts, comment, lsc_credential,"
           "  ssh_port, smb_lsc_credential, esxi_lsc_credential,"
           "  port_range, reverse_lookup_only, reverse_lookup_unify,"
           "  alive_test, creation_time, modification_time)"
           " SELECT uuid, owner, name, hosts, exclude_hosts, comment, "
           "        lsc_credential, ssh_port, smb_lsc_credential,"
           "        esxi_lsc_credential, port_range,"
           "        reverse_lookup_only, reverse_lookup_unify, alive_test,"
           "        creation_time, modification_time"
           " FROM targets_trash WHERE id = %llu;",
           resource);

      /* Update the target in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET target = %llu,"
           "     target_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE target = %llu"
           " AND target_location = " G_STRINGIFY (LOCATION_TRASH),
           sql_last_insert_id (),
           resource);

      permissions_set_locations ("target", resource,
                                 sql_last_insert_id (),
                                 LOCATION_TABLE);
      tags_set_locations ("target", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      sql ("DELETE FROM targets_trash WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  /* Task. */

  if (find_trash_task (id, &resource))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (resource)
    {
      /* Check if it's in use by a resource in the trashcan. */
      if (sql_int ("SELECT (target_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " OR (config_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " OR (schedule_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " OR (slave_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " OR (SELECT count(*) > 0 FROM task_alerts"
                   "     WHERE task = tasks.id"
                   "     AND alert_location = " G_STRINGIFY (LOCATION_TRASH) ")"
                   " FROM tasks WHERE id = %llu;",
                   resource))
        {
          sql ("ROLLBACK;");
          return 1;
        }

      permissions_set_locations ("task", resource, resource, LOCATION_TABLE);
      tags_set_locations ("task", resource, resource, LOCATION_TABLE);
      sql ("UPDATE tags SET resource_location = "
           G_STRINGIFY (LOCATION_TABLE)
           " WHERE resource_type = 'report'"
           " AND resource IN (SELECT id FROM reports"
           "                  WHERE reports.task = %llu);",
           resource);
      sql ("UPDATE tags SET resource_location = "
           G_STRINGIFY (LOCATION_TABLE)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT results.id FROM results"
           "                  WHERE results.task = %llu);",
           resource);

      sql ("UPDATE tags_trash SET resource_location = "
           G_STRINGIFY (LOCATION_TABLE)
           " WHERE resource_type = 'report'"
           " AND resource IN (SELECT id FROM reports"
           "                  WHERE reports.task = %llu);",
           resource);
      sql ("UPDATE tags_trash SET resource_location = "
           G_STRINGIFY (LOCATION_TABLE)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT results.id FROM results"
           "                  WHERE results.task = %llu);",
           resource);

      sql ("UPDATE tasks SET hidden = 0 WHERE id = %llu;", resource);
      sql ("COMMIT;");
      return 0;
    }

  sql ("ROLLBACK;");
  return 2;
}

/**
 * @brief Empty the trashcan.
 *
 * @return 0 success, 99 permission denied, -1 error.
 */
int
manage_empty_trashcan ()
{
  gchar *dir;

  sql_begin_immediate ();

  if (user_may ("empty_trashcan") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  sql ("DELETE FROM agents_trash;");
  sql ("DELETE FROM nvt_selectors WHERE name IN"
       " (SELECT nvt_selector FROM configs_trash);");
  sql ("DELETE FROM config_preferences_trash;");
  sql ("DELETE FROM configs_trash;");
  sql ("DELETE FROM permissions"
       " WHERE subject_type = 'group'"
       " AND subject IN (SELECT id from groups_trash)"
       " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";");
  sql ("DELETE FROM group_users_trash;");
  sql ("DELETE FROM groups_trash;");
  sql ("DELETE FROM alert_condition_data_trash;");
  sql ("DELETE FROM alert_event_data_trash;");
  sql ("DELETE FROM alert_method_data_trash;");
  sql ("DELETE FROM alerts_trash;");
  sql ("DELETE FROM filters_trash;");
  sql ("DELETE FROM lsc_credentials_trash;");
  sql ("DELETE FROM notes_trash;");
  sql ("DELETE FROM overrides_trash;");
  sql ("DELETE FROM permissions_trash;");
  sql ("DELETE FROM port_ranges_trash;");
  sql ("DELETE FROM port_lists_trash;");
  sql ("DELETE FROM permissions"
       " WHERE subject_type = 'role'"
       " AND subject IN (SELECT id from roles_trash)"
       " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";");
  sql ("DELETE FROM role_users_trash;");
  sql ("DELETE FROM roles_trash;");
  sql ("DELETE FROM scanners_trash;");
  sql ("DELETE FROM schedules_trash;");
  sql ("DELETE FROM slaves_trash;");
  sql ("DELETE FROM tags_trash;");
  sql ("DELETE FROM targets_trash;");
  if (delete_trash_tasks ())
    {
      sql ("ROLLBACK;");
      return -1;
    }

  sql ("UPDATE permissions"
       " SET resource = -1"
       " WHERE resource > 0"
       " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";");
  sql ("UPDATE tags"
       " SET resource = 0, resource_location = " G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource_location = " G_STRINGIFY (LOCATION_TRASH) ";");

  sql ("DELETE FROM report_format_param_options_trash;");
  sql ("DELETE FROM report_format_params_trash;");
  sql ("DELETE FROM report_formats_trash;");

  /* Remove the report formats dir last, in case any SQL rolls back. */

  dir = g_build_filename (OPENVAS_DATA_DIR,
                          "openvasmd",
                          "report_formats_trash",
                          NULL);

  if (g_file_test (dir, G_FILE_TEST_EXISTS) && openvas_file_remove_recurse (dir))
    {
      g_warning ("%s: failed to remove trash dir %s", __FUNCTION__, dir);
      g_free (dir);
      sql ("ROLLBACK;");
      return -1;
    }

  if (g_mkdir_with_parents (dir, 0755 /* "rwxr-xr-x" */))
    {
      g_warning ("%s: failed to create trash dir %s", __FUNCTION__, dir);
      g_free (dir);
      sql ("ROLLBACK;");
      return -1;
    }

  g_free (dir);

  sql ("COMMIT;");
  return 0;
}


/* Assets. */

/**
 * @brief Add a report host.
 *
 * @param[in]  report   UUID of resource.
 * @param[in]  host     Host.
 * @param[in]  start    Start time.
 * @param[in]  end      End time.
 *
 * @return 0 success, -1 failed to parse XML.
 */
void
manage_report_host_add (report_t report, const char *host, time_t start,
                        time_t end)
{
  char *quoted_host = sql_quote (host);

  sql ("INSERT INTO report_hosts"
       " (report, host, start_time, end_time, current_port, max_port)"
       " SELECT %llu, '%s', %lld, %lld, 0, 0"
       " WHERE NOT EXISTS (SELECT 1 FROM report_hosts WHERE report = %llu"
       "                   AND host = '%s');",
       report, quoted_host, (long long) start, (long long) end, report,
       quoted_host);
  g_free (quoted_host);
}

/**
 * @brief Add host details to a report host.
 *
 * @param[in]  report  UUID of resource.
 * @param[in]  host    Host.
 * @param[in]  entity  XML entity containing details.
 *
 * @return 0 success, -1 failed to parse XML.
 */
int
manage_report_host_details (report_t report, const char *host, entity_t entity)
{
  entities_t details;
  entity_t detail;

  details = entity->entities;
  while ((detail = first_entity (details)))
    {
      if (strcmp (entity_name (detail), "detail") == 0)
        {
          entity_t source, source_type, source_name, source_desc, name, value;

          source = entity_child (detail, "source");
          if (source == NULL)
            return -1;
          source_type = entity_child (source, "type");
          if (source_type == NULL)
            return -1;
          source_name = entity_child (source, "name");
          if (source_name == NULL)
            return -1;
          source_desc = entity_child (source, "description");
          if (source_desc == NULL)
            return -1;
          name = entity_child (detail, "name");
          if (name == NULL)
            return -1;
          value = entity_child (detail, "value");
          if (value == NULL)
            return -1;
          insert_report_host_detail
           (report, host, entity_text (source_type), entity_text (source_name),
            entity_text (source_desc), entity_text (name), entity_text (value));
        }
      details = next_entities (details);
    }

  return 0;
}

/**
 * @brief Add a host detail to a report host.
 *
 * @param[in]  report  UUID of resource.
 * @param[in]  host    Host.
 * @param[in]  xml     Report host detail XML.
 *
 * @return 0 success, -1 failed to parse XML, -2 host was NULL.
 */
int
manage_report_host_detail (report_t report, const char *host, const char *xml)
{
  int ret;
  entity_t entity;

  if (host == NULL)
    return -2;

  entity = NULL;
  if (parse_entity (xml, &entity))
    return -1;

  ret = manage_report_host_details (report, host, entity);
  free_entity (entity);
  return ret;
}


/* Settings. */

/**
 * @brief Filter columns for setting iterator.
 */
#define SETTING_ITERATOR_FILTER_COLUMNS \
 { "name", "comment", "value", NULL }

/**
 * @brief Setting iterator columns.
 */
#define SETTING_ITERATOR_COLUMNS                              \
 {                                                            \
   { "id" , NULL },                                           \
   { "uuid", NULL },                                          \
   { "name", NULL },                                          \
   { "comment", NULL },                                       \
   { "value", NULL },                                         \
   { NULL, NULL }                                             \
 }

/**
 * @brief Count number of settings.
 *
 * @param[in]  filter           Filter term.
 *
 * @return Total number of settings in filtered set.
 */
int
setting_count (const char *filter)
{
  static const char *filter_columns[] = SETTING_ITERATOR_FILTER_COLUMNS;
  static column_t select_columns[] = SETTING_ITERATOR_COLUMNS;
  gchar *clause;
  int ret;

  assert (current_credentials.uuid);

  clause = filter_clause ("setting", filter, filter_columns, select_columns,
                          0, NULL, NULL, NULL, NULL, NULL);

  ret = sql_int ("SELECT count (*)"
                 " FROM settings"
                 " WHERE"
                 " (owner = (SELECT id FROM users WHERE uuid = '%s')"
                 "  OR (owner IS NULL"
                 "      AND uuid"
                 "      NOT IN (SELECT uuid FROM settings"
                 "              WHERE owner = (SELECT id FROM users"
                 "                             WHERE uuid = '%s'))))"
                 "%s%s;",
                 current_credentials.uuid,
                 current_credentials.uuid,
                 clause ? " AND " : "",
                 clause ? clause : "");

  g_free (clause);

  return ret;
}

/**
 * @brief Return the uuid of a resource filter from settings.
 *
 * @param[in]  resource  Resource (eg. Filters, Targets, CPE).
 *
 * @return resource filter uuid in settings if it exists, "" otherwise.
 */
char *
setting_filter (const char *resource)
{
  return sql_string ("SELECT value FROM settings WHERE name = '%s Filter'"
                     " AND ((owner IS NULL)"
                     "      OR (owner ="
                     "          (SELECT id FROM users WHERE users.uuid = '%s')))"
                     " ORDER BY owner DESC;",
                     resource,
                     current_credentials.uuid);
}

/**
 * @brief Return the Severity Class user setting.
 *
 * @return User Severity Class in settings if it exists, "" otherwise.
 */
const char *
setting_severity ()
{
  if (current_credentials.severity_class)
    return current_credentials.severity_class;
  return "nist";
}

/**
 * @brief Return the Dynamic Severity user setting as an int.
 *
 * @return 1 if user's Dynamic Severity is "Yes", 0 if it is "No",
 *         or does not exist.
 */
int
setting_dynamic_severity_int ()
{
  return current_credentials.dynamic_severity;
}

/**
 * @brief Return the user's timezone.
 *
 * @return User Severity Class in settings if it exists, else NULL.
 */
char *
setting_timezone ()
{
  return sql_string ("SELECT timezone FROM users WHERE uuid = '%s'",
                     current_credentials.uuid);
}

/**
 * @brief Initialise a setting iterator, including observed settings.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  uuid        UUID of setting to limit iteration to.  0 for all.
 * @param[in]  filter      Filter term.
 * @param[in]  first       First setting.
 * @param[in]  max         Maximum number of settings returned.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_setting_iterator (iterator_t *iterator, const char *uuid,
                       const char *filter, int first, int max, int ascending,
                       const char *sort_field)
{
  static const char *filter_columns[] = SETTING_ITERATOR_FILTER_COLUMNS;
  static column_t select_columns[] = SETTING_ITERATOR_COLUMNS;
  gchar *clause, *columns, *quoted_uuid;

  assert (current_credentials.uuid);

  if (first < 0)
    first = 0;
  if (max < 1)
    max = -1;

  clause = filter_clause ("setting", filter, filter_columns, select_columns,
                          0, NULL, NULL, NULL, NULL, NULL);

  quoted_uuid = uuid ? sql_quote (uuid) : NULL;
  columns = columns_build_select (select_columns);

  if (quoted_uuid)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM settings"
                   " WHERE uuid = '%s'"
                   " AND ((owner IS NULL)"
                   "      OR (owner = (SELECT id FROM users"
                   "                   WHERE users.uuid = '%s')))"
                   /* Force the user's setting to come before the default. */
                   " ORDER BY owner DESC;",
                   columns,
                   quoted_uuid,
                   current_credentials.uuid);
  else
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM settings"
                   " WHERE"
                   " (owner = (SELECT id FROM users WHERE uuid = '%s')"
                   "  OR (owner IS NULL"
                   "      AND uuid"
                   "      NOT IN (SELECT uuid FROM settings"
                   "              WHERE owner = (SELECT id FROM users"
                   "                             WHERE uuid = '%s'))))"
                   "%s%s"
                   " ORDER BY %s %s"
                   " LIMIT %s OFFSET %i;",
                   columns,
                   current_credentials.uuid,
                   current_credentials.uuid,
                   clause ? " AND " : "",
                   clause ? clause : "",
                   sort_field ? sort_field : "id",
                   ascending ? "ASC" : "DESC",
                   sql_select_limit (max),
                   first);

  g_free (columns);
  g_free (clause);
}

/**
 * @brief Get the UUID from a setting iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the setting, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (setting_iterator_uuid, 1);

/**
 * @brief Get the name from a setting iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the setting, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (setting_iterator_name, 2);

/**
 * @brief Get the comment from a setting iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The comment of the setting, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (setting_iterator_comment, 3);

/**
 * @brief Get the value from a setting iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the setting, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (setting_iterator_value, 4);

/**
 * @brief Get the value of a setting.
 *
 * @param[in]   uuid   UUID of setting.
 * @param[out]  value  Value.
 *
 * @return 0 success, -1 error.
 */
int
setting_value_int (const char *uuid, int *value)
{
  gchar *quoted_uuid;

  if (value == NULL || uuid == NULL)
    return -1;

  quoted_uuid = sql_quote (uuid);

  if (sql_int ("SELECT count (*)"
               " FROM settings"
               " WHERE uuid = '%s'"
               " AND ((owner IS NULL)"
               "      OR (owner ="
               "          (SELECT id FROM users WHERE users.uuid = '%s')));",
               quoted_uuid,
               current_credentials.uuid)
      == 0)
    {
      *value = -1;
      g_free (quoted_uuid);
      return -1;
    }

  *value = sql_int ("SELECT value"
                    " FROM settings"
                    " WHERE uuid = '%s'"
                    " AND ((owner IS NULL)"
                    "      OR (owner ="
                    "       (SELECT id FROM users WHERE users.uuid = '%s')))"
                    /* Force the user's setting to come before the default. */
                    " ORDER BY owner DESC;",
                    quoted_uuid,
                    current_credentials.uuid);

  g_free (quoted_uuid);

  return 0;
}

/**
 * @brief Set the value of a setting.
 *
 * @param[in]  uuid      UUID of setting.
 * @param[in]  name      Setting name.  For Timezone and Password.
 * @param[in]  value_64  New setting value, base64 encoded.
 * @param[out] r_errdesc If not NULL the address of a variable to receive
 *                       a malloced string with the error description.  Will
 *                       always be set to NULL on success.
 *
 * @return 0 success, 1 failed to find setting, 2 syntax error in value,
 *         99 permission denied, -1 on error.
 */
int
modify_setting (const gchar *uuid, const gchar *name,
                const gchar *value_64, gchar **r_errdesc)
{
  char *setting_name;

  assert (current_credentials.uuid);

  if (user_may ("modify_setting") == 0)
    return 99;

  if (r_errdesc)
    *r_errdesc = NULL;

  if (name && (strcmp (name, "Timezone") == 0))
    {
      gsize value_size;
      gchar *quoted_timezone, *value;
      if (value_64 && strlen (value_64))
        value = (gchar*) g_base64_decode (value_64, &value_size);
      else
        {
          value = g_strdup ("");
          value_size = 0;
        }
      quoted_timezone = sql_quote (value);
      g_free (value);
      sql ("UPDATE users SET timezone = '%s', modification_time = m_now ()"
           " WHERE uuid = '%s';",
           quoted_timezone,
           current_credentials.uuid);
      g_free (quoted_timezone);
      return 0;
    }

  if (name && (strcmp (name, "Password") == 0))
    {
      gsize value_size;
      gchar *value;
      int ret;

      assert (current_credentials.username);

      if (value_64 && strlen (value_64))
        value = (gchar*) g_base64_decode (value_64, &value_size);
      else
        {
          value = g_strdup ("");
          value_size = 0;
        }

      ret = set_password (current_credentials.username,
                          current_credentials.uuid,
                          value,
                          r_errdesc);
      g_free (value);
      return ret;
    }

  if (uuid && (strcmp (uuid, "5f5a8712-8017-11e1-8556-406186ea4fc5") == 0
               || strcmp (uuid, "f16bb236-a32d-4cd5-a880-e0fcf2599f59") == 0
               || strcmp (uuid, "20f3034c-e709-11e1-87e7-406186ea4fc5") == 0
               || strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0
               || strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0
               || strcmp (uuid, "578a1c14-e2dc-45ef-a591-89d31391d007") == 0))
    {
      gsize value_size;
      gchar *value, *quoted_uuid, *quoted_value;

      assert (current_credentials.username);

      quoted_uuid = sql_quote (uuid);

      if (sql_int ("SELECT count(*) FROM settings"
                   " WHERE uuid = '%s'"
                   " AND owner IS NULL;",
                   quoted_uuid,
                   current_credentials.uuid)
          == 0)
        {
          g_free (quoted_uuid);
          return 1;
        }

      if (value_64 && strlen (value_64))
        value = (gchar*) g_base64_decode (value_64, &value_size);
      else
        {
          value = g_strdup ("");
          value_size = 0;
        }

      if (strcmp (uuid, "5f5a8712-8017-11e1-8556-406186ea4fc5") == 0)
        {
          const gchar *val;
          /* Rows Per Page. */
          val = value;
          while (*val && isdigit (*val)) val++;
          if (*val && strcmp (value, "-1"))
            {
              g_free (quoted_uuid);
              return 2;
            }
        }

      if (strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0)
        {
          GRegex *languages_regex;
          gboolean match;
          /*
           * regex: colon-separated lists of language or language and country
           *  codes (ISO 639-1, 639-2 and 3166-1 alpha-2)
           *  as used in the LANGUAGE env variable by gettext
           */
          languages_regex
            = g_regex_new ("^(Browser Language|"
                           "([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_-]+)?"
                           "(:([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_-]+)?)*)$",
                           0, 0, NULL);
          match = g_regex_match (languages_regex, value, 0, NULL);
          g_regex_unref (languages_regex);

          /* User Interface Language. */
          if (match)
            {
              // Valid languages string or "Browser Language":
              //  keep string as it is
            }
          /* Legacy full language names */
          else if (strcmp (value, "Chinese") == 0)
            {
              g_free (value);
              value = g_strdup ("zh_CN");
            }
          else if (strcmp (value, "English") == 0)
            {
              g_free (value);
              value = g_strdup ("en");
            }
          else if (strcmp (value, "German") == 0)
            {
              g_free (value);
              value = g_strdup ("de");
            }
          /* Invalid value */
          else
            {
              g_free (quoted_uuid);
              g_free (value);
              return 2;
            }
        }

      if (strcmp (uuid, "f16bb236-a32d-4cd5-a880-e0fcf2599f59") == 0)
        {
          /* Severity Class */
          g_free (current_credentials.severity_class);
          current_credentials.severity_class = g_strdup (value);
        }

      if (strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0)
        {
          /* Dynamic Severity */
          current_credentials.dynamic_severity = atoi (value);
        }

      quoted_value = sql_quote (value);
      g_free (value);

      if (sql_int ("SELECT count(*) FROM settings"
                   " WHERE uuid = '%s'"
                   " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
                   quoted_uuid,
                   current_credentials.uuid))
        sql ("UPDATE settings SET value = '%s'"
             " WHERE uuid = '%s'"
             " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
             quoted_value,
             quoted_uuid,
             current_credentials.uuid);
      else
        sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
             " VALUES"
             " ('%s',"
             "  (SELECT id FROM users WHERE uuid = '%s'),"
             "  (SELECT name FROM settings"
             "   WHERE uuid = '%s' AND owner IS NULL"
             "   LIMIT 1),"
             "  (SELECT comment FROM settings"
             "   WHERE uuid = '%s' AND owner IS NULL"
             "   LIMIT 1),"
             "  '%s');",
             quoted_uuid,
             current_credentials.uuid,
             quoted_uuid,
             quoted_uuid,
             quoted_value);

      g_free (quoted_uuid);
      g_free (quoted_value);

      return 0;
    }

  /* Export file name format */
  if (uuid
      && (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0
          || strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0
          || strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0))
    {
      gsize value_size;
      gchar *value, *quoted_value;

      assert (current_credentials.uuid);
      if (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0)
        setting_name = "Details Export File Name";
      else if (strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0)
        setting_name = "List Export File Name";
      else if (strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0)
        setting_name = "Report Export File Name";
      else
        return -1;

      if (value_64 && strlen (value_64))
        value = (gchar*) g_base64_decode (value_64, &value_size);
      else
        {
          value = g_strdup ("");
          value_size = 0;
        }
      quoted_value = sql_quote (value);

      if (strcmp (value, "") == 0)
        {
          g_free (value);
          g_free (quoted_value);
          return 2;
        }

      if (sql_int ("SELECT count(*) FROM settings"
                   " WHERE uuid = '%s'"
                   " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
                   uuid,
                   current_credentials.uuid))
        sql ("UPDATE settings SET value = '%s'"
             " WHERE uuid = '%s'"
             " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
             quoted_value,
             uuid,
             current_credentials.uuid);
      else
        sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
             " VALUES"
             " ('%s',"
             "  (SELECT id FROM users WHERE uuid = '%s'),"
             "  '%s',"
             "  (SELECT comment FROM settings"
             "   WHERE uuid = '%s' AND owner IS NULL),"
             "  '%s');",
             uuid,
             current_credentials.uuid,
             setting_name,
             uuid,
             quoted_value);

      g_free (value);
      g_free (quoted_value);

      return 0;
    }

  /* Resources filters, default resource selections and chart preferences. */

  setting_name = NULL;
  if (uuid)
    {
      /* Filters */
      if (strcmp (uuid, "4a1334c1-cb93-4a79-8634-103b0a50bdcd") == 0)
        setting_name = g_strdup ("Agents Filter");
      else if (strcmp (uuid, "b833a6f2-dcdc-4535-bfb0-a5154b5b5092") == 0)
        setting_name = g_strdup ("Alerts Filter");
      else if (strcmp (uuid, "1a9fbd91-0182-44cd-bc88-a13a9b3b1bef") == 0)
        setting_name = g_strdup ("Configs Filter");
      else if (strcmp (uuid, "186a5ac8-fe5a-4fb1-aa22-44031fb339f3") == 0)
        setting_name = g_strdup ("Credentials Filter");
      else if (strcmp (uuid, "f9691163-976c-47e7-ad9a-38f2d5c81649") == 0)
        setting_name = g_strdup ("Filters Filter");
      else if (strcmp (uuid, "96abcd5a-9b6d-456c-80b8-c3221bfa499d") == 0)
        setting_name = g_strdup ("Notes Filter");
      else if (strcmp (uuid, "eaaaebf1-01ef-4c49-b7bb-955461c78e0a") == 0)
        setting_name = g_strdup ("Overrides Filter");
      else if (strcmp (uuid, "ffb16b28-538c-11e3-b8f9-406186ea4fc5") == 0)
        setting_name = g_strdup ("Permissions Filter");
      else if (strcmp (uuid, "7d52d575-baeb-4d98-bb68-e1730dbc6236") == 0)
        setting_name = g_strdup ("Port Lists Filter");
      else if (strcmp (uuid, "48ae588e-9085-41bc-abcb-3d6389cf7237") == 0)
        setting_name = g_strdup ("Reports Filter");
      else if (strcmp (uuid, "249c7a55-065c-47fb-b453-78e11a665565") == 0)
        setting_name = g_strdup ("Report Formats Filter");
      else if (strcmp (uuid, "739ab810-163d-11e3-9af6-406186ea4fc5") == 0)
        setting_name = g_strdup ("Results Filter");
      else if (strcmp (uuid, "f38e673a-bcd1-11e2-a19a-406186ea4fc5") == 0)
        setting_name = g_strdup ("Roles Filter");
      else if (strcmp (uuid, "a83e321b-d994-4ae8-beec-bfb5fe3e7336") == 0)
        setting_name = g_strdup ("Schedules Filter");
      else if (strcmp (uuid, "2681c32a-8dfd-40c9-a9c6-8d4e2c7799eb") == 0)
        setting_name = g_strdup ("Slaves Filter");
      else if (strcmp (uuid, "108eea3b-fc61-483c-9da9-046762f137a8") == 0)
        setting_name = g_strdup ("Tags Filter");
      else if (strcmp (uuid, "236e2e41-9771-4e7a-8124-c432045985e0") == 0)
        setting_name = g_strdup ("Targets Filter");
      else if (strcmp (uuid, "1c981851-8244-466c-92c4-865ffe05e721") == 0)
        setting_name = g_strdup ("Tasks Filter");
      else if (strcmp (uuid, "3414a107-ae46-4dea-872d-5c4479a48e8f") == 0)
        setting_name = g_strdup ("CPE Filter");
      else if (strcmp (uuid, "def63b5a-41ef-43f4-b9ef-03ef1665db5d") == 0)
        setting_name = g_strdup ("CVE Filter");
      else if (strcmp (uuid, "bef08b33-075c-4f8c-84f5-51f6137e40a3") == 0)
        setting_name = g_strdup ("NVT Filter");
      else if (strcmp (uuid, "adb6ffc8-e50e-4aab-9c31-13c741eb8a16") == 0)
        setting_name = g_strdup ("OVAL Filter");
      else if (strcmp (uuid, "e4cf514a-17e2-4ab9-9c90-336f15e24750") == 0)
        setting_name = g_strdup ("CERT-Bund Filter");
      else if (strcmp (uuid, "312350ed-bc06-44f3-8b3f-ab9eb828b80b") == 0)
        setting_name = g_strdup ("DFN-CERT Filter");
      else if (strcmp (uuid, "feefe56b-e2da-4913-81cc-1a6ae3b36e64") == 0)
        setting_name = g_strdup ("All SecInfo Filter");

      /* Default resource selections */
      else if (strcmp (uuid, "f9f5a546-8018-48d0-bef5-5ad4926ea899") == 0)
        setting_name = g_strdup ("Default Alert");

      else if (strcmp (uuid, "fe7ea321-e3e3-4cc6-9952-da836aae83ce") == 0)
        setting_name = g_strdup ("Default OpenVAS Scan Config");
      else if (strcmp (uuid, "fb19ac4b-614c-424c-b046-0bc32bf1be73") == 0)
        setting_name = g_strdup ("Default OSP Scan Config");

      else if (strcmp (uuid, "6fc56b72-c1cf-451c-a4c4-3a9dc784c3bd") == 0)
        setting_name = g_strdup ("Default SSH Credential");
      else if (strcmp (uuid, "a25c0cfe-f977-417b-b1da-47da370c03e8") == 0)
        setting_name = g_strdup ("Default SMB Credential");
      else if (strcmp (uuid, "83545bcf-0c49-4b4c-abbf-63baf82cc2a7") == 0)
        setting_name = g_strdup ("Default ESXi Credential");

      else if (strcmp (uuid, "d74a9ee8-7d35-4879-9485-ab23f1bd45bc") == 0)
        setting_name = g_strdup ("Default Port List");

      else if (strcmp (uuid, "f7d0f6ed-6f9e-45dc-8bd9-05cced84e80d") == 0)
        setting_name = g_strdup ("Default OpenVAS Scanner");
      else if (strcmp (uuid, "b20697c9-be0a-4cd4-8b4d-5fe7841ebb03") == 0)
        setting_name = g_strdup ("Default OSP Scanner");

      else if (strcmp (uuid, "778eedad-5550-4de0-abb6-1320d13b5e18") == 0)
        setting_name = g_strdup ("Default Schedule");

      else if (strcmp (uuid, "aec201fa-8a82-4b61-bebe-a44ea93b2909") == 0)
        setting_name = g_strdup ("Default Slave");

      else if (strcmp (uuid, "23409203-940a-4b4a-b70c-447475f18323") == 0)
        setting_name = g_strdup ("Default Target");

      /* Scan Management charts */
      else if (strcmp (uuid, "3d5db3c7-5208-4b47-8c28-48efc621b1e0") == 0)
        setting_name = g_strdup ("Chart Selection Tasks Left");
      else if (strcmp (uuid, "ce8608af-7e66-45a8-aa8a-76def4f9f838") == 0)
        setting_name = g_strdup ("Chart Selection Tasks Right");

      /* SecInfo charts */
      else if (strcmp (uuid, "84ab32da-fe69-44d8-8a8f-70034cf28d4e") == 0)
        setting_name = g_strdup ("Chart Selection SecInfo Dashboard 1");
      else if (strcmp (uuid, "517d0efe-426e-49a9-baa7-eda2832c93e8") == 0)
        setting_name = g_strdup ("Chart Filter SecInfo Dashboard 1");
      else if (strcmp (uuid, "42d48049-3153-43bf-b30d-72ca5ab1eb49") == 0)
        setting_name = g_strdup ("Chart Selection SecInfo Dashboard 2");
      else if (strcmp (uuid, "3c693fb2-4f87-4b1f-a09e-cb9aa66440f4") == 0)
        setting_name = g_strdup ("Chart Filter SecInfo Dashboard 2");
      else if (strcmp (uuid, "76f34fe0-254a-4481-97aa-c6f1da2f842b") == 0)
        setting_name = g_strdup ("Chart Selection SecInfo Dashboard 3");
      else if (strcmp (uuid, "bffa72a5-8110-49f9-aa5e-f431ce834826") == 0)
        setting_name = g_strdup ("Chart Filter SecInfo Dashboard 3");
      else if (strcmp (uuid, "71106ed7-b677-414e-bf67-2e7716441db3") == 0)
        setting_name = g_strdup ("Chart Selection SecInfo Dashboard 4");
      else if (strcmp (uuid, "268079c6-f353-414f-9b7c-43f5419edf2d") == 0)
        setting_name = g_strdup ("Chart Filter SecInfo Dashboard 4");

      else if (strcmp (uuid, "f68d9369-1945-477b-968f-121c6029971b") == 0)
        setting_name = g_strdup ("Chart Selection NVTs Left");
      else if (strcmp (uuid, "af89a84a-d3ec-43a8-97a8-aa688bf093bc") == 0)
        setting_name = g_strdup ("Chart Selection NVTs Right");

      else if (strcmp (uuid, "815ddd2e-8654-46c7-a05b-d73224102240") == 0)
        setting_name = g_strdup ("Chart Selection CVEs Left");
      else if (strcmp (uuid, "418a5746-d68a-4a2d-864a-0da993b32220") == 0)
        setting_name = g_strdup ("Chart Selection CVEs Right");

      else if (strcmp (uuid, "9cff9b4d-b164-43ce-8687-f2360afc7500") == 0)
        setting_name = g_strdup ("Chart Selection CPEs Left");
      else if (strcmp (uuid, "629fdb73-35fa-4247-9018-338c202f7c03") == 0)
        setting_name = g_strdup ("Chart Selection CPEs Right");

      else if (strcmp (uuid, "9563efc0-9f4e-4d1f-8f8d-0205e32b90a4") == 0)
        setting_name = g_strdup ("Chart Selection OVAL Definitions Left");
      else if (strcmp (uuid, "fe1610a3-4e87-4b0d-9b7a-f0f66fef586b") == 0)
        setting_name = g_strdup ("Chart Selection OVAL Definitions Right");

      else if (strcmp (uuid, "a6946f44-480f-4f37-8a73-28a4cd5310c4") == 0)
        setting_name = g_strdup ("Chart Selection CERT-Bund Advisories Left");
      else if (strcmp (uuid, "469d50da-880a-4bfc-88ed-22e53764c683") == 0)
        setting_name = g_strdup ("Chart Selection CERT-Bund Advisories Right");

      else if (strcmp (uuid, "9812ea49-682d-4f99-b3cc-eca051d1ce59") == 0)
        setting_name = g_strdup ("Chart Selection DFN-CERT Advisories Left");
      else if (strcmp (uuid, "72014b52-4389-435d-9438-8c13601ecbd2") == 0)
        setting_name = g_strdup ("Chart Selection DFN-CERT Advisories Right");

      else if (strcmp (uuid, "4c7b1ea7-b7e6-4d12-9791-eb9f72b6f864") == 0)
        setting_name = g_strdup ("Chart Selection All SecInfo Left");
      else if (strcmp (uuid, "985f38eb-1a30-4a35-abb6-3eec05b5d54a") == 0)
        setting_name = g_strdup ("Chart Selection All SecInfo Right");
    }

  if (setting_name)
    {
      gchar *quoted_value, *value;
      gsize value_size;

      assert (current_credentials.username);

      if (value_64 && strlen (value_64))
        value = (gchar*) g_base64_decode (value_64, &value_size);
      else
        {
          value = g_strdup ("");
          value_size = 0;
        }

      quoted_value = sql_quote (value);

      if (sql_int ("SELECT count(*) FROM settings"
                   " WHERE uuid = '%s'"
                   " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
                   uuid,
                   current_credentials.uuid))
        sql ("UPDATE settings SET value = '%s'"
             " WHERE uuid = '%s'"
             " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
             quoted_value,
             uuid,
             current_credentials.uuid);
      else
        sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
             " VALUES"
             " ('%s',"
             "  (SELECT id FROM users WHERE uuid = '%s'),"
             "  '%s',"
             "  (SELECT comment FROM settings"
             "   WHERE uuid = '%s' AND owner IS NULL),"
             "  '%s');",
             uuid,
             current_credentials.uuid,
             setting_name,
             uuid,
             quoted_value);

      g_free (value);
      g_free (quoted_value);

      return 0;
    }

  return 1;
}


/* SCAP. */

/**
 * @brief Filter columns for CVE iterator.
 */
#define CVE_INFO_ITERATOR_FILTER_COLUMNS                         \
 { GET_ITERATOR_FILTER_COLUMNS, "vector",                        \
   "complexity", "authentication", "confidentiality_impact",     \
   "integrity_impact", "availability_impact", "products",        \
   "cvss", "description", "severity", "published", NULL }

/**
 * @brief CVE iterator columns.
 */
#define CVE_INFO_ITERATOR_COLUMNS                               \
 {                                                              \
   GET_ITERATOR_COLUMNS_PREFIX (""),                            \
   { "''", "_owner" },                                          \
   { "0", NULL },                                               \
   { "vector", NULL },                                          \
   { "complexity", NULL },                                      \
   { "authentication", NULL },                                  \
   { "confidentiality_impact", NULL },                          \
   { "integrity_impact", NULL },                                \
   { "availability_impact", NULL },                             \
   { "products", NULL },                                        \
   { "cvss", NULL },                                            \
   { "description", NULL },                                     \
   { "cvss", "severity" },                                      \
   { "creation_time", "published" },                            \
   { NULL, NULL }                                               \
 }

/**
 * @brief Filter columns for CVE iterator.
 */
#define CPE_INFO_ITERATOR_FILTER_COLUMNS                    \
 { GET_ITERATOR_FILTER_COLUMNS, "title", "status",          \
   "deprecated_by_id", "max_cvss", "cves", "nvd_id",        \
   "severity", NULL }

/**
 * @brief CPE iterator columns.
 */
#define CPE_INFO_ITERATOR_COLUMNS                               \
 {                                                              \
   GET_ITERATOR_COLUMNS_PREFIX (""),                            \
   { "''", "_owner" },                                          \
   { "0", NULL },                                                           \
   { "title", NULL },                                           \
   { "status", NULL },                                          \
   { "deprecated_by_id", NULL },                                \
   { "max_cvss", NULL },                                        \
   { "cve_refs", "cves" },                                      \
   { "nvd_id", NULL },                                          \
   { "max_cvss", "severity" },                                  \
   { NULL, NULL }                                               \
 }

/**
 * @brief Filter columns for OVALDEF iterator.
 */
#define OVALDEF_INFO_ITERATOR_FILTER_COLUMNS                \
 { GET_ITERATOR_FILTER_COLUMNS, "version", "deprecated",    \
   "class", "title", "description", "file",                 \
   "status", "max_cvss", "cves", "severity", NULL }

/**
 * @brief OVALDEF iterator columns.
 */
#define OVALDEF_INFO_ITERATOR_COLUMNS                            \
 {                                                               \
   GET_ITERATOR_COLUMNS_PREFIX (""),                             \
   { "''", "_owner" },                                           \
   { "0", NULL },                                                           \
   { "version", NULL },                                          \
   { "deprecated", NULL },                                       \
   { "def_class", "class"},                                      \
   { "title", NULL },                                            \
   { "description", NULL },                                      \
   { "xml_file", "file" },                                       \
   { "status", NULL },                                           \
   { "max_cvss", NULL },                                         \
   { "cve_refs", "cves" },                                       \
   { "max_cvss", "severity" },                                   \
   { NULL, NULL }                                                \
 }

/**
 * @brief Filter columns for CERT_BUND_ADV iterator.
 */
#define CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS           \
 { GET_ITERATOR_FILTER_COLUMNS, "title", "summary",         \
   "cves", "max_cvss", "severity", NULL }

/**
 * @brief CERT_BUND_ADV iterator columns.
 */
#define CERT_BUND_ADV_INFO_ITERATOR_COLUMNS                       \
 {                                                               \
   GET_ITERATOR_COLUMNS_PREFIX (""),                             \
   { "''", "_owner" },                                           \
   { "0", NULL },                                                           \
   { "title", NULL },                                            \
   { "summary", NULL },                                          \
   { "cve_refs", "cves" },                                       \
   { "max_cvss", NULL },                                         \
   { "max_cvss", "severity" },                                   \
   { NULL, NULL }                                                \
 }

/**
 * @brief Filter columns for DFN_CERT_ADV iterator.
 */
#define DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS           \
 { GET_ITERATOR_FILTER_COLUMNS, "title", "summary",         \
   "cves", "max_cvss", "severity", NULL }

/**
 * @brief DFN_CERT_ADV iterator columns.
 */
#define DFN_CERT_ADV_INFO_ITERATOR_COLUMNS                       \
 {                                                               \
   GET_ITERATOR_COLUMNS_PREFIX (""),                             \
   { "''", "_owner" },                                           \
   { "0", NULL },                                                           \
   { "title", NULL },                                            \
   { "summary", NULL },                                          \
   { "cve_refs", "cves" },                                       \
   { "max_cvss", NULL },                                         \
   { "max_cvss", "severity" },                                   \
   { NULL, NULL }                                                \
 }

/**
 * @brief Filter columns for All SecInfo iterator.
 */
#define ALL_INFO_ITERATOR_FILTER_COLUMNS                               \
 { GET_ITERATOR_FILTER_COLUMNS, "type", "extra", "severity", NULL }

/**
 * @brief All SecInfo iterator columns.
 */
#define ALL_INFO_ITERATOR_COLUMNS                                       \
 {                                                                      \
   { "id", NULL },                                                      \
   { "uuid", NULL },                                                    \
   { "name", NULL },                                                    \
   { "comment", NULL },                                                 \
   { "iso_time (created)", NULL },                                      \
   { "iso_time (modified)", NULL },                                     \
   { "created", NULL },                                                 \
   { "modified", NULL },                                                \
   { "''", "_owner" },                                                  \
   { "0", NULL },                                                       \
   { "type", NULL },                                                    \
   { "extra", NULL },                                                   \
   { "severity", NULL },                                                \
   { NULL, NULL }                                                       \
 }

/**
 * @brief All SecInfo iterator column union.
 */
#define ALL_INFO_UNION_COLUMNS                                                 \
  "(SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner, 'cve' AS type,"      \
  "        description as extra, cvss as severity"                             \
  " FROM cves"                                                                 \
  " UNION ALL SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner,"           \
  "                  'cpe' AS type, title as extra, max_cvss as severity"      \
  "           FROM cpes"                                                       \
  " UNION ALL SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner,"           \
  "                  'nvt' AS type, CASE summary WHEN 'NOSUMMARY' THEN tag"    \
  "                                  ELSE summary END AS extra,"               \
  "                  CAST (cvss_base AS float) as severity"                    \
  "           FROM nvts"                                                       \
  " UNION ALL SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner,"           \
  "                  'cert_bund_adv' AS type, title as extra,"                 \
  "                  max_cvss as severity"                                     \
  "           FROM cert_bund_advs"                                             \
  " UNION ALL SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner,"           \
  "                  'dfn_cert_adv' AS type, title as extra,"                  \
  "                  max_cvss as severity"                                     \
  "           FROM dfn_cert_advs"                                              \
  " UNION ALL SELECT " GET_ITERATOR_COLUMNS_STRING ", '' AS _owner,"           \
  "                  'ovaldef' AS type, title as extra, max_cvss as severity"  \
  "           FROM ovaldefs)"                                                  \
  " AS allinfo"

/**
 * @brief Initialise an CVE iterator, for CVEs reported for a certain CPE.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  cve         CVE.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_cpe_cve_iterator (iterator_t *iterator, const char *cve, int ascending,
                       const char *sort_field)
{
  gchar *quoted_cpe;
  assert (cve);
  quoted_cpe = sql_quote (cve);
  init_iterator (iterator,
                 "SELECT id, name, cvss FROM cves WHERE id IN"
                 " (SELECT cve FROM affected_products"
                 "  WHERE cpe ="
                 "  (SELECT id FROM cpes WHERE name = '%s'))"
                 " ORDER BY %s %s;",
                 quoted_cpe,
                 sort_field ? sort_field : "cvss DESC, name",
                 ascending ? "ASC" : "DESC");
  g_free (quoted_cpe);
}

/**
 * @brief Get the name from a CVE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the CVE, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (cve_iterator_name, 1);

/**
 * @brief Get the CVSS from a CVE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS of the CVE, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (cve_iterator_cvss, 2);

/**
 * @brief Count number of cpe.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of cpes in filtered set.
 */
int
cpe_info_count (const get_data_t *get)
{
  static const char *filter_columns[] = CPE_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CPE_INFO_ITERATOR_COLUMNS;
  return count ("cpe", get, columns, NULL, filter_columns, 0, 0, 0, FALSE);
}

/**
 * @brief Initialise a info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_cpe_info_iterator (iterator_t* iterator, get_data_t *get, const char *name)
{
  static const char *filter_columns[] = CPE_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CPE_INFO_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by ID, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  ret = init_get_iterator (iterator,
                           "cpe",
                           get,
                           columns,
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           FALSE);
  g_free (clause);
  return ret;
}

/**
 * @brief Get the short file name for an OVALDEF.
 *
 * @param[in]  cve  Full OVAL identifier with file suffix.
 *
 * @return The file name of the OVAL definition relative to the SCAP directory,
 *         Freed by g_free.
 */
gchar *
cve_cvss_base (const gchar *cve)
{
  gchar *quoted_cve, *ret;
  quoted_cve = sql_quote (cve);
  ret = sql_string ("SELECT cvss FROM cves WHERE name = '%s'",
                    quoted_cve);
  g_free (quoted_cve);
  return ret;
}

/**
 * @brief Count number of cve.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of cpes in filtered set.
 */
int
cve_info_count (const get_data_t *get)
{
  static const char *filter_columns[] = CVE_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CVE_INFO_ITERATOR_COLUMNS;
  return count ("cve", get, columns, NULL, filter_columns, 0, 0, 0, FALSE);
}

/**
 * @brief Initialise a info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_cve_info_iterator (iterator_t* iterator, get_data_t *get, const char *name)
{
  static const char *filter_columns[] = CVE_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CVE_INFO_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by ID, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  ret = init_get_iterator (iterator,
                           "cve",
                           get,
                           columns,
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           FALSE);
  g_free (clause);
  return ret;
}
/**
 * @brief Get the title from a CPE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The Title of the CPE, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_title, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the status from a CPE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The Status of the CPE, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_status, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the id of the deprecating CPE from a CPE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The deprecated_by CVD ID, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_deprecated_by, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the Highest CVSS Score of all CVE's referencing this cpe.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The Highest CVSS of the CPE, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_max_cvss, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the Number of CVE's referencing this cpe from a CPE iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The Number of references to the CPE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_cve_refs, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the NVD ID for this CPE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The NVD ID of this CPE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cpe_info_iterator_nvd_id, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the CVSS attack vector for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS attack vector of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_vector, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the CVSS attack complexity for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS attack complexity of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_complexity, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the CVSS attack authentication for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS attack authentication of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_authentication, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the CVSS confidentiality impact for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS confidentiality impact of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_confidentiality_impact, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the CVSS integrity impact for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS integrity impact of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_integrity_impact, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the CVSS availability impact for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS availability impact of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_availability_impact, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get a space seperated list of CPEs affected by this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return A space seperated list of CPEs or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_products, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the CVSS base score for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The CVSS base score of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_cvss, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Get the Summary for this CVE.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The Summary of this CVE, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (cve_info_iterator_description, GET_ITERATOR_COLUMN_COUNT + 8);

/* OVAL data */
/**
 * @brief Initialise an OVAL definition (ovaldef) info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_ovaldef_info_iterator (iterator_t* iterator, get_data_t *get,
                            const char *name)
{
  static const char *filter_columns[] = OVALDEF_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OVALDEF_INFO_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by ID, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  ret = init_get_iterator (iterator,
                           "ovaldef",
                           get,
                           columns,
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           FALSE);
  g_free (clause);
  return ret;
}

/**
 * @brief Count number of ovaldef.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of OVAL definitions in filtered set.
 */
int
ovaldef_info_count (const get_data_t *get)
{
  static const char *filter_columns[] = OVALDEF_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OVALDEF_INFO_ITERATOR_COLUMNS;
  return count ("ovaldef", get, columns, NULL, filter_columns, 0, 0, 0, FALSE);
}

/**
 * @brief Get the version number from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The version number of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_version, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the deprecation status from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return True if the OVAL definition is deprecated, false if not,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_deprecated, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the definition class from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The definition class (e.g. 'patch' or 'vulnerability') of the OVAL
 *         definition, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_class, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the title from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The title / short description of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_title, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the description from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The long description of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_description, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the source xml file from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The short xml source file name of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_file, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the repository entry status from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The repository entry status of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_status, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get maximum CVSS score from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum CVSS score of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_max_cvss, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Get number of referenced CVEs from an OVALDEF iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum CVSS score of the OVAL definition,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (ovaldef_info_iterator_cve_refs, GET_ITERATOR_COLUMN_COUNT + 8);

/* CERT data */

/* CERT-Bund data */
/**
 * @brief Initialise an CERT-Bund advisory (cert_bund_adv) info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_cert_bund_adv_info_iterator (iterator_t* iterator, get_data_t *get,
                                  const char *name)
{
  static const char *filter_columns[] =
      CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by ID, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  ret = init_get_iterator (iterator,
                           "cert_bund_adv",
                           get,
                           columns,
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           FALSE);
  g_free (clause);
  return ret;
}

/**
 * @brief Count number of cert_bund_adv.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of CERT-Bund advisories in filtered set.
 */
int
cert_bund_adv_info_count (const get_data_t *get)
{
  static const char *filter_columns[] =
                      CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
  return count ("cert_bund_adv", get, columns, NULL, filter_columns,
                0, 0, 0, FALSE);
}

/**
 * @brief Get the title from an CERT_BUND_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The title of the CERT-Bund advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cert_bund_adv_info_iterator_title,
            GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the summary from an CERT_BUND_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The summary of the CERT-Bund advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cert_bund_adv_info_iterator_summary,
            GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the number of cves from an CERT_BUND_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The number of CVEs referenced in the CERT-Bund advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cert_bund_adv_info_iterator_cve_refs,
            GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the maximum CVSS from an CERT_BUND_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum CVSS of the CVEs referenced in the CERT-Bund advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (cert_bund_adv_info_iterator_max_cvss,
            GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Initialise CVE iterator, for CVEs referenced by a CERT-Bund advisory.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  cve         Name of the CVE.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_cve_cert_bund_adv_iterator (iterator_t *iterator, const char *cve,
                                int ascending, const char *sort_field)
{
  static column_t select_columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
  gchar *columns;

  assert (cve);

  columns = columns_build_select (select_columns);
  init_iterator (iterator,
                 "SELECT %s"
                 " FROM cert_bund_advs"
                 " WHERE id IN (SELECT adv_id FROM cert_bund_cves"
                 "              WHERE cve_name = '%s')"
                 " ORDER BY %s %s;",
                 columns,
                 cve,
                 sort_field ? sort_field : "name",
                 ascending ? "ASC" : "DESC");
  g_free (columns);
}

/**
 * @brief Initialise an CERT-Bund iterator, for advisories relevant to a NVT.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  oid         OID of the NVT.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_nvt_cert_bund_adv_iterator (iterator_t *iterator, const char *oid,
                                int ascending, const char *sort_field)
{
  static column_t select_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  gchar *columns;

  assert (oid);

  columns = columns_build_select (select_columns);
  init_iterator (iterator,
                 "SELECT %s"
                 " FROM cert_bund_advs"
                 " WHERE id IN (SELECT adv_id FROM cert_bund_cves"
                 "              WHERE cve_name IN (SELECT cve_name"
                 "                                 FROM nvt_cves"
                 "                                 WHERE oid = '%s'))"
                 " ORDER BY %s %s;",
                 columns,
                 oid,
                 sort_field ? sort_field : "name",
                 ascending ? "ASC" : "DESC");
  g_free (columns);
}

/* DFN-CERT data */
/**
 * @brief Initialise an DFN-CERT advisory (dfn_cert_adv) info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_dfn_cert_adv_info_iterator (iterator_t* iterator, get_data_t *get,
                            const char *name)
{
  static const char *filter_columns[] =
      DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  gchar *clause = NULL;
  int ret;

  if (get->id)
    {
      gchar *quoted = sql_quote (get->id);
      clause = g_strdup_printf (" AND uuid = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by ID, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  else if (name)
    {
      gchar *quoted = sql_quote (name);
      clause = g_strdup_printf (" AND name = '%s'", quoted);
      g_free (quoted);
      /* The entry is specified by name, so filtering just gets in the way. */
      g_free (get->filter);
      get->filter = NULL;
    }
  ret = init_get_iterator (iterator,
                           "dfn_cert_adv",
                           get,
                           columns,
                           NULL,
                           filter_columns,
                           0,
                           NULL,
                           clause,
                           FALSE);
  g_free (clause);
  return ret;
}

/**
 * @brief Count number of dfn_cert_adv.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of DFN-CERT advisories in filtered set.
 */
int
dfn_cert_adv_info_count (const get_data_t *get)
{
  static const char *filter_columns[] =
                      DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  return count ("dfn_cert_adv", get, columns, NULL, filter_columns,
                0, 0, 0, FALSE);
}

/**
 * @brief Get the title from an DFN_CERT_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The title of the DFN-CERT advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (dfn_cert_adv_info_iterator_title, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the summary from an DFN_CERT_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The summary of the DFN-CERT advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (dfn_cert_adv_info_iterator_summary, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the number of cves from an DFN_CERT_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The number of CVEs referenced in the DFN-CERT advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (dfn_cert_adv_info_iterator_cve_refs, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the maximum CVSS from an DFN_CERT_ADV iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum CVSS of the CVEs referenced in the DFN-CERT advisory,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (dfn_cert_adv_info_iterator_max_cvss, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Initialise CVE iterator, for CVEs referenced by a DFN-CERT advisory.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  cve         Name of the CVE.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_cve_dfn_cert_adv_iterator (iterator_t *iterator, const char *cve,
                                int ascending, const char *sort_field)
{
  static column_t select_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  gchar *columns;

  assert (cve);

  columns = columns_build_select (select_columns);
  init_iterator (iterator,
                 "SELECT %s"
                 " FROM dfn_cert_advs"
                 " WHERE id IN (SELECT adv_id FROM dfn_cert_cves"
                 "              WHERE cve_name = '%s')"
                 " ORDER BY %s %s;",
                 columns,
                 cve,
                 sort_field ? sort_field : "name",
                 ascending ? "ASC" : "DESC");
  g_free (columns);
}

/**
 * @brief Initialise an DFN-CERT iterator, for advisories relevant to a NVT.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  oid         OID of the NVT.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_nvt_dfn_cert_adv_iterator (iterator_t *iterator, const char *oid,
                                int ascending, const char *sort_field)
{
  static column_t select_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  gchar *columns;

  assert (oid);

  columns = columns_build_select (select_columns);
  init_iterator (iterator,
                 "SELECT %s"
                 " FROM dfn_cert_advs"
                 " WHERE id IN (SELECT adv_id FROM dfn_cert_cves"
                 "              WHERE cve_name IN (SELECT cve_name"
                 "                                 FROM nvt_cves"
                 "                                 WHERE oid = '%s'))"
                 " ORDER BY %s %s;",
                 columns,
                 oid,
                 sort_field ? sort_field : "name",
                 ascending ? "ASC" : "DESC");
  g_free (columns);
}

/**
 * @brief Get the short file name for an OVALDEF.
 *
 * @param[in]  item_id  Full OVAL identifier with file suffix.
 *
 * @return The file name of the OVAL definition relative to the SCAP directory,
 *         Freed by g_free.
 */
gchar*
get_ovaldef_short_filename (char* item_id)
{
  return sql_string ("SELECT xml_file FROM ovaldefs WHERE uuid = '%s';",
                     item_id);
}

/**
 * @brief Get the uuid for an OVALDEF from a name and file name.
 *
 * @param[in]  name     Oval definition name.
 * @param[in]  fname    Oval definition file name.
 *
 * @return The OVAL definition uuid from the SCAP directory. Freed by g_free.
 */
char*
ovaldef_uuid (const char *name, const char *fname)
{
  char *quoted_name, *quoted_fname, *ret;

  assert (name);
  assert (fname);
  quoted_name = sql_quote (name);
  quoted_fname = sql_quote (fname);
  ret = sql_string ("SELECT uuid FROM ovaldefs WHERE name = '%s'"
                    " AND xml_file = '%s';", name, fname);
  g_free (quoted_name);
  g_free (quoted_fname);
  return ret;
}

/**
 * @brief Get the severity of an OVALDEF using an ID.
 *
 * @param[in]  uuid     Oval definition ID.
 *
 * @return The severity of the OVAL definition from the SCAP directory.
 *         Freed by g_free.
 */
char *
ovaldef_severity (const char *id)
{
  char *quoted_id, *ret;

  assert (id);
  quoted_id = sql_quote (id);
  ret = sql_string ("SELECT max_cvss FROM ovaldefs WHERE uuid = '%s';",
                    quoted_id);
  g_free (quoted_id);
  return ret;
}

/**
 * @brief Get the version of an OVALDEF using an ID.
 *
 * @param[in]  uuid     Oval definition ID.
 *
 * @return The version of the OVAL definition from the SCAP directory.
 *         Freed by g_free.
 */
char *
ovaldef_version (const char *id)
{
  char *quoted_id, *ret;

  assert (id);
  quoted_id = sql_quote (id);
  ret = sql_string ("SELECT version FROM ovaldefs WHERE uuid = '%s';",
                    quoted_id);
  g_free (quoted_id);
  return ret;
}

/**
 * @brief Get the CVE names of an OVALDEF as ", " separated str.
 *
 * @param[in]  uuid     Oval definition ID.
 *
 * @return String of CVEs affecting of the OVAL definition, NULL otherwise.
 *         Freed by g_free.
 */
char *
ovaldef_cves (const char *id)
{
  char *quoted_id, *ret = NULL;
  iterator_t iterator;

  assert (id);
  quoted_id = sql_quote (id);
  init_iterator (&iterator,
                 "SELECT DISTINCT cves.name FROM cves, ovaldefs,"
                 " affected_ovaldefs WHERE ovaldefs.uuid = '%s'"
                 " AND cves.id = affected_ovaldefs.cve"
                 " AND ovaldefs.id = affected_ovaldefs.ovaldef;", quoted_id);
  g_free (quoted_id);
  while (next (&iterator))
    {
      char *tmp = ret;
      ret = g_strdup_printf ("%s%s%s", ret ?: "", ret ? ", " : "",
                             iterator_string (&iterator, 0));
      g_free (tmp);
    }
  cleanup_iterator (&iterator);
  return ret;
}

/* All SecInfo Data */

/**
 * @brief Count number of SecInfo entries.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of SecInfo entires in filtered set.
 */
int
all_info_count (const get_data_t *get)
{
  return total_info_count (get, 1);
}

/**
 * @brief Count number of all SecInfo entries.
 *
 * @param[in]   get  GET params.
 * @param[in]   filtered Whether to count entries in filtered set only.
 *
 * @return Total number of SecInfo entires.
 */
int
total_info_count (const get_data_t *get, int filtered)
{
  gchar *clause;

  if (filtered)
    {
      static const char *filter_columns[] = ALL_INFO_ITERATOR_FILTER_COLUMNS;
      static column_t select_columns[] = ALL_INFO_ITERATOR_COLUMNS;
      gchar *filter;

      if (get->filt_id && strcmp (get->filt_id, "0"))
        {
          filter = filter_term (get->filt_id);
          if (filter == NULL)
            return -1;
        }
      else
        filter = NULL;

      clause = filter_clause ("allinfo", filter ? filter : get->filter,
                              filter_columns, select_columns, get->trash,
                              NULL, NULL, NULL, NULL, NULL);
      if (clause)
        return sql_int ("SELECT count (id) FROM"
                        ALL_INFO_UNION_COLUMNS
                        " WHERE %s;",
                        clause);
    }

  return sql_int ("SELECT (SELECT count (*) FROM cves)"
                  " + (SELECT count (*) FROM cpes)"
                  " + (SELECT count (*) FROM nvts)"
                  " + (SELECT count (*) FROM cert_bund_advs)"
                  " + (SELECT count (*) FROM dfn_cert_advs)"
                  " + (SELECT count (*) FROM ovaldefs);");
}

/**
 * @brief Initialise an info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  get             GET data.
 * @param[in]  name            Name of the info
 *
 *
 * @return 0 success, 1 failed to find info, 2 failed to find filter,
 *         -1 error.
 */
int
init_all_info_iterator (iterator_t* iterator, get_data_t *get,
                        const char *name)
{
  static const char *filter_columns[] = ALL_INFO_ITERATOR_FILTER_COLUMNS;
  static column_t select_columns[] = ALL_INFO_ITERATOR_COLUMNS;
  int first, max;
  gchar *columns, *clause, *filter, *order;

  if (get->filt_id && strcmp (get->filt_id, "0"))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  clause = filter_clause ("allinfo", filter ? filter : get->filter,
                          filter_columns, select_columns, get->trash,
                          &order, &first, &max, NULL, NULL);
  columns = columns_build_select (select_columns);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM" ALL_INFO_UNION_COLUMNS
                 " %s%s"
                 " %s"
                 " LIMIT %s OFFSET %i;",
                 columns,
                 clause ? "WHERE " : "",
                 clause ? clause   : "",
                 order,
                 sql_select_limit (max),
                 first);

  g_free (order);
  g_free (filter);
  g_free (columns);
  g_free (clause);
  return 0;
}

/**
 * @brief Get the secinfo type from an all info iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of a secinfo entry,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (all_info_iterator_type, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the secinfo extra information from an all info iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return extra info secinfo entry,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (all_info_iterator_extra, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the severity from an all info iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return extra info secinfo entry,
 *         or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (all_info_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 2);


/* Users. */

/**
 * @brief Create the given user.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  name        Name of user.
 * @param[in]  role_name   Role of user.  Admin if NULL.
 *
 * @return 0 success, -1 error,
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_create_user (GSList *log_config, const gchar *database,
                    const gchar *name, const gchar *role_name)
{
  char *uuid;
  array_t *roles;
  const gchar *db;
  int ret;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  roles = make_array ();
  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          array_free (roles);
          cleanup_manage_process (TRUE);
          printf ("Internal Error.\n");
          return -1;
        }
      if (role == 0)
        {
          array_free (roles);
          cleanup_manage_process (TRUE);
          printf ("Failed to find role.\n");
          return -1;
        }
      array_add (roles, role_uuid (role));
    }
  else
    array_add (roles, g_strdup (ROLE_UUID_ADMIN));

  uuid = openvas_uuid_make ();

  /* Setup a dummy user, so that create_user will work. */
  current_credentials.uuid = "";

  ret = create_user (name, uuid, NULL, 0, NULL, 0, NULL, NULL, NULL, roles,
                     NULL, NULL, NULL, 0);

  switch (ret)
    {
      case 0:
        printf ("User created with password '%s'.\n", uuid);
        break;
      case -2:
        printf ("User exists already.\n");
        break;
      default:
        printf ("Failed to create user.\n");
        break;
    }

  current_credentials.uuid = NULL;

  array_free (roles);
  free (uuid);

  cleanup_manage_process (TRUE);

  return ret;
}

/**
 * @brief Delete the given user.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  name        Name of user.
 *
 * @return 0 success, 2 failed to find user, 4 user has active tasks, -1 error.
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_delete_user (GSList *log_config, const gchar *database,
                    const gchar *name)
{
  const gchar *db;
  int ret;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  /* Setup a dummy user, so that delete_user will work. */
  current_credentials.uuid = "";

  switch ((ret = delete_user (NULL, name, 1, 0)))
    {
      case 0:
        printf ("User deleted.\n");
        break;
      case 2:
        printf ("Failed to find user.\n");
        break;
      case 4:
        printf ("User has active tasks.\n");
        break;
      default:
        printf ("Internal Error.\n");
        break;
    }

  current_credentials.uuid = NULL;

  cleanup_manage_process (TRUE);

  return ret;
}

/**
 * @brief List users.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  role_name   Role name.
 *
 * @return 0 success, -1 error.
 */
int
manage_get_users (GSList *log_config, const gchar *database,
                  const gchar* role_name)
{
  iterator_t users;
  const gchar *db;
  int ret;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          cleanup_manage_process (TRUE);
          printf ("Internal Error.\n");
          return -1;
        }
      if (role == 0)
        {
          cleanup_manage_process (TRUE);
          printf ("Failed to find role.\n");
          return -1;
        }
      init_iterator (&users,
                     "SELECT name FROM users"
                     " WHERE id IN (SELECT user FROM role_users"
                     "              WHERE role = %llu);",
                     role);
    }
  else
    init_iterator (&users, "SELECT name FROM users;");
  while (next (&users))
    printf ("%s\n", iterator_string (&users, 0));
  cleanup_iterator (&users);

  cleanup_manage_process (TRUE);

  return 0;
}

/**
 * @brief List scanners.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 *
 * @return 0 success, -1 error.
 */
int
manage_get_scanners (GSList *log_config, const gchar *database)
{
  iterator_t scanners;
  const gchar *db;
  int ret;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  init_iterator (&scanners, "SELECT uuid, name FROM scanners;");
  while (next (&scanners))
    printf ("%s  %s\n", iterator_string (&scanners, 0),
            iterator_string (&scanners, 1));

  cleanup_iterator (&scanners);

  cleanup_manage_process (TRUE);

  return 0;
}

/**
 * @brief Set the password of a user.
 *
 * @param[in]  name      Name of user.
 * @param[in]  uuid      User UUID.
 * @param[in]  password  New password.
 * @param[out] r_errdesc Address to receive a malloced string with the error
 *                       description, or NULL.
 *
 * @return 0 success, -1 error.
 */
int
set_password (const gchar *name, const gchar *uuid, const gchar *password,
              gchar **r_errdesc)
{
  gchar *errstr, *hash;

  assert (name && uuid);

  if ((errstr = openvas_validate_password (password, name)))
    {
      g_warning ("new password for '%s' rejected: %s", name, errstr);
      if (r_errdesc)
        *r_errdesc = errstr;
      else
        g_free (errstr);
      return -1;
    }
  hash = get_password_hashes (GCRY_MD_MD5, password);
  sql ("UPDATE users SET password = '%s', modification_time = m_now ()"
       " WHERE uuid = '%s';",
       hash,
       uuid);
  g_free (hash);
  return 0;
}

/**
 * @brief Set the password of a user.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  database  Location of manage database.
 * @param[in]  name      Name of user.
 * @param[in]  password  New password.
 *
 * @return 0 success, 1 failed to find user, -1 error.
 */
int
manage_set_password (GSList *log_config, const gchar *database,
                     const gchar *name, const gchar *password)
{
  user_t user;
  char *uuid;
  int ret;
  const gchar *db;

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  sql_begin_immediate ();

  if (find_user_by_name (name, &user))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (user == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  uuid = user_uuid (user);
  if (uuid == NULL)
    {
      sql ("ROLLBACK;");
      return -1;
    }

  ret = set_password (name, uuid, password, NULL);
  sql ("COMMIT;");
  free (uuid);
  return ret;
}

/**
 * @brief Find a user for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of user.
 * @param[out]  user        User return, 0 if succesfully failed to find user.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
gboolean
find_user_with_permission (const char* uuid, user_t* user,
                           const char *permission)
{
  return find_resource_with_permission ("user", uuid, user, permission, 0);
}

/**
 * @brief Find a user given a name.
 *
 * @param[in]   name  A user name.
 * @param[out]  user  User return, 0 if succesfully failed to find user.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
gboolean
find_user_by_name_with_permission (const char* name, user_t *user,
                                   const char *permission)
{
  return find_resource_by_name_with_permission ("user", name, user, permission);
}

/**
 * @brief Find a user given a name.
 *
 * @param[in]   name  A user name.
 * @param[out]  user  User return, 0 if succesfully failed to find user.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
static gboolean
find_user_by_name (const char* name, user_t *user)
{
  return find_resource_by_name ("user", name, user);
}

/**
 * @brief Adds a new user to the OpenVAS installation.
 *
 * @todo Adding users authenticating with certificates is not yet implemented.
 *
 * @param[in]  name         The name of the new user.
 * @param[in]  password     The password of the new user.
 * @param[in]  hosts        The host the user is allowed/forbidden to scan.
 * @param[in]  hosts_allow  Whether hosts is allow or forbid.
 * @param[in]  ifaces       Interfaces the user is allowed/forbidden to scan.
 * @param[in]  ifaces_allow     Whether ifaces is allow or forbid.
 * @param[in]  allowed_methods  Allowed login methods.
 * @param[in]  groups       Groups.
 * @param[out] group_id_return  ID of group on "failed to find" error.
 * @param[in]  roles        Roles.
 * @param[out] role_id_return  ID of role on "failed to find" error.
 * @param[out] r_errdesc    If not NULL the address of a variable to receive
 *                          a malloced string with the error description.  Will
 *                          always be set to NULL on success.
 * @param[out] new_user     Created user.
 * @param[in]  forbid_super_admin  Whether to forbid creation of Super Admin.
 *
 * @return 0 if the user has been added successfully, 1 failed to find group,
 *         2 failed to find role, 3 syntax error in hosts, 99 permission denied,
 *         -1 on error, -2 if user exists already.
 */
int
create_user (const gchar * name, const gchar * password, const gchar * hosts,
             int hosts_allow, const gchar *ifaces, int ifaces_allow,
             const array_t * allowed_methods, array_t *groups,
             gchar **group_id_return, array_t *roles, gchar **role_id_return,
             gchar **r_errdesc, user_t *new_user, int forbid_super_admin)
{
  char *errstr;
  gchar *quoted_hosts, *quoted_ifaces, *quoted_method, *quoted_name, *hash;
  gchar *clean, *generated;
  int index, max;
  user_t user;

  assert (name);
  assert (password);

  if (r_errdesc)
    *r_errdesc = NULL;

  /* allowed_methods is a NULL terminated array. */
  if (allowed_methods && (allowed_methods->len > 2))
    return -3;

  if (allowed_methods && (allowed_methods->len == 0))
    allowed_methods = NULL;

  // TODO validate methods  single source, one of ldap, ...

  if (validate_username (name) != 0)
    {
      g_warning ("Invalid characters in user name!");
      if (r_errdesc)
        *r_errdesc = g_strdup ("Invalid characters in user name");
      return -1;
    }

  if (allowed_methods
      && strcmp (g_ptr_array_index (allowed_methods, 0), "ldap_connect") == 0)
    password = generated = openvas_uuid_make ();
  else
    generated = NULL;

  if ((errstr = openvas_validate_password (password, name)))
    {
      g_warning ("new password for '%s' rejected: %s", name, errstr);
      if (r_errdesc)
        *r_errdesc = errstr;
      else
        g_free (errstr);
      g_free (generated);
      return -1;
    }

  sql_begin_immediate ();

  if (user_may ("create_user") == 0)
    {
      sql ("ROLLBACK;");
      g_free (generated);
      return 99;
    }

  /* Check if user exists already. */

  if (resource_with_name_exists_global (name, "user", 0))
    {
      sql ("ROLLBACK;");
      g_free (generated);
      return -2;
    }
  quoted_name = sql_quote (name);

  /* Check hosts. */

  max = manage_max_hosts ();
  manage_set_max_hosts (MANAGE_USER_MAX_HOSTS);
  if (hosts && (manage_count_hosts (hosts, NULL) < 0))
    {
      manage_set_max_hosts (max);
      sql ("ROLLBACK;");
      g_free (generated);
      return 3;
    }
  manage_set_max_hosts (max);

  /* Get the password hashes. */

  hash = get_password_hashes (GCRY_MD_MD5, password);

  /* Add the user to the database. */

  clean = clean_hosts (hosts ? hosts : "", &max);
  quoted_hosts = sql_quote (clean);
  quoted_ifaces = sql_quote (ifaces ? ifaces : "");
  g_free (clean);
  quoted_method = sql_quote (allowed_methods
                              ? g_ptr_array_index (allowed_methods, 0)
                              : "file");
  sql ("INSERT INTO users"
       " (uuid, owner, name, password, hosts, hosts_allow,"
       "  ifaces, ifaces_allow, method, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (),"
       "  (SELECT id FROM users WHERE uuid = '%s'),"
       "  '%s', '%s', '%s', %i,"
       "  '%s', %i, '%s', m_now (), m_now ());",
       current_credentials.uuid,
       quoted_name,
       hash,
       quoted_hosts,
       hosts_allow,
       quoted_ifaces,
       ifaces_allow,
       quoted_method);
  user = sql_last_insert_id ();
  g_free (generated);
  g_free (hash);
  g_free (quoted_hosts);
  g_free (quoted_ifaces);
  g_free (quoted_method);
  g_free (quoted_name);

  /* Add the user to any given groups. */

  index = 0;
  while (groups && (index < groups->len))
    {
      gchar *group_id;
      group_t group;

      group_id = (gchar*) g_ptr_array_index (groups, index);
      if (strcmp (group_id, "0") == 0)
        {
          index++;
          continue;
        }

      if (find_group (group_id, &group))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (group == 0)
        {
          sql ("ROLLBACK;");
          if (group_id_return) *group_id_return = group_id;
          return 1;
        }

      sql ("INSERT INTO group_users (\"group\", \"user\") VALUES (%llu, %llu);",
           group,
           user);

      index++;
    }

  /* Add the user to any given roles. */

  index = 0;
  while (roles && (index < roles->len))
    {
      gchar *role_id;
      role_t role;

      role_id = (gchar*) g_ptr_array_index (roles, index);
      if (strcmp (role_id, "0") == 0)
        {
          index++;
          continue;
        }

      if (forbid_super_admin && role_can_super_everyone (role_id))
        {
          sql ("ROLLBACK;");
          return 99;
        }

      if (find_role (role_id, &role))
        {
          sql ("ROLLBACK;");
          return -1;
        }

      if (role == 0)
        {
          sql ("ROLLBACK;");
          if (role_id_return) *role_id_return = role_id;
          return 2;
        }

      sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);",
           role,
           user);

      index++;
    }

  if (new_user)
    *new_user = user;

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Create a user from an existing user.
 *
 * @param[in]  name      Name of new user.  NULL to copy from existing.
 * @param[in]  comment   Comment on new user.  NULL to copy from existing.
 * @param[in]  user_id   UUID of existing user.
 * @param[out] new_user  New user.
 *
 * @return 0 success, 1 user exists already, 2 failed to find existing
 *         user, 99 permission denied, -1 error.
 */
int
copy_user (const char* name, const char* comment, const char *user_id,
           user_t* new_user)
{
  user_t user;
  int ret;
  gchar *quoted_uuid;

  if (user_can_super_everyone (user_id))
    return 99;

  sql_begin_immediate ();

  ret = copy_resource_lock ("user", name, comment, user_id,
                            "password, timezone, hosts, hosts_allow,"
                            " ifaces, ifaces_allow, method",
                            1, &user, NULL);
  if (ret)
    {
      sql ("ROLLBACK;");
      return ret;
    }

  sql ("UPDATE users SET password = NULL WHERE id = %llu;", user);

  quoted_uuid = sql_quote (user_id);

  sql ("INSERT INTO group_users (\"user\", \"group\")"
       " SELECT %llu, \"group\" FROM group_users"
       " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');",
       user,
       quoted_uuid);

  sql ("INSERT INTO role_users (\"user\", role)"
       " SELECT %llu, role FROM role_users"
       " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');",
       user,
       quoted_uuid);

  g_free (quoted_uuid);

  sql ("COMMIT;");

  if (new_user)
    *new_user = user;

  return ret;
}

/**
 * @brief Delete a user.
 *
 * @param[in]  user_id_arg  UUID of user.
 * @param[in]  name_arg     Name of user.  Overridden by user_id.
 * @param[in]  ultimate     Whether to remove entirely, or to trashcan.
 * @param[in]  forbid_super_admin  Whether to forbid removal of Super Admin.
 *
 * @return 0 success, 2 failed to find user, 4 user has active tasks,
 *         5 attempted suicide, 99 permission denied, -1 error.
 */
int
delete_user (const char *user_id_arg, const char *name_arg, int ultimate,
             int forbid_super_admin)
{
  iterator_t tasks;
  user_t user;
  get_data_t get;
  char *current_uuid;

  assert (user_id_arg || name_arg);

  if (current_credentials.username && current_credentials.uuid)
    {
      if (user_id_arg)
        {
          if (strcmp (user_id_arg, current_credentials.uuid) == 0)
            return 5;
        }
      else if (name_arg
               && (strcmp (name_arg, current_credentials.username) == 0))
        return 5;
    }

  sql_begin_exclusive ();

  if (user_may ("delete_user") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  user = 0;
  if (user_id_arg)
    {
      if (forbid_super_admin
          && (strcmp (user_id_arg, ROLE_UUID_SUPER_ADMIN) == 0))
        {
          sql ("ROLLBACK;");
          return 99;
        }

      if (find_user_with_permission (user_id_arg, &user, "delete_user"))
        {
          sql ("ROLLBACK;");
          return -1;
        }
    }
  else if (find_user_by_name_with_permission (name_arg, &user, "delete_user"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (user == 0)
    return 2;

  if (forbid_super_admin)
    {
      char *uuid;

      uuid = user_uuid (user);
      if (user_is_super_admin (uuid))
        {
          free (uuid);
          sql ("ROLLBACK;");
          return 99;
        }
      free (uuid);
    }

  /* Set requested and running tasks to stopped. */

  memset (&get, '\0', sizeof (get));
  current_uuid = current_credentials.uuid;
  current_credentials.uuid = sql_string ("SELECT uuid FROM users"
                                         " WHERE id = %llu;",
                                         user);
  init_user_task_iterator (&tasks, 0);
  while (next (&tasks))
    switch (task_iterator_run_status (&tasks))
      {
        case TASK_STATUS_DELETE_REQUESTED:
        case TASK_STATUS_DELETE_ULTIMATE_REQUESTED:
        case TASK_STATUS_DELETE_ULTIMATE_WAITING:
        case TASK_STATUS_DELETE_WAITING:
        case TASK_STATUS_REQUESTED:
        case TASK_STATUS_RUNNING:
        case TASK_STATUS_STOP_REQUESTED_GIVEUP:
        case TASK_STATUS_STOP_REQUESTED:
        case TASK_STATUS_STOP_WAITING:
          {
            cleanup_iterator (&tasks);
            free (current_credentials.uuid);
            current_credentials.uuid = current_uuid;
            sql ("ROLLBACK;");
            return 4;
          }
        default:
          break;
      }
  cleanup_iterator (&tasks);
  free (current_credentials.uuid);
  current_credentials.uuid = current_uuid;

  sql ("DELETE FROM agents WHERE owner = %llu;", user);
  sql ("DELETE FROM agents_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM alert_condition_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_condition_data"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_event_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_event_data"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_method_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_method_data"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alerts WHERE owner = %llu;", user);
  sql ("DELETE FROM alerts_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM nvt_selectors"
       " WHERE name IN (SELECT nvt_selector FROM configs WHERE owner = %llu)"
       " AND name != '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
       " AND name != '" MANAGE_NVT_SELECTOR_UUID_DISCOVERY "'"
       " AND name != '" MANAGE_NVT_SELECTOR_UUID_HOST_DISCOVERY "'"
       " AND name != '" MANAGE_NVT_SELECTOR_UUID_SYSTEM_DISCOVERY "';",
       user);
  sql ("DELETE FROM config_preferences"
       " WHERE config IN (SELECT id FROM configs WHERE owner = %llu);",
       user);
  sql ("DELETE FROM config_preferences_trash"
       " WHERE config IN (SELECT id FROM configs_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM configs WHERE owner = %llu;", user);
  sql ("DELETE FROM configs_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM filters WHERE owner = %llu;", user);
  sql ("DELETE FROM filters_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM lsc_credentials WHERE owner = %llu;", user);
  sql ("DELETE FROM lsc_credentials_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM notes WHERE owner = %llu;", user);
  sql ("DELETE FROM notes_trash WHERE owner = %llu;", user);

  /* Make permissions global if they are owned by the user and are related
   * to users/groups/roles that are owned by the user. */
  sql ("UPDATE permissions SET owner = NULL"
       " WHERE owner = %llu"
       " AND ((subject_type = 'user' AND subject IN (SELECT id FROM users WHERE owner = %llu))"
       "      OR (subject_type = 'group' AND subject IN (SELECT id FROM groups WHERE owner = %llu))"
       "      OR (subject_type = 'role' AND subject IN (SELECT id FROM roles WHERE owner = %llu))"
       "      OR (resource_type = 'user' AND resource IN (SELECT id FROM users WHERE owner = %llu))"
       "      OR (resource_type = 'group' AND resource IN (SELECT id FROM groups WHERE owner = %llu))"
       "      OR (resource_type = 'role' AND resource IN (SELECT id FROM roles WHERE owner = %llu)));",
       user,
       user,
       user,
       user,
       user,
       user,
       user);

  /* Make users, roles and groups global if they are owned by the user. */
  sql ("UPDATE users SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE roles SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE groups SET owner = NULL WHERE owner = %llu;", user);

  /* Remove all other permissions owned by the user or given on the user. */
  sql ("DELETE FROM permissions"
       " WHERE owner = %llu"
       " OR subject_type = 'user' AND subject = %llu"
       " OR (resource_type = 'user' AND resource = %llu);",  /* For Super. */
       user,
       user,
       user);

  sql ("DELETE FROM port_ranges"
       " WHERE port_list IN (SELECT id FROM port_lists WHERE owner = %llu);",
       user);
  sql ("DELETE FROM port_ranges_trash"
       " WHERE port_list IN (SELECT id FROM port_lists_trash"
       "                     WHERE owner = %llu);",
       user);
  sql ("DELETE FROM port_lists WHERE owner = %llu;", user);
  sql ("DELETE FROM port_lists_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM report_format_param_options"
       " WHERE report_format_param"
       "       IN (SELECT id FROM report_format_params"
       "           WHERE report_format IN (SELECT id"
       "                                   FROM report_formats"
       "                                   WHERE owner = %llu));",
       user);
  sql ("DELETE FROM report_format_param_options_trash"
       " WHERE report_format_param"
       "       IN (SELECT id FROM report_format_params_trash"
       "           WHERE report_format IN (SELECT id"
       "                                   FROM report_formats_trash"
       "                                   WHERE owner = %llu));",
       user);
  sql ("DELETE FROM report_format_params"
       " WHERE report_format IN (SELECT id FROM report_formats"
       "                         WHERE owner = %llu);",
       user);
  sql ("DELETE FROM report_format_params_trash"
       " WHERE report_format IN (SELECT id"
       "                         FROM report_formats_trash"
       "                         WHERE owner = %llu);",
       user);
  sql ("DELETE FROM report_formats WHERE owner = %llu;", user);
  sql ("DELETE FROM report_formats_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM report_counts WHERE \"user\" = %llu", user);
  sql ("DELETE FROM report_host_details"
       " WHERE report_host IN (SELECT id FROM report_hosts"
       "                       WHERE report IN (SELECT id FROM reports"
       "                                        WHERE owner = %llu));",
       user);
  sql ("DELETE FROM report_hosts"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);
  sql ("DELETE FROM results"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);
  sql ("DELETE FROM reports WHERE owner = %llu;", user);

  sql ("DELETE FROM overrides WHERE owner = %llu;", user);
  sql ("DELETE FROM overrides_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM permissions WHERE owner = %llu;", user);
  sql ("DELETE FROM permissions_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM schedules WHERE owner = %llu;", user);
  sql ("DELETE FROM schedules_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM slaves WHERE owner = %llu;", user);
  sql ("DELETE FROM slaves_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM settings WHERE owner = %llu;", user);
  sql ("DELETE FROM tags WHERE owner = %llu;", user);
  sql ("DELETE FROM tags_trash WHERE owner = %llu;", user);
  sql ("DELETE FROM targets WHERE owner = %llu;", user);
  sql ("DELETE FROM targets_trash WHERE owner = %llu;", user);

  sql ("DELETE FROM task_files"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM task_alerts"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM task_preferences"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM tasks WHERE owner = %llu;", user);

  sql ("UPDATE groups SET owner = 0 WHERE owner = %llu;", user);
  sql ("UPDATE roles SET owner = 0 WHERE owner = %llu;", user);
  sql ("UPDATE users SET owner = 0 WHERE owner = %llu;", user);

  sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM group_users_trash WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM role_users WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM role_users_trash WHERE \"user\" = %llu;", user);

  sql ("DELETE FROM users WHERE id = %llu;", user);

  sql ("COMMIT;");
  return 0;
}

/**
 * @brief Modify a user.
 *
 * @param[in]  user_id      The UUID of the user.  Overrides name.
 * @param[in]  name         The name of the user.  If NULL then set to name
 *                          when return is 3 or 4.
 * @param[in]  new_name     New name for the user.  NULL to leave as is.
 * @param[in]  password     The password of the user.  NULL to leave as is.
 * @param[in]  hosts        The host the user is allowed/forbidden to scan.
 *                          NULL to leave as is.
 * @param[in]  hosts_allow  Whether hosts is allow or forbid.
 * @param[in]  ifaces       Interfaces the user is allowed/forbidden to scan.
 * @param[in]  ifaces_allow     Whether ifaces is allow or forbid.
 * @param[in]  allowed_methods  Allowed login methods.
 * @param[in]  groups           Groups.
 * @param[out] group_id_return  ID of group on "failed to find" error.
 * @param[in]  roles            Roles.
 * @param[out] role_id_return   ID of role on "failed to find" error.
 * @param[out] r_errdesc    If not NULL the address of a variable to receive
 *                          a malloced string with the error description.  Will
 *                          always be set to NULL on success.
 *
 * @return 0 if the user has been added successfully, 1 failed to find group,
 *         2 failed to find user, 3 success and user gained admin, 4 success
 *         and user lost admin, 5 failed to find role, 6 syntax error in hosts,
 *         7 syntax error in new name, 99 permission denied, -1 on error,
 *         -2 for an unknown role, -3 if wrong number of methods.
 */
int
modify_user (const gchar * user_id, gchar **name, const gchar *new_name,
             const gchar * password, const gchar * hosts, int hosts_allow,
             const gchar *ifaces, int ifaces_allow,
             const array_t * allowed_methods, array_t *groups,
             gchar **group_id_return, array_t *roles, gchar **role_id_return,
             gchar **r_errdesc)
{
  char *errstr;
  gchar *hash, *quoted_hosts, *quoted_ifaces, *quoted_method, *clean, *uuid;
  gchar *quoted_new_name;
  user_t user;
  int max, was_admin, is_admin;

  if (r_errdesc)
    *r_errdesc = NULL;

  /* allowed_methods is a NULL terminated array. */
  if (allowed_methods && (allowed_methods->len > 2))
    return -3;

  if (allowed_methods && (allowed_methods->len == 0))
    allowed_methods = NULL;

  if (allowed_methods
      && ((g_ptr_array_index (allowed_methods, 0) == NULL)
          || (strlen (g_ptr_array_index (allowed_methods, 0)) == 0)))
    allowed_methods = NULL;

  // FIX validate methods  single source, one of "", "ldap", ...

  sql_begin_immediate ();

  if (user_may ("modify_user") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  user = 0;
  if (user_id)
    {
      if (find_user_with_permission (user_id, &user, "modify_user"))
        {
          sql ("ROLLBACK;");
          return -1;
        }
    }
  else if (find_user_by_name_with_permission (*name, &user, "modify_user"))
    {
      sql ("ROLLBACK;");
      return -1;
    }
  if (user == 0)
    {
      sql ("ROLLBACK;");
      return 2;
    }

  uuid = sql_string ("SELECT uuid FROM users WHERE id = %llu",
                     user);

  /* The only user that can edit a Super Admin is the Super Admin themself. */
  if (user_can_super_everyone (uuid) && strcmp (uuid, current_credentials.uuid))
    {
      g_free (uuid);
      sql ("ROLLBACK;");
      return 99;
    }

  was_admin = user_is_admin (uuid);

  if (password)
    {
      char *user_name;

      user_name = sql_string ("SELECT name FROM users WHERE id = %llu",
                              user);
      errstr = openvas_validate_password (password, user_name);
      g_free (user_name);
      if (errstr)
        {
          g_warning ("new password for '%s' rejected: %s", user_name, errstr);
          if (r_errdesc)
            *r_errdesc = errstr;
          else
            g_free (errstr);
          sql ("ROLLBACK;");
          return -1;
        }
    }

  /* Check hosts. */

  max = manage_max_hosts ();
  manage_set_max_hosts (MANAGE_USER_MAX_HOSTS);
  if (hosts && (manage_count_hosts (hosts, NULL) < 0))
    {
      manage_set_max_hosts (max);
      sql ("ROLLBACK;");
      return 6;
    }
  manage_set_max_hosts (max);

  /* Check new name. */

  if (new_name)
    {
      if (validate_username (new_name) != 0)
        {
          sql ("ROLLBACK");
          return 7;
        }

      if (strcmp (uuid, current_credentials.uuid) == 0)
        {
          sql ("ROLLBACK;");
          return 99;
        }

      if (resource_with_name_exists_global (new_name, "user", user))
        {
          sql ("ROLLBACK;");
          return 8;
        }
      quoted_new_name = sql_quote (new_name);
    }
  else
    quoted_new_name = NULL;

  /* Get the password hashes. */

  if (password)
    hash = get_password_hashes (GCRY_MD_MD5, password);
  else
    hash = NULL;

  /* Update the user in the database. */

  clean = clean_hosts (hosts ? hosts : "", &max);
  if ((hosts_allow == 0) && (max == 0))
    /* Convert "Deny none" to "Allow All". */
    hosts_allow = 2;
  quoted_ifaces = sql_quote (ifaces ? ifaces : "");
  quoted_hosts = sql_quote (clean);
  g_free (clean);
  quoted_method = sql_quote (allowed_methods
                              ? g_ptr_array_index (allowed_methods, 0)
                              : "");
  sql ("UPDATE users"
       " SET name = %s%s%s,"
       "     hosts = '%s',"
       "     hosts_allow = '%i',"
       "     ifaces = '%s',"
       "     ifaces_allow = %i,"
       "     method = %s%s%s,"
       "     modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_new_name ? "'" : "",
       quoted_new_name ? quoted_new_name : "name",
       quoted_new_name ? "'" : "",
       quoted_hosts,
       hosts_allow,
       quoted_ifaces,
       ifaces_allow,
       allowed_methods ? "'" : "",
       allowed_methods ? quoted_method : "method",
       allowed_methods ? "'" : "",
       user);
  g_free (quoted_new_name);
  g_free (quoted_hosts);
  g_free (quoted_ifaces);
  g_free (quoted_method);
  if (hash)
    sql ("UPDATE users"
         " SET password = '%s'"
         " WHERE id = %llu;",
         hash,
         user);
  g_free (hash);

  /* Update the user groups. */

  if (groups)
    {
      int index;

      sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user);
      index = 0;
      while (groups && (index < groups->len))
        {
          gchar *group_id;
          group_t group;

          group_id = (gchar*) g_ptr_array_index (groups, index);
          if (strcmp (group_id, "0") == 0)
            {
              index++;
              continue;
            }

          if (find_group (group_id, &group))
            {
              sql ("ROLLBACK;");
              return -1;
            }

          if (group == 0)
            {
              sql ("ROLLBACK;");
              if (group_id_return) *group_id_return = group_id;
              return 1;
            }

          sql ("INSERT INTO group_users (\"group\", \"user\")"
               " VALUES (%llu, %llu);",
               group,
               user);

          index++;
        }
    }

  /* Update the user roles. */

  if (roles)
    {
      int index;

      sql ("DELETE FROM role_users"
           " WHERE \"user\" = %llu"
           " AND role != (SELECT id from roles"
           "              WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "');",
           user);
      index = 0;
      while (roles && (index < roles->len))
        {
          gchar *role_id;
          role_t role;

          role_id = (gchar*) g_ptr_array_index (roles, index);
          if (strcmp (role_id, "0") == 0)
            {
              index++;
              continue;
            }

          if (find_role (role_id, &role))
            {
              sql ("ROLLBACK;");
              return -1;
            }

          if (role == 0)
            {
              sql ("ROLLBACK;");
              if (role_id_return) *role_id_return = role_id;
              return 1;
            }

          if (role_can_super_everyone (role_id))
            {
              sql ("ROLLBACK;");
              return 99;
            }

          sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);",
               role,
               user);

          index++;
        }
    }

  sql ("COMMIT;");

  if (was_admin)
    {
      is_admin = user_is_admin (uuid);
      g_free (uuid);
      if (is_admin)
        return 0;
      if (*name == NULL)
        *name = sql_string ("SELECT name FROM users WHERE id = %llu",
                            user);
      return 4;
    }

  is_admin = user_is_admin (uuid);
  g_free (uuid);
  if (is_admin)
    {
      if (*name == NULL)
        *name = sql_string ("SELECT name FROM users WHERE id = %llu",
                            user);
      return 3;
    }
  return 0;
}

/**
 * @brief Return the UUID of a user.
 *
 * Warning: this is only safe for users that are known to be in the db.
 *
 * @param[in]  user  User.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
user_uuid (user_t user)
{
  return sql_string ("SELECT uuid FROM users WHERE id = %llu;",
                     user);
}

/**
 * @brief Check whether a user is in use.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
user_in_use (user_t user)
{
  return 0;
}

/**
 * @brief Check whether a trashcan user is in use.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
trash_user_in_use (user_t user)
{
  return 0;
}

/**
 * @brief Check whether a user is writable.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
user_writable (user_t user)
{
  return 1;
}

/**
 * @brief Check whether a trashcan user is writable.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
trash_user_writable (user_t user)
{
  return 1;
}

/**
 * @brief User columns for user iterator.
 */
#define USER_ITERATOR_FILTER_COLUMNS                                  \
 { GET_ITERATOR_FILTER_COLUMNS, "method", "roles", "groups", "hosts", \
   "ifaces", NULL }

/**
 * @brief User iterator columns.
 */
#define USER_ITERATOR_COLUMNS                                              \
 {                                                                         \
   GET_ITERATOR_COLUMNS (users),                                           \
   { "method", NULL },                                                     \
   { "hosts", NULL },                                                      \
   { "hosts_allow", NULL },                                                \
   {                                                                       \
     "coalesce ((SELECT group_concat (name, ', ')"                         \
     "           FROM (SELECT DISTINCT name, order_role (name)"            \
     "                 FROM roles, role_users"                             \
     "                 WHERE role_users.role = roles.id"                   \
     "                 AND \"user\" = users.id"                            \
     "                 ORDER BY order_role (roles.name) ASC)"              \
     "                 AS user_iterator_sub),"                             \
     "           '')",                                                     \
     "roles"                                                               \
   },                                                                      \
   {                                                                       \
     "coalesce ((SELECT group_concat (name, ', ')"                         \
     "           FROM (SELECT DISTINCT name FROM groups, group_users"      \
     "                 WHERE group_users.\"group\" = groups.id"            \
     "                 AND \"user\" = users.id"                            \
     "                 ORDER BY groups.name ASC)"                          \
     "                AS user_iterator_sub),"                              \
     "           '')",                                                     \
     "groups"                                                              \
   },                                                                      \
   { "ifaces", NULL },                                                     \
   { "ifaces_allow", NULL },                                               \
   { NULL, NULL }                                                          \
 }

/**
 * @brief User iterator columns for trash case.
 */
#define USER_ITERATOR_TRASH_COLUMNS                                        \
 {                                                                         \
   GET_ITERATOR_COLUMNS (users_trash),                                     \
   { "method", NULL },                                                     \
   { "hosts", NULL },                                                      \
   { "hosts_allow", NULL },                                                \
   { "ifaces", NULL },                                                     \
   { "ifaces_allow", NULL },                                               \
   { NULL, NULL }                                                          \
 }

/**
 * @brief Count number of users.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of users in usered set.
 */
int
user_count (const get_data_t *get)
{
  static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = USER_ITERATOR_COLUMNS;
  return count ("user", get, columns, NULL, filter_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Initialise a user iterator, including observed users.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find user, failed to find user (filt_id),
 *         -1 error.
 */
int
init_user_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = USER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = USER_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "user",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the method of the user from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Method of the user or NULL if iteration is complete.
 */
DEF_ACCESS (user_iterator_method, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the hosts from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts or NULL if iteration is complete.
 */
DEF_ACCESS (user_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the hosts allow value from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts allow.
 */
int
user_iterator_hosts_allow (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
}

/**
 * @brief Get the ifaces from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Interfaces or NULL if iteration is complete.
 */
DEF_ACCESS (user_iterator_ifaces, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the ifaces allow value from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Interfaces allow.
 */
int
user_iterator_ifaces_allow (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

/**
 * @brief Initialise an info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  user            User.
 */
void
init_user_group_iterator (iterator_t *iterator, user_t user)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (user);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_groups"));
  available = where_owned ("group", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT DISTINCT id, uuid, name, %s FROM groups"
                 " WHERE id IN (SELECT \"group\" FROM group_users"
                 "              WHERE \"user\" = %llu)"
                 " ORDER by name;",
                 available,
                 user);

  g_free (available);
}

/**
 * @brief Get the UUID from a user group iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_group_iterator_uuid, 1);

/**
 * @brief Get the NAME from a user group iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NAME or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_group_iterator_name, 2);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
user_group_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 3);
}

/**
 * @brief Initialise an info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  user            User.
 */
void
init_user_role_iterator (iterator_t *iterator, user_t user)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (user);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_roles"));
  available = where_owned ("role", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT DISTINCT id, uuid, name, order_role (name), %s"
                 " FROM roles"
                 " WHERE id IN (SELECT role FROM role_users"
                 "              WHERE \"user\" = %llu)"
                 " ORDER by order_role (name);",
                 available,
                 user);

  g_free (available);
}

/**
 * @brief Get the UUID from a user role iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return UUID or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_role_iterator_uuid, 1);

/**
 * @brief Get the NAME from a user role iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NAME or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_role_iterator_name, 2);

/**
 * @brief Get the read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
user_role_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 4);
}


/* Tags */

/**
 * @brief Find a tag for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of tag.
 * @param[out]  tag         Tag return, 0 if succesfully failed to find tag.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find tag), TRUE on error.
 */
gboolean
find_tag_with_permission (const char* uuid, tag_t* tag,
                          const char *permission)
{
  return find_resource_with_permission ("tag", uuid, tag, permission, 0);
}

/**
 * @brief Create a tag from an existing tag.
 *
 * @param[in]  name        Name of new tag.  NULL to copy from existing.
 * @param[in]  comment     Comment on new tag.  NULL to copy from existing.
 * @param[in]  tag_id      UUID of existing tag.
 * @param[out] new_tag     New tag.
 *
 * @return 0 success, 2 failed to find existing tag,
 *         99 permission denied, -1 error.
 */
int
copy_tag (const char* name, const char* comment, const char *tag_id,
          tag_t* new_tag)
{
  return copy_resource ("tag", name, comment, tag_id,
                        "value, resource_type, resource, resource_location,"
                        " resource_uuid, active",
                        1, new_tag);
}

/**
 * @brief Create a tag.
 *
 * @param[in]  name          Name of the tag.
 * @param[in]  comment       Comment for the tag.
 * @param[in]  value         Value of the tag.
 * @param[in]  resource_type Resource type to attach the tag to.
 * @param[in]  resource_uuid Unique ID of the resource to attach the tag to.
 * @param[in]  active        0 for inactive, NULL or any other value for active.
 * @param[out] tag          Created tag.
 *
 * @return 0 success, 99 permission denied, -1 error.
 */
int
create_tag (const char * name, const char * comment, const char * value,
            const char * resource_type, const char * resource_uuid,
            const char * active, tag_t * tag)
{
  gchar *quoted_name, *quoted_comment, *quoted_value;
  gchar *lc_resource_type, *quoted_resource_type, *quoted_resource_uuid;
  int resource_location = LOCATION_TABLE;

  gchar *resource_permission = NULL;
  resource_t resource;

  sql_begin_immediate ();

  if (user_may ("create_tag") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  lc_resource_type = g_ascii_strdown (resource_type, -1);
  if (strcmp (lc_resource_type, "")
      && valid_db_resource_type (lc_resource_type) == 0)
    {
      g_free (lc_resource_type);
      sql ("ROLLBACK;");
      return -1;
    }

  if ((strcmp (lc_resource_type, "cpe") == 0)
      || (strcmp (lc_resource_type, "cve") == 0)
      || (strcmp (lc_resource_type, "ovaldef") == 0)
      || (strcmp (lc_resource_type, "cert_bund_adv") == 0)
      || (strcmp (lc_resource_type, "dfn_cert_adv") == 0))
    resource_permission = g_strdup ("get_info");
  else
    resource_permission = g_strdup_printf ("get_%ss", resource_type);

  if (find_resource_with_permission (resource_type, resource_uuid,
                                     &resource, resource_permission, 0))
    {
      sql ("ROLLBACK;");
      g_free (lc_resource_type);
      g_free (resource_permission);
      return -1;
    }
  else if (resource == 0
           && (strcmp (lc_resource_type, "nvt"))
           && (strcmp (lc_resource_type, "cve"))
           && (strcmp (lc_resource_type, "cpe"))
           && (strcmp (lc_resource_type, "ovaldef"))
           && (strcmp (lc_resource_type, "cert_bund_adv"))
           && (strcmp (lc_resource_type, "dfn_cert_adv"))
           && (strcmp (lc_resource_type, "report"))
           && (strcmp (lc_resource_type, "result"))
           && (strcmp (lc_resource_type, "user")))
    {
      if (find_resource_with_permission (resource_type, resource_uuid,
                                         &resource, resource_permission, 1))
        {
          sql ("ROLLBACK;");
          g_free (lc_resource_type);
          g_free (resource_permission);
          return -1;
        }
      else if (resource != 0)
        resource_location = LOCATION_TRASH;
    }

  quoted_name = sql_insert (name);
  quoted_resource_type = sql_insert (lc_resource_type);
  quoted_resource_uuid = resource_uuid ? sql_insert (resource_uuid)
                                       : g_strdup ("''");

  quoted_comment = sql_insert (comment ? comment : "");
  quoted_value = sql_insert (value ? value : "");
  sql ("INSERT INTO tags"
      " (uuid, owner, creation_time, modification_time, name, comment,"
      "  value, resource_type, resource_uuid, resource, resource_location,"
      "  active)"
      " VALUES"
      " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s'),"
      "  %i, %i, %s, %s, %s, %s, %s, %llu, %d, %i);",
      current_credentials.uuid,
      time (NULL),
      time (NULL),
      quoted_name,
      quoted_comment,
      quoted_value,
      quoted_resource_type,
      quoted_resource_uuid,
      resource,
      resource_location,
      active
       ? (strcmp (active, "0") == 0
           ? 0
           : 1)
       : 1);

  g_free (quoted_name);
  g_free (lc_resource_type);
  g_free (quoted_resource_type);
  g_free (quoted_resource_uuid);
  g_free (quoted_comment);
  g_free (quoted_value);

  if (tag)
    *tag = sql_last_insert_id ();

  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Delete a tag.
 *
 * @param[in]  tag_id     UUID of tag.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 2 failed to find tag, 99 permission denied, -1 error.
 */
int
delete_tag (const char *tag_id, int ultimate)
{
  tag_t tag = 0;

  sql_begin_immediate ();

  if (user_may ("delete_tag") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  if (find_tag_with_permission (tag_id, &tag, "delete_tag"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (tag == 0)
    {
      if (find_trash ("tag", tag_id, &tag))
        {
          sql ("ROLLBACK;");
          return -1;
        }
      if (tag == 0)
        {
          sql ("ROLLBACK;");
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql ("COMMIT;");
          return 0;
        }

      permissions_set_orphans ("tag", tag, LOCATION_TRASH);

      sql ("DELETE FROM tags_trash WHERE id = %llu;", tag);
      sql ("COMMIT;");
      return 0;
    }

  if (ultimate == 0)
    {
      sql ("INSERT INTO tags_trash"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, resource_type, resource, resource_uuid,"
           "  resource_location, active, value)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, resource_type, resource, resource_uuid,"
           "        resource_location, active, value"
           " FROM tags WHERE id = %llu;",
           tag);

      permissions_set_locations ("tag", tag,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("tag", tag, LOCATION_TABLE);
      tags_set_orphans ("tag", tag, LOCATION_TABLE);
    }

  sql ("DELETE FROM tags WHERE id = %llu;", tag);
  sql ("COMMIT;");

  return 0;
}

/**
 * @brief Modify a tag.
 *
 * @param[in]   tag_id        UUID of tag.
 * @param[in]   name          New name of the tag or NULL.
 * @param[in]   comment       New comment for the tag or NULL.
 * @param[in]   value         New value of the tag or NULL.
 * @param[in]   resource_type New resource type to attach the tag to or NULL.
 * @param[in]   resource_uuid New Unique ID of the resource to attach or NULL.
 * @param[in]   active        0 for inactive, any other for active or NULL.
 *
 * @return 0 success, 1 failed to find tag, 2 tag_id required, 99 permission
 *         denied, -1 internal error.
 */
int
modify_tag (const char *tag_id, const char *name, const char *comment,
            const char *value, const char *resource_type,
            const char *resource_uuid, const char *active)
{
  gchar *quoted_name, *quoted_comment, *quoted_value;
  gchar *lc_resource_type, *quoted_resource_type, *quoted_resource_uuid;
  tag_t tag;

  if (tag_id == NULL)
    return 2;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (user_may ("modify_tag") == 0)
    {
      sql ("ROLLBACK;");
      return 99;
    }

  tag = 0;
  if (find_tag_with_permission (tag_id, &tag, "modify_tag"))
    {
      sql ("ROLLBACK;");
      return -1;
    }

  if (tag == 0)
    {
      sql ("ROLLBACK;");
      return 1;
    }

  lc_resource_type = (resource_type
                      ? g_ascii_strdown (resource_type, -1)
                      : g_strdup (""));
  if (strcmp (lc_resource_type, "")
      && valid_db_resource_type (lc_resource_type) == 0)
    {
      sql ("ROLLBACK;");
      return -1;
    }

  quoted_resource_type = sql_insert (lc_resource_type);
  quoted_name = sql_insert (name ? name : "");
  quoted_resource_uuid = sql_insert (resource_uuid ? resource_uuid : "");
  quoted_comment = sql_insert (comment ? comment : "");
  quoted_value = sql_insert (value ? value : "");

  if (name)
    {
      sql ("UPDATE tags SET"
           " name = %s"
           " WHERE id = %llu;",
           quoted_name,
           tag);
    }

  if (resource_type)
    {
      sql ("UPDATE tags SET"
           " resource_type = %s"
           " WHERE id = %llu;",
           quoted_resource_type,
           tag);
    }

  if (resource_uuid)
    {
      resource_t resource;
      gchar *resource_permission;
      int resource_location = LOCATION_TABLE;

      if ((strcmp (lc_resource_type, "cpe") == 0)
          || (strcmp (lc_resource_type, "cve") == 0)
          || (strcmp (lc_resource_type, "ovaldef") == 0)
          || (strcmp (lc_resource_type, "cert_bund_adv") == 0)
          || (strcmp (lc_resource_type, "dfn_cert_adv") == 0))
        resource_permission = g_strdup ("get_info");
      else
        resource_permission = g_strdup_printf ("get_%ss", resource_type);

      if (find_resource_with_permission (resource_type, resource_uuid,
                                         &resource, resource_permission, 0))
        {
          sql ("ROLLBACK;");
          g_free (lc_resource_type);
          g_free (resource_permission);
          return -1;
        }
      else if (resource == 0
              && (strcmp (lc_resource_type, "nvt"))
              && (strcmp (lc_resource_type, "cve"))
              && (strcmp (lc_resource_type, "cpe"))
              && (strcmp (lc_resource_type, "ovaldef"))
              && (strcmp (lc_resource_type, "cert_bund_adv"))
              && (strcmp (lc_resource_type, "dfn_cert_adv"))
              && (strcmp (lc_resource_type, "report"))
              && (strcmp (lc_resource_type, "result"))
              && (strcmp (lc_resource_type, "user")))
        {
          if (find_resource_with_permission (resource_type, resource_uuid,
                                             &resource, resource_permission, 1))
            {
              sql ("ROLLBACK;");
              g_free (lc_resource_type);
              g_free (resource_permission);
              return -1;
            }
          else if (resource != 0)
            resource_location = LOCATION_TRASH;
        }

      sql ("UPDATE tags SET"
           " resource_uuid = %s,"
           " resource = %llu,"
           " resource_location = %d"
           " WHERE id = %llu;",
           quoted_resource_uuid,
           resource,
           resource_location,
           tag);
    }

  if (comment)
    {
      sql ("UPDATE tags SET"
           " comment = %s"
           " WHERE id = %llu;",
           quoted_comment,
           tag);
    }

  if (value)
    {
      sql ("UPDATE tags SET"
           " value = %s"
           " WHERE id = %llu;",
           quoted_value,
           tag);
    }

  if (active)
    {
      sql ("UPDATE tags SET"
           " active = %i"
           " WHERE id = %llu;",
           strcmp (active, "0") ? 1 : 0,
           tag);
    }

  sql ("UPDATE tags SET"
       " modification_time = %i"
       " WHERE id = %llu;",
       time (NULL),
       tag);

  g_free (quoted_name);
  g_free (lc_resource_type);
  g_free (quoted_resource_type);
  g_free (quoted_resource_uuid);
  g_free (quoted_comment);
  g_free (quoted_value);

  sql ("COMMIT;");

  return 0;
}


/**
 * @brief Filter columns for Tag iterator.
 */
#define TAG_ITERATOR_FILTER_COLUMNS                                           \
 { GET_ITERATOR_FILTER_COLUMNS, "resource_type", "resource", "resource_uuid", \
   "resource_location", "active", "value", "orphan", "resource_name", NULL }

/**
 * @brief Tag iterator columns.
 */
#define TAG_ITERATOR_COLUMNS                                                  \
 {                                                                           \
   GET_ITERATOR_COLUMNS (tags),                                              \
   { "resource_type", NULL },                                                \
   { "resource", NULL },                                                     \
   { "resource_uuid", NULL },                                                \
   { "resource_location", NULL },                                            \
   { "active", NULL },                                                       \
   { "value", NULL },                                                        \
   { "(resource = 0)", "orphan" },                                           \
   {                                                                         \
     "resource_name (resource_type, resource_uuid, resource_location)",      \
     "resource_name"                                                         \
   },                                                                        \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Tag iterator trash columns.
 */
#define TAG_ITERATOR_TRASH_COLUMNS                                           \
 {                                                                           \
   GET_ITERATOR_COLUMNS (tags_trash),                                        \
   { "resource_type", NULL },                                                \
   { "resource", NULL },                                                     \
   { "resource_uuid", NULL },                                                \
   { "resource_location", NULL },                                            \
   { "active", NULL },                                                       \
   { "value", NULL },                                                        \
   { "(resource = 0)", "orphan" },                                           \
   {                                                                         \
     "resource_name (resource_type, resource_uuid, resource_location)",      \
     "resource_name"                                                         \
   },                                                                        \
   { NULL, NULL }                                                            \
 }

/**
 * @brief Filter columns for Tag name iterator.
 */
#define TAG_NAME_ITERATOR_FILTER_COLUMNS                         \
 { "name", "resource_type", NULL }

/**
 * @brief Tag name iterator columns.
 */
#define TAG_NAME_ITERATOR_COLUMNS                                \
 {                                                               \
   { "name" , NULL },                                            \
   { "resource_type" , NULL },                                   \
   { NULL, NULL }                                                \
 }

/**
 * @brief Initialise a tag iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find tag, 2 failed to find filter,
 *         -1 error.
 */
int
init_tag_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = TAG_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TAG_ITERATOR_COLUMNS;
  static column_t trash_columns[] = TAG_ITERATOR_TRASH_COLUMNS;

  return init_get_iterator (iterator,
                            "tag",
                            get,
                            columns,
                            trash_columns,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Count number of tags.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of tags in filtered set.
 */
int
tag_count (const get_data_t *get)
{
  static const char *filter_columns[] = TAG_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TAG_ITERATOR_COLUMNS;
  static column_t trash_columns[] = TAG_ITERATOR_TRASH_COLUMNS;

  return count ("tag", get, columns, trash_columns, filter_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Get the resource_type from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The resource type attached to a tag.
 */
DEF_ACCESS (tag_iterator_resource_type, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the resource_uuid from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the resource attached to a tag.
 */
DEF_ACCESS (tag_iterator_resource_uuid, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the resource_location from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Location of the resource of a tag (0 = normal table, 1 = trashcan).
 */
int
tag_iterator_resource_location (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
  return ret;
}

/**
 * @brief Get the readable status of a resource from a tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if readable, otherwise 0.
 */
int
tag_iterator_resource_readable (iterator_t* iterator)
{
  resource_t found;
  const char *type, *uuid;
  gchar *permission;

  if (iterator->done) return 0;

  type = tag_iterator_resource_type (iterator);
  uuid = tag_iterator_resource_uuid (iterator);

  if (type == NULL || uuid == NULL)
    return 0;

  if ((strcmp (type, "cpe") == 0)
      || (strcmp (type, "cve") == 0)
      || (strcmp (type, "ovaldef") == 0)
      || (strcmp (type, "cert_bund_adv") == 0)
      || (strcmp (type, "dfn_cert_adv") == 0))
    permission = g_strdup ("get_info");
  else
    permission = g_strdup_printf ("get_%ss", type);

  found = 0;
  find_resource_with_permission (type,
                                 uuid,
                                 &found,
                                 permission,
                                 tag_iterator_resource_location (iterator)
                                 == LOCATION_TRASH);
  g_free (permission);
  return found > 0;
}

/**
 * @brief Get if a tag is active from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether a tag is active (0 = inactive, 1 = active).
 */
int
tag_iterator_active (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
  return ret;
}

/**
 * @brief Get the value from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value associated with a tag.
 */
DEF_ACCESS (tag_iterator_value, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get if a tag is orphaned from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether a tag is orphaned.
 */
int
tag_iterator_orphan (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
  return ret;
}

/**
 * @brief Get the resource_name from a Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the resource attached to a tag.
 */
DEF_ACCESS (tag_iterator_resource_name, GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Initialise a iterator of tag names.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET params.
 *
 * @return 0 success, -1 error.
 */
int
init_tag_name_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = TAG_NAME_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = TAG_NAME_ITERATOR_COLUMNS;

  return init_get_iterator (iterator,
                            "tag",
                            get,
                            columns,
                            columns,
                            filter_columns,
                            1,
                            NULL,
                            NULL,
                            TRUE);
}

/**
 * @brief Get the name from a Tag name iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The tag name.
 */
DEF_ACCESS (tag_name_iterator_name, 0);

/**
 * @brief Initialise a iterator of tags attached to a resource.
 *
 * @param[in]  iterator         Iterator.
 * @param[in]  type             Resource type.
 * @param[in]  resource         Resource.
 * @param[in]  active_only      Whether to select only active tags.
 * @param[in]  sort_field       Field to sort by.
 * @param[in]  ascending        Whether to sort in ascending order.
 *
 * @return 0 success, -1 error.
 */
int
init_resource_tag_iterator (iterator_t* iterator, const char* type,
                            resource_t resource, int active_only,
                            const char* sort_field, int ascending)
{
  assert (type);
  assert (resource);
  assert (current_credentials.uuid);

  init_iterator (iterator,
                 "SELECT id, uuid, name, value, comment"
                 " FROM tags"
                 " WHERE resource_type = '%s'"
                 " AND resource = %llu"
                 "%s"
                 " AND ((owner IS NULL)"
                 "      OR (owner ="
                 "          (SELECT id FROM users WHERE users.uuid = '%s')))"
                 " ORDER BY %s %s;",
                 type,
                 resource,
                 active_only ? " AND active=1" : "",
                 current_credentials.uuid,
                 sort_field ? sort_field : "active DESC, name",
                 ascending ? "ASC" : "DESC");

  return 0;
}

/**
 * @brief Get the Tag UUID from a resource Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the tag.
 */
DEF_ACCESS (resource_tag_iterator_uuid, 1);

/**
 * @brief Get the Tag name from a resource Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the tag.
 */
DEF_ACCESS (resource_tag_iterator_name, 2);

/**
 * @brief Get the Tag value from a resource Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the tag.
 */
DEF_ACCESS (resource_tag_iterator_value, 3);

/**
 * @brief Get the Tag comment from a resource Tag iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The comment of the tag.
 */
DEF_ACCESS (resource_tag_iterator_comment, 4);

/**
 * @brief Count number of tags attached to a resource.
 *
 * @param[in]  type         Resource type.
 * @param[in]  resource     Resource.
 * @param[in]  active_only  Whether to count only active tags.
 *
 * @return Total number of tags attached to the resource.
 */
int
resource_tag_count (const char* type, resource_t resource, int active_only)
{
  int ret;

  assert (type);
  assert (resource);

  ret = sql_int ("SELECT count (id)"
                 " FROM tags"
                 " WHERE resource_type = '%s'"
                 "   AND resource = %llu"
                 "   %s;",
                 type,
                 resource,
                 active_only ? "AND active=1": "");

  return ret;
}

/**
 * @brief Return whether a tag is in use by a task.
 *
 * @param[in]  tag  Tag.
 *
 * @return 1 if in use, else 0.
 */
int
tag_in_use (tag_t tag)
{
  return 0;
}

/**
 * @brief Return whether a trashcan tag is referenced by a task.
 *
 * @param[in]  tag  Tag.
 *
 * @return 1 if in use, else 0.
 */
int
trash_tag_in_use (tag_t tag)
{
  return 0;
}

/**
 * @brief Return whether a tag is writable.
 *
 * @param[in]  tag  Tag.
 *
 * @return 1 if writable, else 0.
 */
int
tag_writable (tag_t tag)
{
  return 1;
}

/**
 * @brief Return whether a trashcan tag is writable.
 *
 * @param[in]  tag  Tag.
 *
 * @return 1 if writable, else 0.
 */
int
trash_tag_writable (tag_t tag)
{
  return 0;
}

/**
 * @brief Return the SQL column definition for a resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return The SQL column definitions.
 */
char*
type_columns (const char *type, int apply_overrides)
{
  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    {
      static column_t columns[] = TASK_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "ALLINFO") == 0)
    {
      static column_t columns[] = ALL_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "CPE") == 0)
    {
      static column_t columns[] = CPE_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "CVE") == 0)
    {
      static column_t columns[] = CVE_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "CERT_BUND_ADV") == 0)
    {
      static column_t columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "DFN_CERT_ADV") == 0)
    {
      static column_t columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "NVT") == 0)
    return g_strdup (nvt_iterator_columns ());
  else if (strcasecmp (type, "OVALDEF") == 0)
    {
      static column_t columns[] = OVALDEF_INFO_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "ALERT") == 0)
    {
      static column_t columns[] = ALERT_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else
    return NULL;
}

// FIX
/**
 * @brief Return the columns for a resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return The columns.
 */
column_t *
type_select_columns (const char *type, int apply_overrides)
{
  static column_t task_columns[] = TASK_ITERATOR_COLUMNS;
  static column_t allinfo_columns[] = ALL_INFO_ITERATOR_COLUMNS;
  static column_t cpe_columns[] = CPE_INFO_ITERATOR_COLUMNS;
  static column_t cve_columns[] = CVE_INFO_ITERATOR_COLUMNS;
  static column_t cert_bund_adv_columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
  static column_t dfn_cert_adv_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  static column_t nvt_columns[] = NVT_ITERATOR_COLUMNS;
  static column_t ovaldef_columns[] = OVALDEF_INFO_ITERATOR_COLUMNS;
  static column_t alert_columns[] = ALERT_ITERATOR_COLUMNS;

  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    return task_columns;
  else if (strcasecmp (type, "ALLINFO") == 0)
    return allinfo_columns;
  else if (strcasecmp (type, "CPE") == 0)
    return cpe_columns;
  else if (strcasecmp (type, "CVE") == 0)
    return cve_columns;
  else if (strcasecmp (type, "CERT_BUND_ADV") == 0)
    return cert_bund_adv_columns;
  else if (strcasecmp (type, "DFN_CERT_ADV") == 0)
    return dfn_cert_adv_columns;
  else if (strcasecmp (type, "NVT") == 0)
    return nvt_columns;
  else if (strcasecmp (type, "OVALDEF") == 0)
    return ovaldef_columns;
  else if (strcasecmp (type, "ALERT") == 0)
    return alert_columns;
  return NULL;
}

/**
 * @brief Return the filter columns for a resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return The filter columns.
 */
const char**
type_filter_columns (const char *type, int apply_overrides)
{
  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    {
      static const char *ret[] = TASK_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "ALLINFO") == 0)
    {
      static const char *ret[] = ALL_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "CPE") == 0)
    {
      static const char *ret[] = CPE_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "CVE") == 0)
    {
      static const char *ret[] = CVE_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "CERT_BUND_ADV") == 0)
    {
      static const char *ret[] = CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "DFN_CERT_ADV") == 0)
    {
      static const char *ret[] = DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "NVT") == 0)
    {
      static const char *ret[] = NVT_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "OVALDEF") == 0)
    {
      static const char *ret[] = OVALDEF_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "ALERT") == 0)
    {
      static const char *ret[] = ALERT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else
    return NULL;

}

/**
 * @brief Return the SQL column definition for a trash resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return The SQL column definitions.
 */
char*
type_trash_columns (const char *type, int apply_overrides)
{
  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    {
      static column_t columns[] = TASK_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "ALERT") == 0)
    {
      static column_t columns[] = ALERT_ITERATOR_TRASH_COLUMNS;
      return columns_build_select (columns);
    }
  else
    return NULL;
}

/**
 * @brief Return the subquery definition for a resource type.
 *
 * @param[in]  type             Resource type to get columns of.
 * @param[in]  apply_overrides  Whether to apply overrides.
 *
 * @return The SQL subquery definition.
 */
gchar*
type_opts_table (const char *type, int apply_overrides, int min_qod)
{
  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    {
      return task_iterator_opts_table (apply_overrides, min_qod);
    }
  else
    return NULL;
}

/**
 * @brief Return the table name or union for a resource type.
 *
 * @param[in]  type   Resource type to get columns of.
 * @param[in]  trash  Whether to get the trash table.
 *
 * @return The SQL column definitions.
 */
char*
type_table (const char *type, int trash)
{
  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "ALLINFO") == 0)
    {
      return g_strdup (ALL_INFO_UNION_COLUMNS);
    }
  else if (trash && type_trash_in_table (type) == 0)
    {
      return g_strdup_printf ("%ss_trash", type);
    }
  else if (trash == 0 || type_trash_in_table (type))
    {
      return g_strdup_printf ("%ss", type);
    }
  else
    return NULL;
}

/**
 * @brief Adjust location of resource in tags.
 *
 * @param[in]   type  Type.
 * @param[in]   old   Resource ID in old table.
 * @param[in]   new   Resource ID in new table.
 * @param[in]   to    Destination, trash or table.
 */
void
tags_set_locations (const char *type, resource_t old, resource_t new,
                    int to)
{
  sql ("UPDATE tags SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
  sql ("UPDATE tags_trash SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
}

/**
 * @brief Set tags to orphan.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource.
 * @param[in]  location  Location: table or trash.
 */
void
tags_set_orphans (const char *type, resource_t resource, int location)
{
  sql ("UPDATE tags SET resource = 0, resource_location = "
       G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
  sql ("UPDATE tags_trash SET resource = 0, resource_location = "
       G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
}


/* Optimize. */

/**
 * @brief Run one of the optimizations.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  name        Name of optimization.
 *
 * @return 0 success, 1 error in name, -1 error,
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_optimize (GSList *log_config, const gchar *database, const gchar *name)
{
  const gchar *db;
  gchar *success_text;
  int ret;

  if (name == NULL)
    {
      printf ("Name required for optimize.\n");
      return 1;
    }

  if (openvas_auth_init_funcs (manage_user_hash, manage_user_set_role,
                               manage_user_exists, manage_user_uuid))
    return -1;

  db = database ? database : sql_default_database ();
  success_text = NULL;

  ret = init_manage_helper (log_config, db, 70000, NULL);
  assert (ret != -4);
  if (ret)
    return ret;

  init_manage_process (0, db);

  ret = 0;
  if (strcasecmp (name, "vacuum") == 0)
    {
      struct stat state;
      long long int old_size, new_size;

      old_size = 0LL;
      new_size = 0LL;
      ret = stat (db, &state);
      if (ret)
        switch (errno)
          {
            case ENOENT:
              break;
            default:
              g_warning ("%s: failed to stat database: %s",
                          __FUNCTION__,
                          strerror (errno));
          }
      else
        old_size = state.st_size;

      if (sql_is_sqlite3 ())
        sql ("VACUUM;");
      else
        {
          sql_begin_exclusive ();
          sql ("VACUUM;");
          sql ("COMMIT;");
        }

      ret = stat (db, &state);
      if (ret)
        switch (errno)
          {
            case ENOENT:
              break;
            default:
              g_warning ("%s: failed to stat database: %s",
                          __FUNCTION__,
                          strerror (errno));
          }
      else
        new_size = state.st_size;

      if (old_size && new_size)
        success_text = g_strdup_printf ("Optimized: vacuum."
                                        " Database file size reduced by"
                                        " %lld MiB (%0.1f %%).\n",
                                        (old_size - new_size) / (1024 * 1024),
                                        (old_size - new_size)
                                          * 100.0 / old_size);
      else
        success_text = g_strdup_printf ("Optimized: vacuum.");
    }
  else if (strcasecmp (name, "analyze") == 0)
    {
      sql ("ANALYZE;");
      success_text = g_strdup_printf ("Optimized: analyze.");
    }
  else if (strcasecmp (name, "cleanup-config-prefs") == 0)
    {
      int changes;
      sql ("DELETE FROM config_preferences WHERE id NOT IN"
           " (SELECT min(id) FROM config_preferences"
           "  GROUP BY config, type, name, value);");
      changes = sql_changes();
      success_text = g_strdup_printf ("Optimized: cleanup-config-prefs."
                                      " Duplicate config preferences removed:"
                                      " %d.",
                                      changes);
    }
  else if (strcasecmp (name, "remove-open-port-results") == 0)
    {
      int changes;
      sql ("DELETE FROM results WHERE nvt='0';");
      changes = sql_changes();
      success_text = g_strdup_printf ("Optimized: remove-open-port-results."
                                      " Superfluous open port results removed:"
                                      " %d.",
                                      changes);
    }
  else if (strcasecmp (name, "cleanup-port-names") == 0)
    {
      int changes_iana, changes_old_format;

      sql_begin_exclusive ();
      sql ("UPDATE results"
           " SET port = substr (port, 1,"
           "                    strpos (port, ' (IANA:') - 1)"
           " WHERE port LIKE '% (IANA:%';");
      changes_iana = sql_changes();
      sql ("UPDATE results"
           " SET port = substr (port,"
           "                    strpos (port ,'(') + 1,"
           "                    strpos (port, ')') - strpos (port, '(') - 1)"
           " WHERE port LIKE '%(%)%';");
      changes_old_format = sql_changes();
      sql ("COMMIT;");

      success_text = g_strdup_printf ("Optimized: cleanup-port-names."
                                      " Ports converted from old format: %d,"
                                      " removed IANA port names: %d.",
                                      changes_old_format, changes_iana);
    }
  else if (strcasecmp (name, "rebuild-report-cache") == 0)
    {
      int changes;

      sql_begin_exclusive ();

      reports_build_count_cache (1, &changes);

      sql ("COMMIT;");

      success_text = g_strdup_printf ("Optimized: rebuild-report-cache."
                                      " Result counts recalculated for %d"
                                      " report/user combinations.",
                                      changes);
    }
  else if (strcasecmp (name, "update-report-cache") == 0)
    {
      int changes;

      sql_begin_exclusive ();

      reports_build_count_cache (0, &changes);

      sql ("COMMIT;");

      success_text = g_strdup_printf ("Optimized: update-report-cache."
                                      " Result counts calculated for %d"
                                      " report/user combinations.",
                                      changes);
    }
  else
    {
      printf ("Error in optimize name.\n");
      ret = 1;
    }

  if (success_text)
    {
      printf ("%s\n", success_text);
      g_message ("   %s", success_text);
      g_free (success_text);
    }

  current_credentials.uuid = NULL;

  cleanup_manage_process (TRUE);

  return ret;
}
