/* 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 "utils.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/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.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/misc/openvas_auth.h>
#include <openvas/misc/ldap_connect_auth.h>
#include <openvas/misc/radius.h>
#include <openvas/misc/openvas_logging.h>
#include <openvas/misc/openvas_uuid.h>
#include <openvas/misc/openvas_server.h>
#include <openvas/misc/openvas_ssh.h>
#include <openvas/base/pwpolicy.h>
#include <openvas/omp/omp.h>

#undef G_LOG_DOMAIN
/**
 * @brief GLib log domain.
 */
#define G_LOG_DOMAIN "md manage"

/**
 * @brief Scanner (openvassd) address.
 */
#define OPENVASSD_ADDRESS OPENVAS_RUN_DIR "/openvassd.sock"

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

int
manage_create_sql_functions ();

void
create_tables ();

void
check_db_sequences ();

static int
check_db_encryption_key ();

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.
 *
 * 1 if set via scheduler, 2 if set via event, else 0.
 */
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 (const char *, int, const char *, const char *,
                       const char *);

int
set_certs (const char *, const char *, const char *);

gchar *
predefined_report_format_dir (const gchar *);

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_discovery_service_detection (char * const);

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

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

int
check_config_discovery (const char *);

void
check_config_host_discovery (const char *);

int
check_config_system_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, int);

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

static gchar* apply_report_format (gchar *, gchar *, gchar *, gchar *,
                                   GList **);

static int cache_report_counts (report_t, int, 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
verify_report_format_internal (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 *);

static int
create_permission_internal (const char *, const char *, const char *,
                            const char *, const char *, const char *,
                            permission_t *);

static int
check_config_families ();

static int
task_second_last_report (task_t, report_t *);

static gchar *
new_secinfo_message (event_t, const void*, alert_t);

static gchar *
new_secinfo_list (event_t, const void*, alert_t, int*);

static void
check_for_new_scap ();

static void
check_for_new_cert ();

static void
check_for_updated_scap ();

static void
check_for_updated_cert ();

static gchar*
results_extra_where (const get_data_t*, report_t, const gchar*,
                     int, int, int, const gchar*);

static int
report_counts_id_full (report_t, int *, int *, int *, int *, int *, int *,
                       double *, const get_data_t*, const char* ,
                       int *, int *, int *, int *, int *, int *, double *);

static int
check_report_format (const gchar *);


/* Variables. */

/**
 * @brief Function to fork a connection that will accept OMP requests.
 */
int (*manage_fork_connection) (openvas_connection_t *, gchar*) = NULL;

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

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

/**
 * @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 Default max number of bytes of user-defined message in email alerts.
 */
#define MAX_EMAIL_MESSAGE_LENGTH 2000

/**
 * @brief Maximum number of bytes of user-defined message text in email alerts.
 *
 * A value less or equal to 0 allows any size.
 */
static int max_email_message_length = MAX_EMAIL_MESSAGE_LENGTH;

/**
 * @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_ASSET", "Create an asset."},
    {"CREATE_CONFIG", "Create a config."},
    {"CREATE_CREDENTIAL", "Create a credential."},
    {"CREATE_FILTER", "Create a filter."},
    {"CREATE_GROUP", "Create a group."},
    {"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_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_ASSET", "Delete an asset."},
    {"DELETE_CONFIG", "Delete a config."},
    {"DELETE_CREDENTIAL", "Delete a credential."},
    {"DELETE_FILTER", "Delete a filter."},
    {"DELETE_GROUP", "Delete a group."},
    {"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_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."},
    {"EMPTY_TRASHCAN", "Empty the trashcan."},
    {"GET_AGENTS", "Get all agents."},
    {"GET_AGGREGATES", "Get aggregates of resources."},
    {"GET_ALERTS", "Get all alerts."},
    {"GET_ASSETS", "Get all assets."},
    {"GET_CONFIGS", "Get all configs."},
    {"GET_CREDENTIALS", "Get all credentials."},
    {"GET_FEEDS", "Get details of one or all feeds this Manager uses."},
    {"GET_FILTERS", "Get all filters."},
    {"GET_GROUPS", "Get all groups."},
    {"GET_INFO", "Get raw information for a given item."},
    {"GET_NOTES", "Get all notes."},
    {"GET_NVTS", "Get one or all available NVTs."},
    {"GET_NVT_FAMILIES", "Get a list of all NVT families."},
    {"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_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_ASSET", "Modify an existing asset."},
    {"MODIFY_AUTH", "Modify the authentication methods."},
    {"MODIFY_CONFIG", "Update an existing config."},
    {"MODIFY_CREDENTIAL", "Modify an existing credential."},
    {"MODIFY_FILTER", "Modify an existing filter."},
    {"MODIFY_GROUP", "Modify an existing group."},
    {"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_TAG", "Modify an existing tag."},
    {"MODIFY_TARGET", "Modify an existing target."},
    {"MODIFY_TASK", "Update an existing task."},
    {"MODIFY_USER", "Modify a user."},
    {"MOVE_TASK", "Assign task to another slave scanner, even while running."},
    {"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_CONFIG", "Synchronize a config with a scanner."},
    {"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 " ACL_USER_OWNS () ";",
                   quoted_type, quoted_name, resource,
                   current_credentials.uuid);
  else
    ret = sql_int ("SELECT COUNT(*) FROM %ss"
                   " WHERE name = '%s'"
                   " AND " ACL_USER_OWNS () ";",
                   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 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.
 */
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 (acl_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". */

  memset (&tm, 0, sizeof (struct tm));
  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.
 */
int
parse_iso_time (const char *text_time)
{
  int epoch_time;
  struct tm tm;

  memset (&tm, 0, sizeof (struct tm));
  tm.tm_isdst = -1;
  if (strptime ((char*) text_time, "%FT%T%z", &tm) == NULL)
    {
      gchar *tz;

      memset (&tm, 0, sizeof (struct tm));
      tm.tm_isdst = -1;
      if (strptime ((char*) text_time, "%FT%TZ", &tm) == NULL)
        {
          /* Try time without timezone suffix, applying timezone of user */

          memset (&tm, 0, sizeof (struct tm));
          tm.tm_isdst = -1;
          if (strptime ((char*) text_time, "%FT%T", &tm) == NULL)
            return parse_ctime (text_time);

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

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

          memset (&tm, 0, sizeof (struct tm));
          tm.tm_isdst = -1;
          if (strptime ((char*) text_time, "%FT%T", &tm) == NULL)
            {
              assert (0);
              g_warning ("%s: Failed to parse time", __FUNCTION__);
              if (tz != NULL)
                setenv ("TZ", tz, 1);
              g_free (tz);
              return 0;
            }
        }
      else
        {
          /* Time has "Z" suffix for UTC */

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

          memset (&tm, 0, sizeof (struct tm));
          tm.tm_isdst = -1;
          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. */

      memset (&tm, 0, sizeof (struct tm));
      tm.tm_isdst = -1;
      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 Calculate difference between now and epoch_time in days
 *
 * @param[in]  epoch_time  Time in seconds from epoch.
 *
 * @return Int days bettween now and epoch_time or -1 if epoch_time is in the
 * past
 */
int
days_from_now (time_t *epoch_time)
{
    time_t now = time (NULL);
    int diff = *epoch_time - now;

    if (diff < 0) return -1;
    return diff / 86400; /* 60 sec * 60 min * 24 h */
}

/**
 * @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 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 $ */
  memset (&tm, 0, sizeof (struct tm));
  if (strptime ((char*) string, "%F %T %z", &tm) == NULL)
    {
      memset (&tm, 0, sizeof (struct tm));
      if (strptime ((char*) string, "$Date: %F %T %z", &tm) == NULL)
        {
          memset (&tm, 0, sizeof (struct tm));
          if (strptime ((char*) string, "%a %b %d %T %Y %z", &tm) == NULL)
            {
              memset (&tm, 0, sizeof (struct tm));
              if (strptime ((char*) string, "$Date: %a, %d %b %Y %T %z", &tm)
                  == NULL)
                {
                  memset (&tm, 0, sizeof (struct tm));
                  if (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;
}

/**
 * @brief Get last time NVT alerts were checked.
 *
 * @return Last check time.
 */
static int
nvts_check_time ()
{
  return sql_int ("SELECT"
                  " CASE WHEN EXISTS (SELECT * FROM meta"
                  "                   WHERE name = 'nvts_check_time')"
                  "      THEN CAST ((SELECT value FROM meta"
                  "                  WHERE name = 'nvts_check_time')"
                  "                 AS INTEGER)"
                  "      ELSE 0"
                  "      END;");
}

/**
 * @brief Get last time SCAP SecInfo alerts were checked.
 */
static int
scap_check_time ()
{
  return sql_int ("SELECT"
                  " CASE WHEN EXISTS (SELECT * FROM meta"
                  "                   WHERE name = 'scap_check_time')"
                  "      THEN CAST ((SELECT value FROM meta"
                  "                  WHERE name = 'scap_check_time')"
                  "                 AS INTEGER)"
                  "      ELSE 0"
                  "      END;");
}

/**
 * @brief Get last time CERT SecInfo alerts were checked.
 */
static int
cert_check_time ()
{
  return sql_int ("SELECT"
                  " CASE WHEN EXISTS (SELECT * FROM meta"
                  "                   WHERE name = 'cert_check_time')"
                  "      THEN CAST ((SELECT value FROM meta"
                  "                  WHERE name = 'cert_check_time')"
                  "                 AS INTEGER)"
                  "      ELSE 0"
                  "      END;");
}

/**
 * @brief Setup for an option process.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Database.
 *
 * @return 0 success, -1 error, -2 database is wrong version,
 *         -3 database needs to be initialised from server.
 */
int
manage_option_setup (GSList *log_config, const gchar *database)
{
  const gchar *db;
  int ret;

  if (openvas_auth_init ())
    {
      fprintf (stderr, "Authentication init failed\n");
      return -1;
    }

  db = database ? database : sql_default_database ();

  ret = init_manage_helper (log_config, db, MANAGE_ABSOLUTE_MAX_IPS_PER_TARGET, NULL);
  assert (ret != -4);
  switch (ret)
    {
      case 0:
        break;
      case -2:
        fprintf (stderr, "Database is wrong version.\n");
        return ret;
      case -3:
        fprintf (stderr,
                 "Database must be initialised"
                 " (with --update or --rebuild).\n");
        return ret;
      default:
        fprintf (stderr, "Internal error.\n");
        return ret;
    }

  init_manage_process (0, db);

  return 0;
}

/**
 * @brief Cleanup for an option process.
 */
void
manage_option_cleanup ()
{
  cleanup_manage_process (TRUE);
}

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

/**
 * @brief Print an array of columns.
 *
 * @param[in]  columns  Columns.
 */
void
column_array_print (column_t *columns)
{
  g_debug ("%s: %p", __FUNCTION__, columns);
  g_debug ("%s: {", __FUNCTION__);
  while (columns->select)
    {
      g_debug ("%s:   { \"%s\", \"%s\", %i }",
               __FUNCTION__,
               columns->select,
               columns->filter ? columns->filter : "NULL",
               columns->type);
      columns++;
    }
  g_debug ("%s: }", __FUNCTION__);
}

/**
 * @brief Copy an array of columns.
 *
 * @param[in]  columns  Columns.
 */
column_t *
column_array_copy (column_t *columns)
{
  column_t *point, *start;
  int count;

  count = 1;
  point = columns;
  while (point->select)
    {
      point++;
      count++;
    }

  g_debug ("%s: %i", __FUNCTION__, count);
  start = g_malloc0 (sizeof (column_t) * count);
  point = start;

  while (columns->select)
    {
      point->select = g_strdup (columns->select);
      point->filter = columns->filter ? g_strdup (columns->filter) : NULL;
      point->type = columns->type;
      point++;
      columns++;
    }
  return start;
}

/**
 * @brief Free an array of columns.
 *
 * @param[in]  columns  Columns.
 */
void
column_array_free (column_t *columns)
{
  while (columns->filter)
    {
      g_free (columns->select);
      g_free (columns->filter);
      columns++;
    }
  g_free (columns);
}

/**
 * @brief Set the select clause of a column in an array of columns.
 *
 * Frees the existing select clause.
 *
 * @param[in]  columns  Columns.
 * @param[in]  filter   Filter term name.
 * @param[in]  select   Select clause.
 */
void
column_array_set (column_t *columns, const gchar *filter, gchar *select)
{
  while (columns->select)
    {
      if (columns->filter && (strcmp (columns->filter, filter) == 0))
        {
          g_free (columns->select);
          columns->select = select;
          break;
        }
      columns++;
    }
}


/* 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 (memset (&date, 0, sizeof (date)),
               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;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "autofp") == 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, "autofp") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "delta_states") == 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, "delta_states") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "levels") == 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, "levels") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "min_qod") == 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, "min_qod") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "notes") == 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, "notes") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "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, "overrides") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "result_hosts_only") == 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, "result_hosts_only") == 0))
            return 0;
        }
    }

  if (keyword->column
      && (strcmp (keyword->column, "timezone") == 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, "timezone") == 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 Flag to control the default sorting produced by split filter.
 *
 * If this is true, and the filter does not specify a sort field, then
 * split_filter will not insert a default sort term, so that the random
 * (and fast) table order in the database will be used.
 */
int table_order_if_sort_not_specified = 0;

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

  assert (given_filter);

  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 (table_order_if_sort_not_specified == 0 && 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.
 *
 * It's up to the caller to ensure that max is adjusted for Max Rows Per Page
 * (by calling manage_max_rows).
 *
 * @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 (SETTING_UUID_ROWS_PER_PAGE, 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.
 *
 * It's up to the caller to ensure that max is adjusted for Max Rows Per Page
 * (by calling manage_max_rows).
 *
 * @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_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_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 (SETTING_UUID_ROWS_PER_PAGE, 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))
        // FIX name??
        *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_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)
    {
      int max;

      if (strcmp (keyword->string, "-2") == 0)
        setting_value_int (SETTING_UUID_ROWS_PER_PAGE, &max);
      else
        max = atoi (keyword->string);

      g_string_append_printf (clean,
                              " %s%c%i",
                              keyword->column,
                              relation,
                              manage_max_rows (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 Get the column expression for a filter column.
 *
 * @param[in]  select_columns  SELECT columns.
 * @param[in]  filter_column   Filter column.
 * @param[out] type            Type of returned column.
 *
 * @return Column for the SELECT statement.
 */
static gchar *
columns_select_column_single (column_t *select_columns,
                              const char *filter_column,
                              keyword_type_t* type)
{
  column_t *columns;
  if (type)
    *type = KEYWORD_TYPE_UNKNOWN;
  if (select_columns == NULL)
    return NULL;
  columns = select_columns;
  while ((*columns).select)
    {
      if ((*columns).filter
          && strcmp ((*columns).filter, filter_column) == 0)
        {
          if (type)
            *type = (*columns).type;
          return (*columns).select;
        }
      if ((*columns).filter
          && *((*columns).filter)
          && *((*columns).filter) == '_'
          && strcmp (((*columns).filter) + 1, filter_column) == 0)
        {
          if (type)
            *type = (*columns).type;
          return (*columns).select;
        }
      columns++;
    }
  columns = select_columns;
  while ((*columns).select)
    {
      if (strcmp ((*columns).select, filter_column) == 0)
        {
          if (type)
            *type = (*columns).type;
          return (*columns).select;
        }
      columns++;
    }
  return NULL;
}

/**
 * @brief Get the selection term for a filter column.
 *
 * @param[in]  select_columns  SELECT columns.
 * @param[in]  where_columns   WHERE "columns".
 * @param[in]  filter_column   Filter column.
 *
 * @return Column for the SELECT statement.
 */
static gchar *
columns_select_column (column_t *select_columns,
                       column_t *where_columns,
                       const char *filter_column)
{
  gchar *column;
  column = columns_select_column_single (select_columns, filter_column, NULL);
  if (column)
    return column;
  return columns_select_column_single (where_columns, filter_column, NULL);
}

/**
 * @brief Get the selection term for a filter column.
 *
 * @param[in]  select_columns  SELECT columns.
 * @param[in]  where_columns   WHERE "columns".
 * @param[in]  filter_column   Filter column.
 * @param[out] type            Type of the returned column.
 *
 * @return Column for the SELECT statement.
 */
static gchar *
columns_select_column_with_type (column_t *select_columns,
                                 column_t *where_columns,
                                 const char *filter_column,
                                 keyword_type_t* type)
{
  gchar *column;
  column = columns_select_column_single (select_columns, filter_column, type);
  if (column)
    return column;
  return columns_select_column_single (where_columns, filter_column, type);
}

/**
 * @brief Return column list for SELECT statement.
 *
 * @param[in]  select_columns  SELECT columns.
 *
 * @return Column list 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 Check whether a keyword applies to a column.
 *
 * @param[in]  keyword  Keyword.
 * @param[in]  column   Column.
 *
 * @return 1 if applies, else 0.
 */
static int
keyword_applies_to_column (keyword_t *keyword, const char* column)
{
  if ((strcmp (column, "threat") == 0)
      && (strstr ("None", keyword->string) == NULL)
      && (strstr ("False Positive", keyword->string) == NULL)
      && (strstr ("Debug", keyword->string) == NULL)
      && (strstr ("Error", keyword->string) == NULL)
      && (strstr ("Alarm", keyword->string) == NULL)
      && (strstr ("High", keyword->string) == NULL)
      && (strstr ("Medium", keyword->string) == NULL)
      && (strstr ("Low", keyword->string) == NULL)
      && (strstr ("Log", keyword->string) == NULL))
    return 0;
  if ((strcmp (column, "trend") == 0)
      && (strstr ("more", keyword->string) == NULL)
      && (strstr ("less", keyword->string) == NULL)
      && (strstr ("up", keyword->string) == NULL)
      && (strstr ("down", keyword->string) == NULL)
      && (strstr ("same", keyword->string) == NULL))
    return 0;
  if ((strcmp (column, "status") == 0)
      && (strstr ("Delete Requested", keyword->string) == NULL)
      && (strstr ("Ultimate Delete Requested", keyword->string) == NULL)
      && (strstr ("Done", keyword->string) == NULL)
      && (strstr ("New", keyword->string) == NULL)
      && (strstr ("Running", keyword->string) == NULL)
      && (strstr ("Stop Requested", keyword->string) == NULL)
      && (strstr ("Stopped", keyword->string) == NULL)
      && (strstr ("Internal Error", keyword->string) == NULL))
    return 0;
  return 1;
}

/**
 * @brief Return SQL WHERE clause for restricting a SELECT to a filter term.
 *
 * @param[in]  type     Resource type.
 * @param[in]  filter   Filter term.
 * @param[in]  filter_columns  Filter columns.
 * @param[in]  select_columns  SELECT columns.
 * @param[in]  where_columns   Columns in SQL that only appear in WHERE clause.
 * @param[out] trash           Whether the trash table is being queried.
 * @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,
               column_t *where_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 ("");
  /* NB This may add terms that are missing, like "sort". */
  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 (vector_find_filter (filter_columns, keyword->string) == 0)
            {
              point++;
              continue;
            }

          if (first_order)
            {
              if ((strcmp (type, "task") == 0)
                  && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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
                       || strcmp (keyword->string, "fp_per_host") == 0
                       || strcmp (keyword->string, "log_per_host") == 0
                       || strcmp (keyword->string, "low_per_host") == 0
                       || strcmp (keyword->string, "medium_per_host") == 0
                       || strcmp (keyword->string, "high_per_host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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,
                                                  where_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)
                       || (strcmp (keyword->string, "hosts") == 0)
                       || (strcmp (keyword->string, "result_hosts") == 0)
                       || (strcmp (keyword->string, "latest_severity") == 0)
                       || (strcmp (keyword->string, "highest_severity") == 0)
                       || (strcmp (keyword->string, "average_severity") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) ASC",
                                          column);
                }
              else if (strcmp (keyword->string, "ip") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_inet (%s) 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,
                                                  where_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 (vector_find_filter (filter_columns, keyword->string) == 0)
            {
              point++;
              continue;
            }

          if (first_order)
            {
              if ((strcmp (type, "task") == 0)
                  && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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
                       || strcmp (keyword->string, "fp_per_host") == 0
                       || strcmp (keyword->string, "log_per_host") == 0
                       || strcmp (keyword->string, "low_per_host") == 0
                       || strcmp (keyword->string, "medium_per_host") == 0
                       || strcmp (keyword->string, "high_per_host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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,
                                                  where_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)
                       || (strcmp (keyword->string, "hosts") == 0)
                       || (strcmp (keyword->string, "result_hosts") == 0)
                       || (strcmp (keyword->string, "latest_severity") == 0)
                       || (strcmp (keyword->string, "highest_severity") == 0)
                       || (strcmp (keyword->string, "average_severity") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_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,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) DESC",
                                          column);
                }
              else if (strcmp (keyword->string, "ip") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_inet (%s) DESC",
                                          column);
                }
              else if ((strcmp (type, "note")
                        && strcmp (type, "override"))
                       || (strcmp (keyword->string, "nvt")
                           && strcmp (keyword->string, "name")))
                {
                  gchar *column;
                  g_debug ("   %s: select_columns: %p", __FUNCTION__, select_columns);
                  g_debug ("   %s: where_columns: %p", __FUNCTION__, where_columns);
                  g_debug ("   %s: keyword->string: %p", __FUNCTION__, keyword->string);
                  column = columns_select_column (select_columns,
                                                  where_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 %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_ilike_op (),
                                        tag_name,
                                        sql_ilike_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_ilike_op (),
                                        tag_name,
                                        type,
                                        type,
                                        sql_ilike_op (),
                                        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;
              keyword_type_t column_type;
              quoted_keyword = sql_quote (keyword->string);
              column = columns_select_column_with_type (select_columns,
                                                        where_columns,
                                                        keyword->column,
                                                        &column_type);
              assert (column);
              if (keyword->type == KEYWORD_TYPE_INTEGER
                  && (column_type == KEYWORD_TYPE_INTEGER
                      || column_type == KEYWORD_TYPE_DOUBLE))
                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
                   && (column_type == KEYWORD_TYPE_DOUBLE
                       || column_type == KEYWORD_TYPE_INTEGER))
                g_string_append_printf (clause,
                                        "%s(CAST (%s AS REAL)"
                                        " = CAST (%f AS REAL)",
                                        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,
                                          where_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_ilike_op (),
                                  quoted_keyword);
        }
      else if (keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE)
        {
          gchar *column;
          keyword_type_t column_type;

          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_with_type (select_columns,
                                                    where_columns,
                                                    keyword->column,
                                                    &column_type);
          assert (column);
          if (keyword->type == KEYWORD_TYPE_INTEGER
              && (column_type == KEYWORD_TYPE_INTEGER
                  || column_type == KEYWORD_TYPE_DOUBLE))
            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
                   && (column_type == KEYWORD_TYPE_DOUBLE
                       || column_type == KEYWORD_TYPE_INTEGER))
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS REAL)"
                                    " > CAST (%f AS REAL)",
                                    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;
          keyword_type_t column_type;

          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_with_type (select_columns,
                                                    where_columns,
                                                    keyword->column,
                                                    &column_type);
          assert (column);
          if (keyword->type == KEYWORD_TYPE_INTEGER
              && (column_type == KEYWORD_TYPE_INTEGER
                  || column_type == KEYWORD_TYPE_DOUBLE))
            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
                   && (column_type == KEYWORD_TYPE_DOUBLE
                       || column_type == KEYWORD_TYPE_INTEGER))
            g_string_append_printf (clause,
                                    "%s(CAST (%s AS REAL)"
                                    " < CAST (%f AS REAL)",
                                    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,
                                          where_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;
                keyword_type_t column_type;

                select_column = columns_select_column_with_type (select_columns,
                                                                 where_columns,
                                                                 filter_column,
                                                                 &column_type);
                assert (select_column);

                if (keyword->type == KEYWORD_TYPE_INTEGER
                    && (column_type == KEYWORD_TYPE_INTEGER
                        || column_type == KEYWORD_TYPE_DOUBLE))
                  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
                         && (column_type == KEYWORD_TYPE_DOUBLE
                             || column_type == KEYWORD_TYPE_INTEGER))
                  g_string_append_printf (clause,
                                          "%s"
                                          "(%s IS NULL"
                                          " OR CAST (%s AS REAL)"
                                          "    != CAST (%f AS REAL))",
                                          (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;
                keyword_type_t column_type;

                select_column = columns_select_column_with_type (select_columns,
                                                                 where_columns,
                                                                 filter_column,
                                                                 &column_type);
                assert (select_column);

                if (keyword->type == KEYWORD_TYPE_INTEGER
                    && (column_type == KEYWORD_TYPE_INTEGER
                        || column_type == KEYWORD_TYPE_DOUBLE))
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS NUMERIC)"
                                          " = %i",
                                          (index ? " OR " : ""),
                                          select_column,
                                          keyword->integer_value);
                else if (keyword->type == KEYWORD_TYPE_DOUBLE
                         && (column_type == KEYWORD_TYPE_DOUBLE
                             || column_type == KEYWORD_TYPE_INTEGER))
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS REAL)"
                                          " = CAST (%f AS REAL)",
                                          (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;
                keyword_type_t column_type;
                int column_type_matches = 0;

                select_column = columns_select_column_with_type (select_columns,
                                                                 where_columns,
                                                                 filter_column,
                                                                 &column_type);

                if (column_type != KEYWORD_TYPE_INTEGER
                    && column_type != KEYWORD_TYPE_DOUBLE)
                  column_type_matches = 1;

                if (keyword_applies_to_column (keyword, filter_column)
                    && select_column && column_type_matches)
                  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 ()
                                           : sql_ilike_op (),
                                          last_was_re ? "" : "%%",
                                          quoted_keyword,
                                          last_was_re ? "" : "%%");
                else
                  g_string_append_printf (clause,
                                          "%s t ()",
                                          (index ? " AND " : ""));
              }
          else
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;
                keyword_type_t column_type;
                int column_type_matches = 0;

                select_column = columns_select_column_with_type (select_columns,
                                                                 where_columns,
                                                                 filter_column,
                                                                 &column_type);
                if (column_type != KEYWORD_TYPE_INTEGER
                    && column_type != KEYWORD_TYPE_DOUBLE)
                  column_type_matches = 1;

                if (keyword_applies_to_column (keyword, filter_column)
                    && select_column && column_type_matches)
                  g_string_append_printf (clause,
                                          "%sCAST (%s AS TEXT)"
                                          " %s '%s%s%s'",
                                          (index ? " OR " : ""),
                                          select_column,
                                          last_was_re
                                           ? sql_regexp_op ()
                                           : sql_ilike_op (),
                                          last_was_re ? "" : "%%",
                                          quoted_keyword,
                                          last_was_re ? "" : "%%");
                else
                  g_string_append_printf (clause,
                                          "%snot t ()",
                                          (index ? " OR " : ""));
              }
        }

      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);
  else
    g_string_free (order, TRUE);

  if (max_return)
    {
      if (*max_return == -2)
        setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max_return);
      else if (*max_return < 1)
        *max_return = -1;

      *max_return = manage_max_rows (*max_return);
    }

  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, KEYWORD_TYPE_INTEGER },                              \
  { prefix "uuid", NULL, KEYWORD_TYPE_STRING },                             \
  { prefix "name", NULL, KEYWORD_TYPE_STRING },                             \
  { prefix "comment", NULL, KEYWORD_TYPE_STRING },                          \
  { " iso_time (" prefix "creation_time)", NULL, KEYWORD_TYPE_STRING },     \
  { " iso_time (" prefix "modification_time)", NULL, KEYWORD_TYPE_STRING }, \
  { prefix "creation_time", "created", KEYWORD_TYPE_INTEGER },              \
  { prefix "modification_time", "modified", KEYWORD_TYPE_INTEGER }

/**
 * @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",                                                                   \
    KEYWORD_TYPE_STRING                                                         \
  },                                                                            \
  { "owner", NULL, KEYWORD_TYPE_INTEGER }

/**
 * @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, "asset") == 0)
         || (strcasecmp (type, "config") == 0)
         || (strcasecmp (type, "credential") == 0)
         || (strcasecmp (type, "filter") == 0)
         || (strcasecmp (type, "group") == 0)
         || (strcasecmp (type, "host") == 0)
         || (strcasecmp (type, "info") == 0)
         || (strcasecmp (type, "note") == 0)
         || (strcasecmp (type, "os") == 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, "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, "asset") == 0)
    return "Asset";
  if (strcasecmp (type, "config") == 0)
    return "Config";
  if (strcasecmp (type, "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, "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, "Asset") == 0)
    return "asset";
  if (strcasecmp (type, "Config") == 0)
    return "config";
  if (strcasecmp (type, "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, "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 (acl_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;
  if ((type == NULL) || (valid_type (type) == 0))
    return TRUE;
  quoted_uuid = sql_quote (uuid);
  if (acl_user_has_access_uuid (type, quoted_uuid, permission, trash) == 0)
    {
      g_free (quoted_uuid);
      *resource = 0;
      return FALSE;
    }
  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 (acl_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 (acl_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)
          && (acl_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      Return for freshly allocated name.
 *
 * @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);
  else
    *name = NULL;

  return 0;
}

/**
 * @brief Get the name of a resource.
 *
 * @param[in]  type      Type.
 * @param[in]  uuid      UUID.
 * @param[out] name      Return for freshly allocated name.
 *
 * @return 0 success, 1 error in type.
 */
int
manage_resource_name (const char *type, const char *uuid, char **name)
{
  return resource_name (type, uuid, LOCATION_TABLE, name);
}

/**
 * @brief Get the name of a trashcan resource.
 *
 * @param[in]  type      Type.
 * @param[in]  uuid      UUID.
 * @param[out] name      Return for freshly allocated name.
 *
 * @return 0 success, 1 error in type.
 */
int
manage_trash_resource_name (const char *type, const char *uuid, char **name)
{
  return resource_name (type, uuid, LOCATION_TRASH, name);
}

/**
 * @brief Get the UUID of a resource.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource.
 *
 * @return Freshly allocated UUID on success, else NULL.
 */
gchar *
resource_uuid (const gchar *type, resource_t resource)
{
  assert (valid_db_resource_type (type));

  return sql_string ("SELECT uuid FROM %ss WHERE id = %llu;",
                     type,
                     resource);
}

/**
 * @brief Initialise a GET iterator, including observed resources.
 *
 * This version includes the pre_sql arg.
 *
 * @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]  where_columns          WHERE columns.  These are columns that
 *                                    can be used for filtering and searching,
 *                                    but are not accessed (so column has no
 *                                    iterator access function).
 * @param[in]  trash_where_columns    WHERE columns for trashcan.
 * @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.
 * @param[in]  ignore_id       Whether to ignore id (e.g. for report results).
 * @param[in]  extra_order     Extra ORDER clauses.
 * @param[in]  pre_sql         SQL to add before the SELECT.  Useful for WITH.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1
 *         error.
 */
static int
init_get_iterator2_pre (iterator_t* iterator, const char *type,
                        const get_data_t *get, column_t *select_columns,
                        column_t *trash_select_columns,
                        column_t *where_columns,
                        column_t *trash_where_columns,
                        const char **filter_columns, int distinct,
                        const char *extra_tables,
                        const char *extra_where, int owned,
                        int ignore_id,
                        const char *extra_order,
                        const char *pre_sql,
                        int assume_permitted)
{
  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 (ignore_id)
    {
      resource = 0;
    }
  else 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). */
      const char* permission;
      /* Special case: "get_assets" subtypes */
      if (strcasecmp (type, "host") == 0
          || strcasecmp (type, "os") == 0)
        permission = "get_assets";
      else
        permission = NULL;

      if (find_resource_with_permission (type, get->id, &resource, permission,
                                         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 && trash_where_columns)
                           ? trash_where_columns
                           : where_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 if (assume_permitted)
    owned_clause = g_strdup (" t ()");
  else
    owned_clause = acl_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 (get->ignore_pagination
      && ((strcmp (type, "host") == 0)
          || (strcmp (type, "os") == 0)
          || (strcmp (type, "task") == 0)
          || (strcmp (type, "report") == 0)
          || (strcmp (type, "result") == 0)))
    {
      first = 0;
      max = -1;
    }

  if (strlen (order) == 0)
    {
      g_free (order);
      order = NULL;
    }

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

/**
 * @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]  where_columns          WHERE columns.  These are columns that
 *                                    can be used for filtering and searching,
 *                                    but are not accessed (so column has no
 *                                    iterator access function).
 * @param[in]  trash_where_columns    WHERE columns for trashcan.
 * @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.
 * @param[in]  ignore_id       Whether to ignore id (e.g. for report results).
 * @param[in]  extra_order     Extra ORDER clauses.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1
 *         error.
 */
static int
init_get_iterator2 (iterator_t* iterator, const char *type,
                    const get_data_t *get, column_t *select_columns,
                    column_t *trash_select_columns,
                    column_t *where_columns,
                    column_t *trash_where_columns,
                    const char **filter_columns, int distinct,
                    const char *extra_tables,
                    const char *extra_where, int owned,
                    int ignore_id,
                    const char *extra_order)
{
  return init_get_iterator2_pre (iterator, type, get, select_columns,
                                 trash_select_columns, where_columns,
                                 trash_where_columns, filter_columns, distinct,
                                 extra_tables, extra_where, owned, ignore_id,
                                 extra_order, NULL, 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)
{
  return init_get_iterator2 (iterator, type, get, select_columns,
                             trash_select_columns, NULL, NULL, filter_columns,
                             distinct, extra_tables, extra_where, owned, FALSE,
                             NULL);
}

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

column_t *
type_where_columns (const char *type, int);

/**
 * @brief Append expression for a column to an array.
 *
 * @param[in]  columns         Array.
 * @param[in]  column_name     Name of column.
 * @param[in]  select_columns  Definition of "SELECT" columns.
 * @param[in]  where_columns   Definition of "WHERE" columns.
 */
static void
append_column (GArray *columns, const gchar *column_name,
               column_t *select_columns, column_t *where_columns)
{
  int i = 0;
  while (select_columns[i].select != NULL)
    {
      gchar *select = NULL;
      if (strcmp (select_columns[i].select, column_name) == 0
          || (select_columns[i].filter
              && strcmp (select_columns[i].filter, column_name) == 0))
        {
          select = g_strdup (select_columns[i].select);
          g_array_append_val (columns, select);
          break;
        }
      i++;
    }
  if (select_columns[i].select == NULL && where_columns)
    {
      i = 0;
      while (where_columns[i].select != NULL)
        {
          gchar *select = NULL;
          if (strcmp (where_columns[i].select, column_name) == 0
              || (where_columns[i].filter
                  && strcmp (where_columns[i].filter, column_name) == 0))
            {
              select = g_strdup (where_columns[i].select);
              g_array_append_val (columns, select);
              break;
            }
          i++;
        }
    }
}

/**
 * @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]  distinct        Whether the query should be distinct.  Skipped
 *                             for trash and single resource.
 * @param[in]  data_columns    Columns to calculate statistics for.
 * @param[in]  group_column    Column to group data by.
 * @param[in]  subgroup_column Column to further group data by.
 * @param[in]  text_columns    Columns to get text from.
 * @param[in]  sort_data       GArray of sorting data.
 * @param[in]  first_group     Row number to start iterating from.
 * @param[in]  max_groups      Maximum number of rows.
 * @param[in]  extra_tables    Join tables.  Skipped for trash and single
 *                             resource.
 * @param[in]  extra_where     Extra WHERE clauses.  Skipped for single
 *                             resource.
 *
 * @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, 7 invalid text column, 8 invalid
 *         subgroup_column, -1 error.
 */
int
init_aggregate_iterator (iterator_t* iterator, const char *type,
                         const get_data_t *get, int distinct,
                         GArray *data_columns,
                         const char *group_column, const char *subgroup_column,
                         GArray *text_columns, GArray *sort_data,
                         int first_group, int max_groups,
                         const char *extra_tables, const char *extra_where)
{
  int owned;
  gchar *autofp_str, *apply_overrides_str, *min_qod_str;
  int autofp, apply_overrides, min_qod;
  column_t *select_columns, *where_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;
  GString *aggregate_select, *outer_col_select;
  gchar *aggregate_group_by;
  gchar *outer_group_by_column, *outer_subgroup_column;
  gchar *select_group_column, *select_subgroup_column;
  GArray *select_data_columns, *select_text_columns;
  GString *order_clause;

  assert (get);

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

  if ((manage_scap_loaded () == FALSE
       && (strcmp (type, "cve") == 0
           || strcmp (type, "cpe") == 0
           || strcmp (type, "ovaldef") == 0
           || strcmp (type, "allinfo") == 0))
      || (manage_cert_loaded () == FALSE
          && (strcmp (type, "cert_bund_adv") == 0
              || strcmp (type, "dfn_cert_adv") == 0
              || strcmp (type, "allinfo") == 0)))
    {
      // Init a dummy iterator if SCAP or CERT DB is required but unavailable.
      init_iterator (iterator,
                     "SELECT NULL LIMIT %s",
                     sql_select_limit (0));
      return 0;
    }

  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;

  autofp_str = filter_term_value (filter ? filter : get->filter,
                                  "autofp");
  autofp = autofp_str ? atoi (autofp_str) : 0;
  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 && strcmp (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);
  where_columns = type_where_columns (type, apply_overrides);
  filter_columns = type_filter_columns (type, apply_overrides);
  opts_table = type_opts_table (type, autofp, apply_overrides, min_qod);

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

  owned = type_owned (type);

  if (data_columns && data_columns->len > 0)
    {
      int i;
      for (i = 0; i < data_columns->len; i++)
        {
          if (vector_find_filter (filter_columns,
                                  g_array_index (data_columns, gchar*, i))
              == 0)
            {
              g_free (columns);
              g_free (trash_columns);
              g_free (opts_table);
              return 3;
            }
        }
    }
  if (text_columns && text_columns->len > 0)
    {
      int i;
      for (i = 0; i < text_columns->len; i++)
        {
          if (vector_find_filter (filter_columns,
                                  g_array_index (text_columns, gchar*, i))
              == 0)
            {
              g_free (columns);
              g_free (trash_columns);
              g_free (opts_table);
              return 7;
            }
        }
    }
  if (group_column && vector_find_filter (filter_columns, group_column) == 0)
    {
      g_free (columns);
      g_free (trash_columns);
      g_free (opts_table);
      return 4;
    }
  if (subgroup_column
      && vector_find_filter (filter_columns, subgroup_column) == 0)
    {
      g_free (columns);
      g_free (trash_columns);
      g_free (opts_table);
      return 8;
    }
  select_data_columns = g_array_new (TRUE, TRUE, sizeof (gchar*));
  select_text_columns = g_array_new (TRUE, TRUE, sizeof (gchar*));

  clause = filter_clause (type, filter ? filter : get->filter, filter_columns,
                          select_columns, where_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 = acl_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 if (strcasecmp (type, "REPORT") == 0)
    {
      if (get->trash)
        trash_extra = g_strdup (" AND (SELECT hidden FROM tasks"
                                "      WHERE tasks.id = task)"
                                "     = 2");
      else
        trash_extra = g_strdup (" AND (SELECT hidden FROM tasks"
                                "      WHERE tasks.id = task)"
                                "     = 0");
    }
  else if (strcasecmp (type, "RESULT") == 0)
    {
      trash_extra = results_extra_where (get, 0, NULL, autofp, apply_overrides,
                                         setting_dynamic_severity_int (),
                                         filter ? filter : get->filter);
    }
  else
    trash_extra = g_strdup ("");

  g_free (owner_filter);
  array_free (permissions);

  select_group_column = NULL;
  select_subgroup_column = NULL;

  if (group_column)
    {
      select_group_column
        = g_strdup (columns_select_column (select_columns,
                                           where_columns,
                                           group_column));
      if (subgroup_column)
        {
          select_subgroup_column
            = g_strdup (columns_select_column (select_columns,
                                               where_columns,
                                               subgroup_column));
        }
    }

  if (data_columns && data_columns->len > 0)
    {
      int column_index;
      for (column_index = 0; column_index < data_columns->len; column_index++)
        append_column (select_data_columns,
                       g_array_index (data_columns, gchar*, column_index),
                       select_columns,
                       where_columns);
    }

  if (text_columns && text_columns->len > 0)
    {
      int column_index;
      for (column_index = 0; column_index < text_columns->len; column_index++)
        append_column (select_text_columns,
                       g_array_index (text_columns, gchar*, column_index),
                       select_columns,
                       where_columns);
    }

  /* 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 (column_is_timestamp (group_column))
    if (sql_is_sqlite3 ())
      outer_group_by_column
        = g_strdup_printf ("CAST (strftime ('%%s',"
                           "                date(%s, 'unixepoch',"
                           "                     'localtime'),"
                           "                'utc')"
                           "      AS INTEGER)",
                           "aggregate_group_value");
    else
      outer_group_by_column
        = g_strdup_printf ("EXTRACT (EPOCH FROM"
                           "           date_trunc ('day',"
                           "           TIMESTAMP WITH TIME ZONE 'epoch'"
                           "           + (%s) * INTERVAL '1 second'))"
                           "  :: integer",
                           "aggregate_group_value");
  else
    outer_group_by_column = g_strdup ("aggregate_group_value");

  if (column_is_timestamp (subgroup_column))
    if (sql_is_sqlite3 ())
      outer_subgroup_column
        = g_strdup_printf ("CAST (strftime ('%%s',"
                           "                date(%s, 'unixepoch',"
                           "                     'localtime'),"
                           "                'utc')"
                           "      AS INTEGER)",
                           "aggregate_subgroup_value");
    else
      outer_subgroup_column
        = g_strdup_printf ("EXTRACT (EPOCH FROM"
                           "           date_trunc ('day',"
                           "           TIMESTAMP WITH TIME ZONE 'epoch'"
                           "           + (%s) * INTERVAL '1 second'))"
                           "  :: integer",
                           "aggregate_subgroup_value");
  else
    outer_subgroup_column = g_strdup ("aggregate_subgroup_value");

  if (sort_data)
    {
      order_clause = g_string_new ("ORDER BY");

      int sort_index;
      for (sort_index = 0; sort_index < sort_data->len; sort_index++) {
        sort_data_t *sort_data_item;
        const gchar *sort_field, *sort_stat;
        int sort_order;
        gchar *order_column;

        sort_data_item = g_array_index (sort_data, sort_data_t*, sort_index);
        sort_field = sort_data_item->field;
        sort_stat = sort_data_item->stat;
        sort_order = sort_data_item->order;

        if (sort_stat && strcmp (sort_stat, "count") == 0)
          order_column = g_strdup ("outer_count");
        else if (sort_stat && strcmp (sort_stat, "value") == 0)
          order_column = g_strdup ("outer_group_column");
        else if (sort_field
                 && group_column
                 && strcmp (sort_field, "")
                 && strcmp (sort_field, group_column)
                 && (subgroup_column == NULL
                     || strcmp (sort_field, subgroup_column)))
          {
            int index;
            order_column = NULL;
            for (index = 0;
                 index < data_columns->len && order_column == NULL;
                 index++)
              {
                gchar *column = g_array_index (data_columns, gchar*, index);
                if (strcmp (column, sort_field) == 0)
                  {
                    if (sort_stat == NULL || strcmp (sort_stat, "") == 0
                        || (   strcmp (sort_stat, "min")
                            && strcmp (sort_stat, "max")
                            && strcmp (sort_stat, "mean")
                            && strcmp (sort_stat, "sum")))
                      order_column = g_strdup_printf ("max (aggregate_max_%d)",
                                                      index);
                    else if (strcmp (sort_stat, "mean") == 0)
                      order_column = g_strdup_printf ("sum (aggregate_avg_%d)",
                                                      index);
                    else
                      order_column = g_strdup_printf ("%s (aggregate_%s_%d)",
                                                      sort_stat, sort_stat,
                                                      index);
                  }
              }

            for (index = 0;
                index < text_columns->len && order_column == NULL;
                index++)
              {
                gchar *column = g_array_index (text_columns, gchar*, index);
                if (strcmp (column, sort_field) == 0)
                  {
                    order_column = g_strdup_printf ("max (text_column_%d)",
                                                    index);
                  }
              }
          }
        else if (sort_field && subgroup_column
                && strcmp (sort_field, subgroup_column) == 0)
          order_column = g_strdup ("outer_subgroup_column");
        else
          order_column = g_strdup ("outer_group_column");

        if (subgroup_column && sort_index == 0)
          {
            xml_string_append (order_clause,
                               " outer_group_column %s, %s %s",
                               sort_order ? "ASC" : "DESC",
                               order_column,
                               sort_order ? "ASC" : "DESC");
          }
        else
          {
            xml_string_append (order_clause,
                               "%s %s %s",
                               sort_index > 0 ? "," : "",
                               order_column,
                               sort_order ? "ASC" : "DESC");
          }
        g_free (order_column);
      }

      if (sort_data->len == 0)
        g_string_append (order_clause, " outer_group_column ASC");
    }
  else
    order_clause = g_string_new ("");

  aggregate_select = g_string_new ("");
  outer_col_select = g_string_new ("");
  if (group_column && strcmp (group_column, ""))
    {
      if (subgroup_column && strcmp (subgroup_column, ""))
        {
          g_string_append_printf (aggregate_select,
                             " count(*) AS aggregate_count,"
                             " %s AS aggregate_group_value,"
                             " %s AS aggregate_subgroup_value",
                             select_group_column,
                             select_subgroup_column);

          aggregate_group_by = g_strdup_printf (" GROUP BY %s, %s",
                                                select_group_column,
                                                select_subgroup_column);
        }
      else
        {
          g_string_append_printf (aggregate_select,
                             " count(*) AS aggregate_count,"
                             " %s AS aggregate_group_value,"
                             " CAST (NULL AS TEXT) AS aggregate_subgroup_value",
                             select_group_column);

          aggregate_group_by = g_strdup_printf (" GROUP BY %s",
                                                select_group_column);
        }


    }
  else
    {
      g_string_append_printf (aggregate_select,
                         " count(*) AS aggregate_count,"
                         " CAST (NULL AS TEXT) AS aggregate_group_value,"
                         " CAST (NULL AS TEXT) AS aggregate_subgroup_value");

      aggregate_group_by = g_strdup ("");
    }

  int col_index;
  for (col_index = 0; col_index < select_data_columns->len; col_index ++)
    {
      gchar *select_data_column = g_array_index (select_data_columns, gchar*,
                                                 col_index);
      // TODO: Test type of column (string, number, timestamp)
      g_string_append_printf (aggregate_select,
                              ","
                              " min(CAST (%s AS real)) AS aggregate_min_%d,"
                              " max(CAST (%s AS real)) AS aggregate_max_%d,"
                              " avg(CAST (%s AS real)) * count(*)"
                              "   AS aggregate_avg_%d,"
                              " sum(CAST (%s AS real))"
                              "   AS aggregate_sum_%d",
                              select_data_column,
                              col_index,
                              select_data_column,
                              col_index,
                              select_data_column,
                              col_index,
                              select_data_column,
                              col_index);
      g_string_append_printf (outer_col_select,
                              ", min(aggregate_min_%d),"
                              " max (aggregate_max_%d),"
                              " sum (aggregate_avg_%d) / sum(aggregate_count),"
                              " sum (aggregate_sum_%d)",
                              col_index, col_index, col_index, col_index);
    }
  for (col_index = 0; col_index < select_text_columns->len; col_index ++)
    {
      gchar *select_text_column = g_array_index (select_text_columns, gchar*,
                                                 col_index);
      g_string_append_printf (aggregate_select,
                              ", max (%s) as text_column_%d",
                              select_text_column,
                              col_index);
      g_string_append_printf (outer_col_select,
                              ", max (text_column_%d)",
                              col_index);
    }

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

  init_iterator (iterator,
                 "SELECT sum(aggregate_count) AS outer_count,"
                 " %s AS outer_group_column,"
                 " %s AS outer_subgroup_column"
                 " %s"
                 " FROM (SELECT%s %s"
                 "       FROM %s%s%s"
                 "       WHERE"
                 "       %s%s"
                 "       %s%s%s%s"
                 "       %s)"
                 "      AS agg_sub"
                 " GROUP BY outer_group_column, outer_subgroup_column %s"
                 " LIMIT %s OFFSET %d;",
                 outer_group_by_column,
                 outer_subgroup_column,
                 outer_col_select->str,
                 distinct ? " DISTINCT" : "",
                 aggregate_select->str,
                 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,
                 order_clause->str,
                 sql_select_limit (max_groups),
                 first_group);

  g_free (columns);
  g_free (trash_columns);
  g_free (opts_table);
  g_free (owned_clause);
  g_free (trash_extra);
  g_free (filter_order);
  g_string_free (order_clause, TRUE);
  g_free (clause);
  g_free (aggregate_group_by);
  g_string_free (aggregate_select, TRUE);
  g_string_free (outer_col_select, TRUE);
  g_free (outer_group_by_column);
  g_free (outer_subgroup_column);
  g_free (select_group_column);
  g_free (select_subgroup_column);
  return 0;
}

#define AGGREGATE_ITERATOR_OFFSET 3
#define AGGREGATE_ITERATOR_N_STATS 4

/**
 * @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.
 * @param[in]  data_column_index  Index of the data column to get min of.
 *
 * @return The minimum value in the current group.
 */
double
aggregate_iterator_min (iterator_t* iterator, int data_column_index)
{
  return sql_column_double (iterator->stmt,
                            AGGREGATE_ITERATOR_OFFSET
                            + data_column_index * AGGREGATE_ITERATOR_N_STATS);
}

/**
 * @brief Get the maximum from an aggregate iterator.
 *
 * @param[in]  iterator           Iterator.
 * @param[in]  data_column_index  Index of the data column to get max of.
 *
 * @return The maximum value in the current group.
 */
double
aggregate_iterator_max (iterator_t* iterator, int data_column_index)
{
  return sql_column_double (iterator->stmt,
                            AGGREGATE_ITERATOR_OFFSET + 1
                            + data_column_index * AGGREGATE_ITERATOR_N_STATS);
}

/**
 * @brief Get the mean from an aggregate iterator.
 *
 * @param[in]  iterator           Iterator.
 * @param[in]  data_column_index  Index of the data column to get mean of.
 *
 * @return The mean value in the current group.
 */
double
aggregate_iterator_mean (iterator_t* iterator, int data_column_index)
{
  return sql_column_double (iterator->stmt,
                            AGGREGATE_ITERATOR_OFFSET + 2
                            + data_column_index * AGGREGATE_ITERATOR_N_STATS);
}

/**
 * @brief Get the sum from a statistics iterator.
 *
 * @param[in]  iterator           Iterator.
 * @param[in]  data_column_index  Index of the data column to get sum of.
 *
 * @return The sum of values in the current group.
 */
double
aggregate_iterator_sum (iterator_t* iterator, int data_column_index)
{
  return sql_column_double (iterator->stmt,
                            AGGREGATE_ITERATOR_OFFSET + 3
                            + data_column_index * AGGREGATE_ITERATOR_N_STATS);
}

/**
 * @brief Get the value of a text column from an aggregate iterator.
 *
 * @param[in]  iterator           Iterator.
 * @param[in]  text_column_index  Index of the text column to get.
 * @param[in]  data_columns       Number of data columns.
 *
 * @return The value, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
aggregate_iterator_text (iterator_t* iterator, int text_column_index,
                         int data_columns)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = (const char*) sql_column_text (iterator->stmt,
                                       AGGREGATE_ITERATOR_OFFSET
                                        + (data_columns
                                           * AGGREGATE_ITERATOR_N_STATS)
                                        + text_column_index);
  return ret;
}

/**
 * @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, 1);
  return ret;
}

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

/**
 * @brief Count number of a particular resource.
 *
 * @param[in]  type              Type of resource.
 * @param[in]  get               GET params.
 * @param[in]  select_columns    SELECT columns.
 * @param[in]  trash_select_columns  SELECT columns for trashcan.
 * @param[in]  where_columns     WHERE columns.
 * @param[in]  trash_where_columns   WHERE columns for trashcan.
 * @param[in]  filter_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
count2 (const char *type, const get_data_t *get, column_t *select_columns,
        column_t *trash_select_columns, column_t *where_columns,
        column_t *trash_where_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 (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 && trash_where_columns
                           ? trash_where_columns
                           : where_columns,
                          get->trash, NULL, NULL, NULL, &permissions,
                          &owner_filter);

  g_free (filter);

  owned_clause = acl_where_owned (type, get, owned, owner_filter, 0, 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 ((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"
                   "       FROM %ss%s%s"
                   "       WHERE %s"
                   "       %s%s%s%s) AS subquery;",
                   distinct ? "DISTINCT " : "",
                   type,
                   get->trash && strcmp (type, "task") ? "_trash" : "",
                   type,
                   get->trash && strcmp (type, "task") ? "_trash" : "",
                   extra_tables ? extra_tables : "",
                   owned_clause,
                   clause ? " AND (" : "",
                   clause ? clause : "",
                   clause ? ") " : "",
                   extra_where ? extra_where : "");

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

/**
 * @brief Count number of a particular resource.
 *
 * @param[in]  type              Type of resource.
 * @param[in]  get               GET params.
 * @param[in]  select_columns    SELECT columns.
 * @param[in]  trash_select_columns  SELECT columns for trashcan.
 * @param[in]  filter_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)
{
  return count2 (type, get, select_columns, trash_select_columns, NULL, NULL,
                 filter_columns, distinct, extra_tables, extra_where, owned);
}

/**
 * @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 whether a resource is predefined.
 *
 * @param[in]  type      Type of resource.
 * @param[in]  resource  Resource.
 *
 * @return 1 if predefined, else 0.
 */
static int
resource_predefined (const gchar *type, resource_t resource)
{
  assert (valid_type (type));
  return sql_int ("SELECT EXISTS (SELECT * FROM resources_predefined"
                  "               WHERE resource_type = '%s'"
                  "               AND resource = %llu);",
                  type,
                  resource);
}

/**
 * @brief Mark a resource as predefined.
 *
 * Currently only report formats use this.
 */
static void
resource_set_predefined (const gchar *type, resource_t resource, int enable)
{
  assert (valid_type (type));

  sql ("DELETE FROM resources_predefined"
       " WHERE resource_type = '%s'"
       " AND resource = %llu;",
       type,
       resource);

  if (enable)
    sql ("INSERT into resources_predefined (resource_type, resource)"
         " VALUES ('%s', %llu);",
         type,
         resource);
}



/**
 * @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 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,"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'secret'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'password'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'private_key')"
                 " FROM credentials");
  iterator.crypt_ctx = lsc_crypt_new ();

  sql_begin_immediate ();

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

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

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

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

      if (secret)
        {
          lsc_crypt_flush (iterator.crypt_ctx);
          password = lsc_crypt_get_password (iterator.crypt_ctx, secret);
          privkey  = lsc_crypt_get_private_key (iterator.crypt_ctx, secret);

          /* 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)
        {
          set_credential_data (rowid, "password", password);
          set_credential_data (rowid, "private_key", privkey);
          set_credential_data (rowid, "secret", NULL);
          sql ("UPDATE credentials SET"
               " modification_time = m_now ()"
               " WHERE id = %llu;", rowid);
          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;
            }
          set_credential_data (rowid, "password", NULL);
          set_credential_data (rowid, "private_key", NULL);
          set_credential_data (rowid, "secret", encblob);
          sql ("UPDATE credentials SET"
               " modification_time = m_now ()"
               " WHERE id = %llu;", 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 Encrypt or re-encrypt all credentials
 *
 * All plaintext credentials in the 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.
 *
 * @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)
{
  int ret;

  g_info ("   (Re-)encrypting all credentials.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  ret = encrypt_all_credentials (FALSE);
  if (ret)
    printf ("Encryption failed.\n");
  else
    printf ("Encryption succeeded.\n");

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Decrypt all credentials
 *
 * @param[in] log_config    Log configuration.
 * @param[in] database      Location of manage database.
 *
 * @return 0 success, -1 error,
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_decrypt_all_credentials (GSList *log_config, const gchar *database)
{
  int ret;

  g_info ("   Decrypting all credentials.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  ret = encrypt_all_credentials (TRUE);
  if (ret)
    printf ("Decryption failed.\n");
  else
    printf ("Decryption succeeded.\n");

  manage_option_cleanup ();

  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 DISTINCT 1, 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 Check if any SecInfo alerts are due.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 *
 * @return 0 success, -1 error,
 *         -2 database is wrong version, -3 database needs to be initialised
 *         from server.
 */
int
manage_check_alerts (GSList *log_config, const gchar *database)
{
  int ret;

  g_info ("   Checking alerts.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

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

  if (manage_scap_loaded ())
    {
      int max_time;

      max_time
       = sql_int ("SELECT %s"
                  "        ((SELECT max (modification_time) FROM scap.cves),"
                  "         (SELECT max (modification_time) FROM scap.cpes),"
                  "         (SELECT max (modification_time) FROM scap.ovaldefs),"
                  "         (SELECT max (creation_time) FROM scap.cves),"
                  "         (SELECT max (creation_time) FROM scap.cpes),"
                  "         (SELECT max (creation_time) FROM scap.ovaldefs));",
                  sql_greatest ());

      if (sql_int ("SELECT NOT EXISTS (SELECT * FROM meta"
                   "                   WHERE name = 'scap_check_time')"))
        sql ("INSERT INTO meta (name, value)"
             " VALUES ('scap_check_time', %i);",
             max_time);
      else if (sql_int ("SELECT value = '0' FROM meta"
                        " WHERE name = 'scap_check_time';"))
        sql ("UPDATE meta SET value = %i"
             " WHERE name = 'scap_check_time';",
             max_time);
      else
        {
          check_for_new_scap ();
          check_for_updated_scap ();
          sql ("UPDATE meta SET value = %i"
               " WHERE name = 'scap_check_time';",
               max_time);
        }
    }

  if (manage_cert_loaded ())
    {
      int max_time;

      max_time
       = sql_int ("SELECT"
                  " %s"
                  "  ((SELECT max (modification_time) FROM cert.cert_bund_advs),"
                  "   (SELECT max (modification_time) FROM cert.dfn_cert_advs),"
                  "   (SELECT max (creation_time) FROM cert.cert_bund_advs),"
                  "   (SELECT max (creation_time) FROM cert.dfn_cert_advs));",
                  sql_greatest ());

      if (sql_int ("SELECT NOT EXISTS (SELECT * FROM meta"
                   "                   WHERE name = 'cert_check_time')"))
        sql ("INSERT INTO meta (name, value)"
             " VALUES ('cert_check_time', %i);",
             max_time);
      else if (sql_int ("SELECT value = '0' FROM meta"
                        " WHERE name = 'cert_check_time';"))
        sql ("UPDATE meta SET value = %i"
             " WHERE name = 'cert_check_time';",
             max_time);
      else
        {
          check_for_new_cert ();
          check_for_updated_cert ();
          sql ("UPDATE meta SET value = %i"
               " WHERE name = 'cert_check_time';",
               max_time);
        }
    }

  current_credentials.uuid = NULL;

  manage_option_cleanup ();

  return ret;
}

/**
 * @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,
 *         3 failed to find filter for condition, -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;
    }
  else if (condition == ALERT_CONDITION_FILTER_COUNT_AT_LEAST)
    {
      if (strcmp (name, "filter_id") == 0)
        {
          filter_t filter;
          if (data == NULL)
            return 3;
          filter = 0;
          if (find_filter_with_permission (data, &filter, "get_filters"))
            return -1;
          if (filter == 0)
            return 3;
          return 0;
        }

      if (strcmp (name, "count"))
        return 1;
    }
  else if (condition == ALERT_CONDITION_FILTER_COUNT_CHANGED)
    {
      if (strcmp (name, "filter_id") == 0)
        {
          filter_t filter;
          if (data == NULL)
            return 3;
          filter = 0;
          if (find_filter_with_permission (data, &filter, "get_filters"))
            return -1;
          if (filter == 0)
            return 3;
          return 0;
        }

      if (strcmp (name, "direction")
          && strcmp (name, "count"))
        return 1;

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


  return 0;
}

/**
 * @brief Validate event data for an alert.
 *
 * @param[in]  name   Name.
 * @param[in]  data   Data to validate.
 * @param[in]  event  The event.
 *
 * @return 0 on success, 1 unexpected data name, 2 syntax error in data.
 */
static int
validate_alert_event_data (gchar *name, gchar* data, event_t event)
{
  if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
    {
      if (strcmp (name, "secinfo_type"))
        return 1;

      if (data == NULL)
        return 2;

      if (strcasecmp (data, "nvt")
          && strcasecmp (data, "cve")
          && strcasecmp (data, "cpe")
          && strcasecmp (data, "cert_bund_adv")
          && strcasecmp (data, "dfn_cert_adv")
          && strcasecmp (data, "ovaldef"))
        return 2;
    }
  return 0;
}

/**
 * @brief Validate method data for the SCP method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 15 error in SCP host, 17 failed to find report format for
 *         SCP method, 18 error in SCP credential, 19 error in SCP path,
 *         -1 error.
 */
int
validate_scp_data (alert_method_t method, const gchar *name, gchar **data)
{
  if (method == ALERT_METHOD_SCP
      && strcmp (name, "scp_credential") == 0)
    {
      credential_t credential;
      if (find_credential_with_permission (*data, &credential,
                                           "get_credentials"))
        return -1;
      else if (credential == 0)
        return 18;
      else
        {
          gchar *username;
          username = credential_value (credential, "username");

          if (username == NULL || strlen (username) == 0)
            {
              g_free (username);
              return 18;
            }

          if (strchr (username, '@') || strchr (username, ':'))
            {
              g_free (username);
              return 18;
            }

          g_free (username);
        }
    }

  if (method == ALERT_METHOD_SCP
      && strcmp (name, "scp_path") == 0)
    {
      if (strlen (*data) == 0)
        return 19;
    }

  if (method == ALERT_METHOD_SCP
      && strcmp (name, "scp_host") == 0)
    {
      int type;
      gchar *stripped;

      stripped = g_strstrip (g_strdup (*data));
      type = openvas_get_host_type (stripped);
      g_free (stripped);
      if ((type != HOST_TYPE_IPV4)
          && (type != HOST_TYPE_IPV6)
          && (type != HOST_TYPE_NAME))
        return 15;
    }

  if (method == ALERT_METHOD_SCP
      && strcmp (name, "scp_report_format") == 0)
    {
      report_format_t report_format;

      report_format = 0;
      if (find_report_format_with_permission (*data,
                                              &report_format,
                                              "get_report_formats"))
        return -1;
      if (report_format == 0)
        return 17;
    }

  return 0;
}

/**
 * @brief Validate method data for the Send method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 12 error in Send host, 13 error in Send port, 14 failed
 *         to find report format for Send method, -1 error.
 */
int
validate_send_data (alert_method_t method, const gchar *name, gchar **data)
{
  if (method == ALERT_METHOD_SEND
      && strcmp (name, "send_host") == 0)
    {
      int type;
      gchar *stripped;

      stripped = g_strstrip (g_strdup (*data));
      type = openvas_get_host_type (stripped);
      g_free (stripped);
      if ((type != HOST_TYPE_IPV4)
          && (type != HOST_TYPE_IPV6)
          && (type != HOST_TYPE_NAME))
        return 12;
    }

  if (method == ALERT_METHOD_SEND
      && strcmp (name, "send_port") == 0)
    {
      int port;
      gchar *stripped, *end;

      stripped = g_strstrip (g_strdup (*data));
      port = strtol (stripped, &end, 10);
      if (*end != '\0')
        {
          g_free (stripped);
          return 13;
        }

      g_free (stripped);
      g_free (*data);
      *data = g_strdup_printf ("%i", port);
    }

  if (method == ALERT_METHOD_SEND
      && strcmp (name, "send_report_format") == 0)
    {
      report_format_t report_format;

      report_format = 0;
      if (find_report_format_with_permission (*data,
                                              &report_format,
                                              "get_report_formats"))
        return -1;
      if (report_format == 0)
        return 14;
    }

  return 0;
}

/**
 * @brief Validate method data for the Send method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 40 invalid credential, 41 invalid SMB share path,
 *         42 invalid SMB file path, -1 error.
 */
int
validate_smb_data (alert_method_t method, const gchar *name, gchar **data)
{
  if (method == ALERT_METHOD_SMB)
    {
      if (strcmp (name, "smb_credential") == 0)
        {
          credential_t credential;
          if (find_credential_with_permission (*data, &credential,
                                              "get_credentials"))
            return -1;
          else if (credential == 0)
            return 40;
          else
            {
              gchar *username;
              username = credential_value (credential, "username");

              if (username == NULL || strlen (username) == 0)
                {
                  g_free (username);
                  return 40;
                }

              if (strchr (username, '@') || strchr (username, ':'))
                {
                  g_free (username);
                  return 40;
                }

              g_free (username);
            }
        }

      if (strcmp (name, "smb_share_path") == 0)
        {
          if (g_regex_match_simple ("^(?>\\\\\\\\|\\/\\/)[^:?<>|]+"
                                    "(?>\\\\|\\/)[^:?<>|]+$", *data, 0, 0)
              == FALSE)
            {
              return 41;
            }
        }

      if (strcmp (name, "smb_file_path") == 0)
        {
          if (g_regex_match_simple ("^[^:?<>|]+$", *data, 0, 0)
              == FALSE)
            {
              return 42;
            }
        }
    }

  return 0;
}

/**
 * @brief Validate method data for the TippingPoint method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 50 invalid credential, 51 invalid hostname,
 *  52 invalid certificate, 53 invalid TLS workaround setting.
 */
int
validate_tippingpoint_data (alert_method_t method, const gchar *name,
                             gchar **data)
{
  if (method == ALERT_METHOD_TIPPINGPOINT)
    {
      if (strcmp (name, "tp_sms_credential") == 0)
        {
          credential_t credential;
          if (find_credential_with_permission (*data, &credential,
                                               "get_credentials"))
            return -1;
          else if (credential == 0)
            return 50;
          else
            {
              if (strcmp (credential_type (credential), "up"))
                return 50;

            }
        }

      if (strcmp (name, "tp_sms_hostname") == 0)
        {
          if (g_regex_match_simple ("^[0-9A-Za-z][0-9A-Za-z.-]*$",
                                    *data, 0, 0)
              == FALSE)
            {
              return 51;
            }
        }

      if (strcmp (name, "tp_sms_tls_certificate") == 0)
        {
          // TODO: Check certificate, return 52 on failure
        }

      if (strcmp (name, "tp_sms_tls_workaround") == 0)
        {
          if (g_regex_match_simple ("^0|1$", *data, 0, 0)
              == FALSE)
            {
              return 53;
            }
        }
    }

  return 0;
}

/**
 * @brief Check alert params.
 *
 * @param[in]  event           Type of event.
 * @param[in]  condition       Event condition.
 * @param[in]  method          Escalation method.
 *
 * @return 0 success, 20 method does not match event, 21 condition does not
 *         match event.
 */
static int
check_alert_params (event_t event, alert_condition_t condition,
                    alert_method_t method)
{
  if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
    {
      if (method == ALERT_METHOD_HTTP_GET
          || method == ALERT_METHOD_SOURCEFIRE
          || method == ALERT_METHOD_START_TASK
          || method == ALERT_METHOD_VERINICE)
        return 20;

      if (condition == ALERT_CONDITION_SEVERITY_AT_LEAST
          || condition == ALERT_CONDITION_SEVERITY_CHANGED
          || condition == ALERT_CONDITION_FILTER_COUNT_CHANGED)
        return 21;
    }
  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, 9 failed to find
 *         filter for condition, 12 error in Send host, 13 error in Send port,
 *         14 failed to find report format for Send method, 15 error in
 *         SCP host, 17 failed to find report format for SCP method, 18 error
 *         in SCP credential, 19 error in SCP path, 20 method does not match
 *         event, 21 condition does not match event, 31 unexpected event data
 *         name, 32 syntax error in event data, 40 invalid SMB credential
 *       , 41 invalid SMB share path, 42 invalid SMB file path,
 *         50 invalid TippingPoint credential, 51 invalid TippingPoint hostname,
 *         52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS
 *         workaround setting.
 *         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, ret;
  gchar *item, *quoted_comment;
  gchar *quoted_name;
  filter_t filter;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (acl_user_may ("create_alert") == 0)
    {
      sql_rollback ();
      return 99;
    }

  ret = check_alert_params (event, condition, method);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  filter = 0;
  if (event != EVENT_NEW_SECINFO && event != EVENT_UPDATED_SECINFO && 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;
              case 3:
                return 9;
              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++)))
    {
      int validation_result;
      gchar *name = sql_quote (item);
      gchar *data = sql_quote (item + strlen (item) + 1);

      validation_result = validate_alert_event_data (name, data, event);

      if (validation_result)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();

          switch (validation_result)
            {
              case 1:
                return 31;
              case 2:
                return 32;
              default:
                return -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++)))
    {
      int ret;
      gchar *name, *data;

      name = sql_quote (item);
      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) > max_email_message_length)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();
          return 8;
        }

      ret = validate_scp_data (method, name, &data);
      if (ret)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

      ret = validate_send_data (method, name, &data);
      if (ret)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

      ret = validate_smb_data (method, name, &data);
      if (ret)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

      ret = validate_tippingpoint_data (method, name, &data);
      if (ret)
        {
          g_free (name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

      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, 11 failed to
 *         find filter for condition, 12 error in Send host, 13 error in Send
 *         port, 14 failed to find report format for Send method, 15 error in
 *         SCP host, 17 failed to find report format for SCP method, 18 error
 *         in SCP credential, 19 error in SCP path, 20 method does not match
 *         event, 21 condition does not match event, 31 unexpected event data
 *         name, 32 syntax error in event data, 40 invalid SMB credential
 *       , 41 invalid SMB share path, 42 invalid SMB file path,
 *         50 invalid TippingPoint credential, 51 invalid TippingPoint hostname,
 *         52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS
 *         workaround setting.
 *         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, ret;
  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 (acl_user_may ("modify_alert") == 0)
    {
      sql_rollback ();
      return 99;
    }

  ret = check_alert_params (event, condition, method);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  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 (event != EVENT_NEW_SECINFO && event != EVENT_UPDATED_SECINFO && 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++)))
        {
          int validation_result;
          gchar *name = sql_quote (item);
          gchar *data = sql_quote (item + strlen (item) + 1);

          validation_result = validate_alert_event_data (name, data, event);

          if (validation_result)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();

              switch (validation_result)
                {
                  case 1:
                    return 31;
                  case 2:
                    return 32;
                  default:
                    return -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;
                  case 3:
                    return 11;
                  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++)))
        {
          int ret;
          gchar *name, *data;

          name = sql_quote (item);
          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) > max_email_message_length)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();
              return 10;
            }

          ret = validate_scp_data (method, name, &data);
          if (ret)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

          ret = validate_send_data (method, name, &data);
          if (ret)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

          ret = validate_smb_data (method, name, &data);
          if (ret)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

          ret = validate_tippingpoint_data (method, name, &data);
          if (ret)
            {
              g_free (name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

          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 (acl_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 name of an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return Name of alert.
 */
char *
alert_name (alert_t alert)
{
  return sql_string ("SELECT name FROM alerts WHERE id = %llu;", alert);
}

/**
 * @brief Return the UUID of the owner of an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return UUID of owner.
 */
char *
alert_owner_uuid (alert_t alert)
{
  return sql_string ("SELECT uuid FROM users"
                     " WHERE id = (SELECT owner 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 Return the event associated with an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return Event.
 */
static event_t
alert_event (alert_t alert)
{
  return sql_int ("SELECT event 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, KEYWORD_TYPE_INTEGER },                                   \
   { "condition", NULL, KEYWORD_TYPE_INTEGER },                               \
   { "method", NULL, KEYWORD_TYPE_INTEGER },                                  \
   { "filter", NULL, KEYWORD_TYPE_INTEGER },                                  \
   { G_STRINGIFY (LOCATION_TABLE), NULL, KEYWORD_TYPE_INTEGER },              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

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

/**
 * @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 = acl_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 = acl_where_owned ("alert", &get, 0, "any", 0, permissions);
  array_free (permissions);

  if (task && 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 if (task)
    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);
  else if (event)
    init_iterator (iterator,
                   "SELECT alerts.id, alerts.uuid, alerts.name"
                   " FROM alerts"
                   " WHERE event = %i"
                   " AND %s;",
                   event,
                   owned_clause);
  else
    init_iterator (iterator,
                   "SELECT alerts.id, alerts.uuid, alerts.name"
                   " FROM alerts"
                   " WHERE %s;",
                   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;
    }

  g_debug ("   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=\""
                 : "Content-Type: text/plain; charset=utf-8\n"
                   "Content-Transfer-Encoding: 8bit"),
               /* @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: 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);

  g_debug ("   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;

  g_debug ("   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 Initialize common files and variables for an alert script.
 *
 * The temporary file / dir parameters will be modified by mkdtemp / mkstemp
 *  to contain the actual path.
 * The extra data is meant for data that should not be logged like passwords.
 *
 * @param[in]     report_filename Filename for the report or NULL for default.
 * @param[in]     report          Report that should be sent.
 * @param[in]     report_size     Size of the report.
 * @param[in]     extra_content   Optional extra data, e.g. credentials
 * @param[in]     extra_size      Optional extra data length
 * @param[in,out] report_dir      Template for temporary report directory
 * @param[out]    report_path     Pointer to store path to report file at
 * @param[out]    error_file      Temporary file for error messages
 * @param[out]    extra_file      Temporary extra data file
 *
 * @return 0 success, -1 error.
 */
static int
alert_script_init (const char *report_filename, const char* report,
                   size_t report_size,
                   const char *extra_content, size_t extra_size,
                   char *report_dir,
                   gchar **report_path, gchar **error_path, gchar **extra_path)
{
  GError *error;

  /* Create temp directory */

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

  /* Create report file */

  *report_path = g_strdup_printf ("%s/%s",
                                  report_dir,
                                  report_filename ? report_filename
                                                  : "report");

  error = NULL;
  g_file_set_contents (*report_path, report, report_size, &error);
  if (error)
    {
      g_warning ("%s: could not write report: %s",
                 __FUNCTION__, error->message);
      g_error_free (error);
      g_free (*report_path);
      openvas_file_remove_recurse (report_dir);
      return -1;
    }

  /* Create error file */

  *error_path = g_strdup_printf ("%s/error_XXXXXX", report_dir);

  if (mkstemp (*error_path) == -1)
    {
      g_warning ("%s: mkstemp for error output failed\n", __FUNCTION__);
      openvas_file_remove_recurse (report_dir);
      g_free (*report_path);
      g_free (*error_path);
      return -1;
    }

  /* Create extra data file */

  if (extra_content)
    {
      *extra_path = g_strdup_printf ("%s/extra_XXXXXX", report_dir);
      if (mkstemp (*extra_path) == -1)
        {
          g_warning ("%s: mkstemp for extra data failed\n", __FUNCTION__);
          openvas_file_remove_recurse (report_dir);
          g_free (*report_path);
          g_free (*error_path);
          g_free (*extra_path);
          return -1;
        }

      error = NULL;
      g_file_set_contents (*extra_path, extra_content, extra_size, &error);
      if (error)
        {
          g_warning ("%s: could not write extra data: %s",
                    __FUNCTION__, error->message);
          g_error_free (error);
          openvas_file_remove_recurse (report_dir);
          g_free (*report_path);
          g_free (*error_path);
          g_free (*extra_path);
          return -1;
        }
    }
  else
    *extra_path = NULL;

  return 0;
}

/**
 * @brief Execute the alert script.
 *
 * @param[in]  alert_id      UUID of the alert.
 * @param[in]  command_args  Args for the "alert" script.
 * @param[in]  report_path   Path to temporary file containing the report
 * @param[in]  report_dir    Temporary directory for the report
 * @param[in]  error_path    Path to the script error message file
 * @param[in]  extra_path    Path to the extra data file
 * @param[out] message       Custom error message generated by the script
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
alert_script_exec (const char *alert_id, const char *command_args,
                   const char *report_path, const char *report_dir,
                   const char *error_path, const char *extra_path,
                   gchar **message)
{
  gchar *script, *script_dir;
  GError *error;

  /* Setup script file name. */
  script_dir = g_build_filename (OPENVAS_DATA_DIR,
                                 "openvasmd",
                                 "global_alert_methods",
                                 alert_id,
                                 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 (script);
      g_free (script_dir);
      return -1;
    }

  /* Run the script */
  {
    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 (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 (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }
    g_free (script_dir);

    /* Call the script. */

    if (extra_path)
      command = g_strdup_printf ("%s %s %s %s"
                                 " > /dev/null 2> %s",
                                 script,
                                 command_args,
                                 extra_path,
                                 report_path,
                                 error_path);
    else
      command = g_strdup_printf ("%s %s %s"
                                 " > /dev/null 2> %s",
                                 script,
                                 command_args,
                                 report_path,
                                 error_path);
    g_free (script);

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

    if (geteuid () == 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_path, nobody->pw_uid, nobody->pw_gid)
            || chown (error_path, nobody->pw_uid, nobody->pw_gid)
            || (extra_path && chown (extra_path, 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 (command);
            return -1;
          }

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

                ret = system (command);
                /*
                 * Check shell command exit status, assuming 0 means success.
                 */
                if (ret == -1)
                  {
                    g_warning ("%s (child):"
                               " system failed with ret %i, %i, %s\n",
                               __FUNCTION__,
                               ret,
                               WEXITSTATUS (ret),
                               command);
                    exit (EXIT_FAILURE);
                  }
                else if (ret != 0)
                  {
                    if (g_file_get_contents (error_path, message,
                                             NULL, &error) == FALSE)
                      {
                        g_warning ("%s: failed to test error message: %s",
                                    __FUNCTION__, error->message);
                        g_error_free (error);
                        if (message)
                          g_free (*message);
                        exit (EXIT_FAILURE);
                      }

                    if (message == NULL)
                      exit (EXIT_FAILURE);
                    else if (*message == NULL || strcmp (*message, "") == 0)
                      {
                        g_free (*message);
                        *message
                          = g_strdup_printf ("Exited with code %d.",
                                              WEXITSTATUS (ret));

                        if (g_file_set_contents (error_path, *message,
                                                 strlen (*message),
                                                 &error) == FALSE)
                          {
                            g_warning ("%s: failed to write error message:"
                                        " %s",
                                        __FUNCTION__, error->message);
                            g_error_free (error);
                            g_free (*message);
                            exit (EXIT_FAILURE);
                          }
                      }

                    g_free (*message);
                    exit (2);
                  }

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

                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 2: // script failed
                      if (message)
                        {
                          GError *error = NULL;
                          if (g_file_get_contents (error_path, message,
                                                   NULL, &error) == FALSE)
                            {
                              g_warning ("%s: failed to get error message: %s",
                                         __FUNCTION__, error->message);
                              g_error_free (error);
                            }

                          if (strcmp (*message, "") == 0)
                            {
                              g_free (*message);
                              *message = NULL;
                            }
                        }
                      if (chdir (previous_dir))
                        g_warning ("%s: chdir failed\n",
                                   __FUNCTION__);
                      return -5;
                    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. */

        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;
          }
        else if (ret)
          {
            if (message)
              {
                GError *error = NULL;
                if (g_file_get_contents (error_path, message, NULL, &error)
                      == FALSE)
                  {
                    g_warning ("%s: failed to get error message: %s",
                               __FUNCTION__, error->message);
                    g_error_free (error);
                  }

                if (strcmp (*message, "") == 0)
                  {
                    g_free (*message);
                    *message = NULL;
                  }

                if (*message == NULL)
                  {
                    *message
                      = g_strdup_printf ("Exited with code %d.",
                                         WEXITSTATUS (ret));
                  }
              }
            g_free (previous_dir);
            g_free (command);
            return -5;
          }
      }

    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);
  }
  return 0;
}

/**
 * @brief Clean up common files and variables for running alert script.
 *
 * @param[in]  report_dir   The temporary directory.
 * @param[in]  report_path  The temporary report file path to free.
 * @param[in]  error_path   The temporary error file path to free.
 * @param[in]  extra_path   The temporary extra data file path to free.
 *
 * @return 0 success, -1 error.
 */
static int
alert_script_cleanup (const char *report_dir,
                      gchar *report_path, gchar *error_path, gchar *extra_path)
{
  openvas_file_remove_recurse (report_dir);
  g_free (report_path);
  g_free (error_path);
  g_free (extra_path);
  return 0;
}

/**
 * @brief Run an alert's "alert" script with one file of extra data.
 *
 * @param[in]  alert_id         ID of alert.
 * @param[in]  command_args     Args for the "alert" script.
 * @param[in]  report_filename  Optional report file name, default: "report"
 * @param[in]  report           Report that should be sent.
 * @param[in]  report_size      Size of the report.
 * @param[in]  extra_content    Optional extra data like passwords
 * @param[in]  extra_size       Size of the report.
 * @param[out] script_message   Custom error message of the script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
run_alert_script (const char *alert_id, const char *command_args,
                  const char *report_filename, const char *report,
                  size_t report_size,
                  const char *extra_content, size_t extra_size,
                  gchar **message)
{
  char report_dir[] = "/tmp/openvasmd_alert_XXXXXX";
  gchar *report_path, *error_path, *extra_path;
  int ret;

  if (message)
    *message = NULL;

  if (report == NULL)
    return -1;

  g_debug ("report: %s", report);

  /* Setup files. */
  ret = alert_script_init (report_filename, report, report_size,
                           extra_content, extra_size,
                           report_dir,
                           &report_path, &error_path, &extra_path);
  if (ret)
    return ret;

  /* Run the script */
  ret = alert_script_exec (alert_id, command_args, report_path, report_dir,
                           error_path, extra_path, message);
  if (ret)
    {
      alert_script_cleanup (report_dir, report_path, error_path, extra_path);
      return ret;
    }

  /* Remove the directory. */
  ret = alert_script_cleanup (report_dir, report_path, error_path, extra_path);

  return ret;
}

/**
 * @brief Send an SNMP TRAP to a host.
 *
 * @param[in]  community  Community.
 * @param[in]  agent      Agent.
 * @param[in]  message    Message.
 * @param[out] script_message  Custom error message of the script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
snmp_to_host (const char *community, const char *agent, const char *message,
              gchar **script_message)
{
  gchar *clean_community, *clean_agent, *clean_message, *command_args;
  int ret;

  g_debug ("SNMP to host: %s", agent);

  if (community == NULL || agent == NULL || message == NULL)
    {
      g_warning ("%s: parameter was NULL", __FUNCTION__);
      return -1;
    }

  clean_community = g_shell_quote (community);
  clean_agent = g_shell_quote (agent);
  clean_message = g_shell_quote (message);
  command_args = g_strdup_printf ("%s %s %s", clean_community, clean_agent,
                                  clean_message);
  g_free (clean_community);
  g_free (clean_agent);
  g_free (clean_message);

  ret = run_alert_script ("9d435134-15d3-11e6-bf5c-28d24461215b", command_args,
                          "report", "", 0, NULL, 0, script_message);

  g_free (command_args);
  return ret;
}

/**
 * @brief Send a report to a host via TCP.
 *
 * @param[in]  host         Address of host.
 * @param[in]  port         Port of host.
 * @param[in]  report      Report that should be sent.
 * @param[in]  report_size Size of the report.
 * @param[out] script_message  Custom error message of the script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
send_to_host (const char *host, const char *port,
              const char *report, int report_size,
              gchar **script_message)
{
  gchar *clean_host, *clean_port, *command_args;
  int ret;

  g_debug ("send to host: %s:%s", host, port);

  if (host == NULL)
    return -1;

  clean_host = g_shell_quote (host);
  clean_port = g_shell_quote (port);
  command_args = g_strdup_printf ("%s %s", clean_host, clean_port);
  g_free (clean_host);
  g_free (clean_port);

  ret = run_alert_script ("4a398d42-87c0-11e5-a1c0-28d24461215b", command_args,
                          "report", report, report_size, NULL, 0,
                          script_message);

  g_free (command_args);
  return ret;
}

/**
 * @brief Send a report to a host via TCP.
 *
 * @param[in]  password     Password.
 * @param[in]  username     Username.
 * @param[in]  host         Address of host.
 * @param[in]  path         Destination filename with path.
 * @param[in]  known_hosts  Content for known_hosts file.
 * @param[in]  report       Report that should be sent.
 * @param[in]  report_size  Size of the report.
 * @param[out] script_message  Custom error message of the alert script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
scp_to_host (const char *password, const char *username, const char *host,
             const char *path, const char *known_hosts, const char *report,
             int report_size, gchar **script_message)
{
  gchar *clean_username, *clean_host, *clean_path;
  gchar *clean_known_hosts, *command_args;
  int ret;

  g_debug ("scp to host: %s@%s:%s", username, host, path);

  if (password == NULL || username == NULL || host == NULL || path == NULL)
    return -1;

  if (known_hosts == NULL)
    known_hosts = "";

  clean_username = g_shell_quote (username);
  clean_host = g_shell_quote (host);
  clean_path = g_shell_quote (path);
  clean_known_hosts = g_shell_quote (known_hosts);
  command_args = g_strdup_printf ("%s %s %s %s", clean_username,
                                  clean_host, clean_path, clean_known_hosts);
  g_free (clean_username);
  g_free (clean_host);
  g_free (clean_path);
  g_free (clean_known_hosts);

  ret = run_alert_script ("2db07698-ec49-11e5-bcff-28d24461215b",
                          command_args, "report", report, report_size,
                          password, strlen (password), script_message);

  g_free (command_args);
  return ret;
}

/**
 * @brief Send a report to a host via SMB.
 *
 * @param[in]  password       Password.
 * @param[in]  username       Username.
 * @param[in]  share_path     Name/address of host and name of the share.
 * @param[in]  file_path      Destination filename with path inside the share.
 * @param[in]  report         Report that should be sent.
 * @param[in]  report_size    Size of the report.
 * @param[out] script_message Custom error message of the alert script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
smb_send_to_host (const char *password, const char *username,
                  const char *share_path, const char *file_path,
                  const char *report, gsize report_size,
                  gchar **script_message)
{
  gchar *clean_share_path, *clean_file_path;
  gchar *authfile_content;
  gchar *command_args;
  int ret;

  g_debug ("smb as %s to share: %s, path: %s", username, share_path, file_path);

  if (password == NULL || username == NULL 
      || share_path == NULL || file_path == NULL)
    return -1;

  clean_share_path = g_shell_quote (share_path);
  clean_file_path = g_shell_quote (file_path);
  authfile_content = g_strdup_printf ("username = %s\n"
                                      "password = %s\n",
                                      username, password);
  command_args = g_strdup_printf ("%s %s",
                                  clean_share_path, clean_file_path);
  g_free (clean_share_path);
  g_free (clean_file_path);

  ret = run_alert_script ("c427a688-b653-40ab-a9d0-d6ba842a9d63", command_args,
                          "report", report, report_size,
                          authfile_content, strlen (authfile_content),
                          script_message);

  g_free (authfile_content);
  g_free (command_args);
  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;

  g_debug ("send to sourcefire: %s:%s", ip, port);
  g_debug ("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 (geteuid () == 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);
                  }

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

        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;

  g_debug ("send to verinice: %s", url);
  g_debug ("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 (geteuid () == 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);
                  }

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

        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 Convert an XML report and send it to a TippingPoint SMS.
 *
 *
 * @return 0 success, -1 error.
 */
static int
send_to_tippingpoint (const char *report, size_t report_size,
                      const char *username, const char *password,
                      const char *hostname, const char *certificate,
                      int cert_workaround, gchar **message)
{
  const char *alert_id = "5b39c481-9137-4876-b734-263849dd96ce";
  char report_dir[] = "/tmp/openvasmd_alert_XXXXXX";
  gchar *auth_config, *report_path, *error_path, *extra_path, *cert_path;
  gchar *command_args, *hostname_clean, *convert_script;
  GError *error = NULL;
  int ret;

  /* Setup auth file contents */
  auth_config = g_strdup_printf ("machine %s\n"
                                 "login %s\n"
                                 "password %s\n",
                                 cert_workaround ? "Tippingpoint" : hostname,
                                 username, password);

  /* Setup common files. */
  ret = alert_script_init ("report", report, report_size,
                           auth_config, strlen (auth_config),
                           report_dir,
                           &report_path, &error_path, &extra_path);
  g_free (auth_config);

  if (ret)
    return ret;

  /* Setup certificate file */
  cert_path = g_build_filename (report_dir, "cacert.pem", NULL);

  if (g_file_set_contents (cert_path,
                           certificate, strlen (certificate),
                           &error) == FALSE)
    {
      g_warning ("%s: Failed to write TLS certificate to file: %s",
                 __FUNCTION__, error->message);
      alert_script_cleanup (report_dir, report_path, error_path, extra_path);
      g_free (cert_path);
      return -1;
    }

  if (geteuid () == 0)
    {
      struct passwd *nobody;

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

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

  /* Build args and run the script */
  hostname_clean = g_shell_quote (hostname);
  convert_script = g_build_filename (OPENVAS_DATA_DIR,
                                     "openvasmd",
                                     "global_alert_methods",
                                     alert_id,
                                     "report-convert.py",
                                     NULL);

  command_args = g_strdup_printf ("%s %s %d %s",
                                  hostname_clean, cert_path, cert_workaround,
                                  convert_script);

  g_free (hostname_clean);
  g_free (cert_path);
  g_free (convert_script);

  ret = alert_script_exec (alert_id, command_args, report_path, report_dir,
                           error_path, extra_path, message);
  if (ret)
    {
      alert_script_cleanup (report_dir, report_path, error_path, extra_path);
      return ret;
    }

  /* Remove the directory. */
  ret = alert_script_cleanup (report_dir, report_path, error_path, extra_path);
  return ret;
}

/**
 * @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 Format string for simple notice alert email.
 */
#define SECINFO_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]  alert       Alert.
 * @param[in]  task        Task.
 * @param[in]  total       Total number of resources (for SecInfo alerts).
 *
 * @return Freshly allocated subject.
 */
static gchar *
alert_subject_print (const gchar *subject, event_t event,
                     const void *event_data,
                     alert_t alert, task_t task, int total)
{
  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 'd':
              /* Date that the check was last performed. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  char time_string[100];
                  time_t date;
                  struct tm *tm;

                  if (event_data && (strcasecmp (event_data, "nvt") == 0))
                    date = nvts_check_time ();
                  else if (type_is_scap (event_data))
                    date = scap_check_time ();
                  else
                    date = cert_check_time ();
                  tm = localtime (&date);
                  if (strftime (time_string, 98, "%F", tm) == 0)
                    break;
                  g_string_append (new_subject, time_string);
                }
              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':
              {
                if (task)
                  {
                    char *name = task_name (task);
                    g_string_append (new_subject, name);
                    free (name);
                  }
                break;
              }
            case 'N':
              {
                /* Alert Name */
                char *name = alert_name (alert);
                g_string_append (new_subject, name);
                free (name);
                break;
              }
            case 'q':
              if (event == EVENT_NEW_SECINFO)
                g_string_append (new_subject, "New");
              else if (event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, "Updated");
              break;
            case 's':
              /* Type. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, type_name (event_data));
              break;
            case 'S':
              /* Type, plural. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, type_name_plural (event_data));
              break;
            case 'T':
              g_string_append_printf (new_subject, "%i", total);
              break;
            case 'u':
              {
                /* Current user or owner of the Alert */
                if (current_credentials.username
                    && strcmp (current_credentials.username, ""))
                  {
                    g_string_append (new_subject, current_credentials.username);
                  }
                else
                  {
                    char *owner = alert_owner_uuid (alert);
                    gchar *name = user_name (owner);
                    g_string_append (new_subject, name);
                    free (owner);
                    g_free (name);
                  }
                break;
              }
            case 'U':
              {
                /* Alert UUID */
                char *uuid = alert_uuid (task);
                g_string_append (new_subject, uuid);
                free (uuid);
                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]  alert        Alert.
 * @param[in]  condition    Alert condition.
 * @param[in]  format_name  Report format name.
 * @param[in]  filter       Filter.
 * @param[in]  term         Filter term.
 * @param[in]  zone         Timezone.
 * @param[in]  host_summary    Host summary.
 * @param[in]  content         The report, for inlining.
 * @param[in]  content_length  Length of content.
 * @param[in]  truncated       Whether the report was truncated.
 * @param[in]  total        Total number of resources (for SecInfo alerts).
 * @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 total,
                     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 'd':
              /* Date that the check was last performed. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  char time_string[100];
                  time_t date;
                  struct tm *tm;

                  if (event_data && (strcasecmp (event_data, "nvt") == 0))
                    date = nvts_check_time ();
                  else if (type_is_scap (event_data))
                    date = scap_check_time ();
                  else
                    date = cert_check_time ();
                  tm = localtime (&date);
                  if (strftime (time_string, 98, "%F", tm) == 0)
                    break;
                  g_string_append (new_message, time_string);
                }
              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':
              if (task)
                {
                  char *name = task_name (task);
                  g_string_append (new_message, name);
                  free (name);
                }
              break;
            case 'N':
              {
                /* Alert Name */
                char *name = alert_name (alert);
                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 'q':
              {
                if (event == EVENT_NEW_SECINFO)
                  g_string_append (new_message, "New");
                else if (event == EVENT_UPDATED_SECINFO)
                  g_string_append (new_message, "Updated");
                break;
              }
            case 's':
              /* Type. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_message, type_name (event_data));
              break;
            case 'S':
              /* Type, plural. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_message, type_name_plural (event_data));
              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 'T':
              {
                g_string_append_printf (new_message, "%i", total);
                break;
              }
            case 'u':
              {
                /* Current user or owner of the Alert */
                if (current_credentials.username
                    && strcmp (current_credentials.username, ""))
                  {
                    g_string_append (new_message, current_credentials.username);
                  }
                else
                  {
                    char *owner = alert_owner_uuid (alert);
                    gchar *name = user_name (owner);
                    g_string_append (new_message, name);
                    free (owner);
                    g_free (name);
                  }
                break;
              }
            case 'U':
              {
                /* Alert UUID */
                char *uuid = alert_uuid (task);
                g_string_append (new_message, uuid);
                free (uuid);
                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 Print an SCP alert file path.
 *
 * @param[in]  message      Format string for message.
 * @param[in]  task         Task.
 *
 * @return Freshly allocated message.
 */
static gchar *
scp_alert_path_print (const gchar *message, task_t task)
{
  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 'n':
              if (task)
                {
                  char *name = task_name (task);
                  g_string_append (new_message, name);
                  free (name);
                }
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_message, *point);

  return g_string_free (new_message, FALSE);
}

/**
 * @brief Build and send email for SecInfo alert.
 *
 * @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.
 * @param[in]  to_address    To address.
 * @param[in]  from_address  From address.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter.
 */
int
email_secinfo (alert_t alert, task_t task, event_t event,
               const void* event_data, alert_method_t method,
               alert_condition_t condition, const gchar *to_address,
               const gchar *from_address)
{
  gchar *alert_subject, *message, *subject, *example, *list, *type, *base64;
  gchar *body;
  char *notice;
  int ret, count;

  list = new_secinfo_list (event, event_data, alert, &count);

  type = g_strdup (event_data);
  if (type && (example = strstr (type, "_example")))
    example[0] = '\0';

  /* Setup subject. */

  subject = g_strdup_printf
             ("[OpenVAS-Manager] %s %s arrived",
              event == EVENT_NEW_SECINFO ? "New" : "Updated",
              type_name_plural (type ? type : "nvt"));
  alert_subject = alert_data (alert, "method", "subject");
  if (alert_subject && strlen (alert_subject))
    {
      g_free (subject);
      subject = alert_subject_print (alert_subject, event,
                                     type, alert, task, count);
    }
  g_free (alert_subject);

  /* Setup body. */

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

  message = alert_data (alert, "method", "message");
  if (message == NULL || strlen (message) == 0)
    {
      g_free (message);
      if (notice && strcmp (notice, "0") == 0)
        /* Message with inlined report. */
        message = g_strdup (SECINFO_ALERT_MESSAGE_INCLUDE);
      else if (notice && strcmp (notice, "2") == 0)
        /* Message with attached report. */
        message = g_strdup (SECINFO_ALERT_MESSAGE_ATTACH);
      else
        /* Simple notice message. */
        message = NULL;
    }

  base64 = NULL;
  if (list && notice && strcmp (notice, "2") == 0)
    {
      /* Add list as text attachment. */
      if (max_attach_length <= 0
          || strlen (list) <= max_attach_length)
        base64 = g_base64_encode ((guchar*) list,
                                  strlen (list));
    }

  if (message && strlen (message))
    body = alert_message_print (message, event, type,
                                task, alert, condition,
                                NULL, 0, NULL, NULL, NULL,
                                list,
                                list ? strlen (list) : 0,
                                0, count, 0);
  else
    {
      gchar *event_desc, *condition_desc;
      event_desc = event_description (event, event_data, NULL);
      condition_desc = alert_condition_description
                        (condition, alert);
      body = g_strdup_printf (SECINFO_SIMPLE_NOTICE_FORMAT,
                              event_desc,
                              event_desc,
                              condition_desc);
      free (event_desc);
      free (condition_desc);
    }

  g_free (message);
  g_free (list);

  /* Send email. */

  ret = email (to_address, from_address, subject,
               body, base64,
               base64 ? "text/plain" : NULL,
               base64 ? "secinfo-alert" : NULL,
               base64 ? "txt" : NULL);
  g_free (body);
  g_free (type);
  g_free (subject);
  return ret;
}

/**
 * @brief Generate report content for alert
 *
 * @param[in]  alert  The alert the report is generated for.
 * @param[in]  report Report or NULL to get last report of task.
 * @param[in]  task   Task the report belongs to.
 * @param[in]  get    GET data for the report.
 * @param[in]  report_format_data_name  Name of alert data with report format.
 * @param[in]  fallback_format_id       UUID of fallback report format.
 * @param[in]  notes_details     Whether to include details of notes in report.
 * @param[in]  overrides_details Whether to include override details in report.
 * @param[out] content              Report content location.
 * @param[out] content_length       Length of report content.
 * @param[out] extension            File extension of report format.
 * @param[out] content_type         Content type of report format.
 * @param[out] term                 Filter term.
 * @param[out] host_summary         Summary of results per host.
 * @param[out] used_report_format   Report format used.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter.
 */
static int
report_content_for_alert (alert_t alert, report_t report, task_t task,
                          const get_data_t *get,
                          const char *report_format_data_name,
                          const char *fallback_format_id,
                          int notes_details, int overrides_details,
                          gchar **content, gsize *content_length,
                          gchar **extension, gchar **content_type,
                          gchar **term, gchar **report_zone,
                          gchar **host_summary,
                          report_format_t *used_report_format)
{
  report_format_t report_format;
  char *filt_id, *format_uuid;
  filter_t filter;
  get_data_t *alert_filter_get;
  gchar *report_content;

  assert (content);

  // Get filter
  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;
    }

  if (filter)
    {
      alert_filter_get = g_malloc0 (sizeof (get_data_t));
      alert_filter_get->details = get->details;
      alert_filter_get->ignore_pagination = get->ignore_pagination;
      alert_filter_get->ignore_max_rows_per_page 
        = get->ignore_max_rows_per_page;
      alert_filter_get->filt_id = g_strdup (filt_id);
      alert_filter_get->filter = filter_term (filt_id);
    }
  else
    alert_filter_get = NULL;

  // Get last report from task if no report is given
  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;
      }

  // Get report format or use fallback
  if (report_format_data_name)
    format_uuid = alert_data (alert,
                              "method",
                              report_format_data_name);
  else
    format_uuid = NULL;

  if (format_uuid && strlen (format_uuid))
    {
      if (find_report_format_with_permission (format_uuid,
                                              &report_format,
                                              "get_report_formats")
          || (report_format == 0))
        {
          g_warning ("%s: Could not find RFP '%s' for %s",
                     __FUNCTION__, format_uuid,
                     alert_method_name (alert_method (alert)));
          g_free (format_uuid);
          return -2;
        }
      g_free (format_uuid);
    }
  else
    {
      g_free (format_uuid);
      if (find_report_format_with_permission
            (fallback_format_id,
             &report_format,
             "get_report_formats")
          || (report_format == 0))
        {
          g_warning ("%s: Could not find fallback RFP '%s' for %s",
                      __FUNCTION__, fallback_format_id,
                     alert_method_name (alert_method (alert)));
          return -2;
        }
    }

  // Generate report content
  report_content = manage_report (report,
                                  alert_filter_get ? alert_filter_get : get,
                                  report_format,
                                  notes_details,
                                  overrides_details,
                                  NULL, /* Type. */
                                  content_length,
                                  extension,
                                  content_type,
                                  term,
                                  report_zone,
                                  host_summary);

  if (alert_filter_get)
    {
      get_data_reset (alert_filter_get);
      g_free (alert_filter_get);
    }

  if (report_content == NULL)
    return -1;

  *content = report_content;
  *used_report_format = report_format;

  return 0;
}

/**
 * @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]  get         GET data for report.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[out] script_message  Custom error message from the script.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter, -4 failed to find credential, -5 alert script failed.
 */
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,
            const get_data_t *get, int notes_details, int overrides_details,
            gchar **script_message)
{
  char *name_alert, *name_task;
  gchar *event_desc, *alert_desc;

  if (script_message)
    *script_message = NULL;

  name_alert = alert_name (alert);
  name_task = task_name (task);
  event_desc = event_description (event, event_data, NULL);
  alert_desc = alert_condition_description (condition, alert);
  g_log ("event alert", G_LOG_LEVEL_MESSAGE,
         "The alert %s%s%s was triggered "
         "(Event: %s, Condition: %s)",
         name_alert,
         name_task ? " for task " : "",
         name_task ? name_task : "",
         event_desc,
         alert_desc);
  free (name_task);
  free (name_alert);
  free (event_desc);
  free (alert_desc);

  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;
              get_data_t *alert_filter_get;

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

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

              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  ret = email_secinfo (alert, task, event, event_data, method,
                                       condition, to_address, from_address);
                  free (to_address);
                  free (from_address);
                  return ret;
                }

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

              if (filter)
                {
                  alert_filter_get = g_malloc0 (sizeof (get_data_t));
                  alert_filter_get->details = get->details;
                  alert_filter_get->ignore_pagination = get->ignore_pagination;
                  alert_filter_get->ignore_max_rows_per_page
                    = get->ignore_max_rows_per_page;
                  alert_filter_get->filt_id = g_strdup (filt_id);
                  alert_filter_get->filter = filter_term (filt_id);
                }
              else
                alert_filter_get = NULL;

              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);
                          if (alert_filter_get)
                            {
                              get_data_reset (alert_filter_get);
                              g_free (alert_filter_get);
                            }
                          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);
                      if (alert_filter_get)
                        {
                          get_data_reset (alert_filter_get);
                          g_free (alert_filter_get);
                        }
                      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,
                                                  alert_filter_get
                                                    ? alert_filter_get : get,
                                                  report_format,
                                                  notes_details,
                                                  overrides_details,
                                                  NULL, /* Type. */
                                                  &content_length,
                                                  NULL,    /* Extension. */
                                                  NULL,   /* Content type. */
                                                  &term,
                                                  &report_zone,
                                                  &host_summary);
                  if (alert_filter_get)
                    {
                      get_data_reset (alert_filter_get);
                      g_free (alert_filter_get);
                    }
                  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,
                                                     alert, task, 0);
                    }
                  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,
                                              0,
                                              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);
                          if (alert_filter_get)
                            {
                              get_data_reset (alert_filter_get);
                              g_free (alert_filter_get);
                            }
                          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);
                      if (alert_filter_get)
                        {
                          get_data_reset (alert_filter_get);
                          g_free (alert_filter_get);
                        }
                      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,
                                                  alert_filter_get
                                                    ? alert_filter_get
                                                    : get,
                                                  report_format,
                                                  notes_details,
                                                  overrides_details,
                                                  NULL, /* Type. */
                                                  &content_length,
                                                  &extension,
                                                  &type,
                                                  &term,
                                                  &report_zone,
                                                  &host_summary);
                  if (alert_filter_get)
                    {
                      get_data_reset (alert_filter_get);
                      g_free (alert_filter_get);
                    }
                  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,
                                                     alert, task, 0);
                    }
                  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,
                                              0,
                                              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,
                                                     alert, task, 0);
                    }
                  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, 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);
                  if (alert_filter_get)
                    {
                      get_data_reset (alert_filter_get);
                      g_free (alert_filter_get);
                    }
                }
              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 " ACL_USER_OWNS ()
                              " ORDER BY coalesce (owner, 0) 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 (extension);
              free (type);
              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;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              g_warning ("%s: Event \"%s NVTs arrived\" with method"
                         " \"HTTP Get\" not support",
                         __FUNCTION__,
                         event == EVENT_NEW_SECINFO ? "New" : "Updated");
              return -1;
            }

          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_SCP:
        {
          credential_t credential;
          char *credential_id;
          char *password, *username, *host, *path, *known_hosts, *filt_id;
          gchar *report_content, *format_uuid, *alert_path;
          gsize content_length;
          report_format_t report_format;
          int ret;
          filter_t filter;
          get_data_t *alert_filter_get;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              gchar *message;

              credential_id = alert_data (alert, "method", "scp_credential");
              if (find_credential_with_permission (credential_id,
                                                   &credential,
                                                   "get_credentials"))
                {
                  return -1;
                }
              else if (credential == 0)
                {
                  return -4;
                }
              else
                {
                  message = new_secinfo_message (event, event_data, alert);

                  password = credential_encrypted_value (credential,
                                                         "password");
                  username = credential_value (credential, "username");

                  host = alert_data (alert, "method", "scp_host");
                  path = alert_data (alert, "method", "scp_path");
                  known_hosts = alert_data (alert, "method", "scp_known_hosts");

                  alert_path = scp_alert_path_print (path, task);
                  free (path);

                  ret = scp_to_host (password, username, host, alert_path,
                                     known_hosts, message, strlen (message),
                                     script_message);

                  g_free (message);
                  free (password);
                  free (username);
                  free (host);
                  g_free (alert_path);
                  free (known_hosts);

                  return ret;
                }
            }

          format_uuid = alert_data (alert,
                                    "method",
                                    "scp_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 ("%s: Could not find SCP RFP '%s'", __FUNCTION__,
                             format_uuid);
                  g_free (format_uuid);
                  return -2;
                }
              g_free (format_uuid);
            }
          else
            {
              g_free (format_uuid);
              if (find_report_format_with_permission
                   ("a994b278-1f62-11e1-96ac-406186ea4fc5",
                    &report_format,
                    "get_report_formats")
                  || (report_format == 0))
                {
                  g_warning ("%s: Could not find XML RFP for SCP",
                             __FUNCTION__);
                  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);
          filter = 0;
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          if (filter)
            {
              alert_filter_get = g_malloc0 (sizeof (get_data_t));
              alert_filter_get->details = get->details;
              alert_filter_get->ignore_pagination = get->ignore_pagination;
              alert_filter_get->ignore_max_rows_per_page
                = get->ignore_max_rows_per_page;
              alert_filter_get->filt_id = g_strdup (filt_id);
              alert_filter_get->filter = filter_term (filt_id);
            }
          else
            alert_filter_get = NULL;

          report_content = manage_report (report,
                                          alert_filter_get ? alert_filter_get
                                                           : get,
                                          report_format,
                                          notes_details, overrides_details,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          NULL,
                                          NULL,
                                          NULL);
          free (filt_id);
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          if (report_content == NULL)
            {
              g_warning ("%s: Empty Report", __FUNCTION__);
              return -1;
            }

          credential_id = alert_data (alert, "method", "scp_credential");
          if (find_credential_with_permission (credential_id, &credential,
                                               "get_credentials"))
            {
              g_free (report_content);
              return -1;
            }
          else if (credential == 0)
            {
              g_free (report_content);
              return -4;
            }
          else
            {
              password = credential_encrypted_value (credential, "password");
              username = credential_value (credential, "username");

              host = alert_data (alert, "method", "scp_host");
              path = alert_data (alert, "method", "scp_path");
              known_hosts = alert_data (alert, "method", "scp_known_hosts");

              alert_path = scp_alert_path_print (path, task);
              free (path);

              ret = scp_to_host (password, username, host, alert_path,
                                 known_hosts, report_content, content_length,
                                 script_message);

              free (password);
              free (username);
              free (host);
              g_free (alert_path);
              free (known_hosts);
            }
          g_free (report_content);

          return ret;
        }
      case ALERT_METHOD_SEND:
        {
          char *host, *port, *filt_id;
          gchar *report_content, *format_uuid;
          gsize content_length;
          report_format_t report_format;
          int ret;
          filter_t filter;
          get_data_t *alert_filter_get;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              gchar *message;

              message = new_secinfo_message (event, event_data, alert);
              host = alert_data (alert, "method", "send_host");
              port = alert_data (alert, "method", "send_port");

              g_debug ("send host: %s", host);
              g_debug ("send port: %s", port);

              ret = send_to_host (host, port, message, strlen (message),
                                  script_message);

              g_free (message);
              free (host);
              free (port);

              return ret;
            }

          format_uuid = alert_data (alert,
                                    "method",
                                    "send_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 ("%s: Could not find Send RFP '%s'", __FUNCTION__,
                             format_uuid);
                  g_free (format_uuid);
                  return -2;
                }
              g_free (format_uuid);
            }
          else
            {
              g_free (format_uuid);
              if (find_report_format_with_permission
                   ("a994b278-1f62-11e1-96ac-406186ea4fc5",
                    &report_format,
                    "get_report_formats")
                  || (report_format == 0))
                {
                  g_warning ("%s: Could not find XML RFP for Send",
                             __FUNCTION__);
                  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);
          filter = 0;
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          if (filter)
            {
              alert_filter_get = g_malloc0 (sizeof (get_data_t));
              alert_filter_get->details = get->details;
              alert_filter_get->ignore_pagination = get->ignore_pagination;
              alert_filter_get->ignore_max_rows_per_page
                = get->ignore_max_rows_per_page;
              alert_filter_get->filt_id = g_strdup (filt_id);
              alert_filter_get->filter = filter_term (filt_id);
            }
          else
            alert_filter_get = NULL;

          report_content = manage_report (report,
                                          alert_filter_get ? alert_filter_get
                                                           : get,
                                          report_format,
                                          notes_details, overrides_details,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          NULL,
                                          NULL,
                                          NULL);
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          free (filt_id);
          if (report_content == NULL)
            {
              g_warning ("%s: Empty Report", __FUNCTION__);
              return -1;
            }

          host = alert_data (alert, "method", "send_host");
          port = alert_data (alert, "method", "send_port");

          g_debug ("send host: %s", host);
          g_debug ("send port: %s", port);

          ret = send_to_host (host, port, report_content, content_length,
                              script_message);

          free (host);
          free (port);
          g_free (report_content);

          return ret;
        }
      case ALERT_METHOD_SMB:
        {
          char *credential_id, *username, *password;
          char *share_path, *file_path_format;
          report_format_t report_format;
          char *name, *report_id, *format_name;
          char *creation_time, *modification_time;
          gchar *file_path, *report_content, *extension;
          gsize content_length;
          credential_t credential;
          int ret;

          credential_id = alert_data (alert, "method", "smb_credential");
          share_path = alert_data (alert, "method", "smb_share_path");
          file_path_format = alert_data (alert, "method", "smb_file_path");

          report_content = NULL;
          extension = NULL;
          report_format = 0;

          g_debug ("smb_credential: %s", credential_id);
          g_debug ("smb_share_path: %s", share_path);
          g_debug ("smb_file_path: %s", file_path_format);

          ret = report_content_for_alert
                  (alert, report, task, get,
                   "smb_report_format",
                   "a994b278-1f62-11e1-96ac-406186ea4fc5", /* XML fallback */
                   notes_details, overrides_details,
                   &report_content, &content_length, &extension,
                   NULL, NULL, NULL, NULL, &report_format);
          if (ret || report_content == NULL)
            {
              free (credential_id);
              free (share_path);
              free (file_path_format);
              g_free (report_content);
              g_free (extension);
              return ret ? ret : -1;
            }

          name = task_name (task);
          report_id = report_uuid (report);

          format_name
            = sql_string ("SELECT name"
                          " FROM report_formats"
                          " WHERE id = %llu",
                          report_format);

          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_path = openvas_export_file_name (file_path_format,
                                                current_credentials.username,
                                                "report",
                                                report_id,
                                                creation_time,
                                                modification_time,
                                                name,
                                                format_name);

          free (name);
          free (report_id);
          free (format_name);
          free (creation_time);
          free (modification_time);

          credential = 0;
          ret = find_credential_with_permission (credential_id, &credential,
                                                 "get_credentials");
          if (ret || credential == 0)
            {
              if (ret == 0)
                {
                  g_warning ("%s: Could not find credential %s",
                             __FUNCTION__, credential_id);
                }
              free (credential_id);
              free (share_path);
              free (file_path);
              g_free (report_content);
              g_free (extension);
              return ret ? -1 : -4;
            }

          username = credential_value (credential, "username");
          password = credential_encrypted_value (credential, "password");

          ret = smb_send_to_host (password, username, share_path, file_path,
                                  report_content, content_length,
                                  script_message);

          g_free (username);
          g_free (password);
          free (credential_id);
          free (share_path);
          free (file_path);
          g_free (report_content);
          g_free (extension);
          return ret;
        }
      case ALERT_METHOD_SNMP:
        {
          char *community, *agent, *snmp_message;
          int ret;
          gchar *message;

          community = alert_data (alert, "method", "snmp_community");
          agent = alert_data (alert, "method", "snmp_agent");
          snmp_message = alert_data (alert, "method", "snmp_message");

          g_debug ("snmp_message: %s", snmp_message);
          g_debug ("snmp_community: %s", community);
          g_debug ("snmp_agent: %s", agent);

          if (snmp_message)
            {
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  int count;
                  gchar *list, *example, *type;

                  type = g_strdup (event_data);

                  if (type && (example = strstr (type, "_example")))
                    example[0] = '\0';

                  list = new_secinfo_list (event, event_data, alert, &count);
                  g_free (list);

                  message = alert_subject_print (snmp_message, event, type,
                                                 alert, task, count);

                  g_free (type);
                }
              else
                message = alert_subject_print (snmp_message, event, event_data,
                                               alert, task, 0);
            }
          else
            {
              gchar *event_desc;
              event_desc = event_description (event, event_data, NULL);
              message = g_strdup_printf ("%s", event_desc);
              g_free (event_desc);
            }

          ret = snmp_to_host (community, agent, message, script_message);

          free (agent);
          free (community);
          free (snmp_message);
          g_free (message);
          return ret;
        }
      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;
          get_data_t *alert_filter_get;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              g_warning ("%s: Event \"%s NVTs arrived\" with method"
                         " \"Sourcefire\" not support",
                         __FUNCTION__,
                         event == EVENT_NEW_SECINFO ? "New" : "Updated");
              return -1;
            }

          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);
          filter = 0;
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          if (filter)
            {
              alert_filter_get = g_malloc0 (sizeof (get_data_t));
              alert_filter_get->details = get->details;
              alert_filter_get->ignore_pagination = get->ignore_pagination;
              alert_filter_get->ignore_max_rows_per_page
                = get->ignore_max_rows_per_page;
              alert_filter_get->filt_id = g_strdup (filt_id);
              alert_filter_get->filter = filter_term (filt_id);
            }
          else
            alert_filter_get = NULL;

          report_content = manage_report (report,
                                          alert_filter_get ? alert_filter_get
                                                           : get,
                                          report_format,
                                          notes_details, overrides_details,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          NULL,
                                          NULL,
                                          NULL);
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          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");

          g_debug ("  sourcefire   ip: %s", ip);
          g_debug ("  sourcefire port: %s", port);
          g_debug ("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);

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

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

          g_free (level);
          g_free (message);

          return 0;
        }
      case ALERT_METHOD_TIPPINGPOINT:
        {
          int ret;
          report_format_t report_format;
          gchar *report_content, *extension;
          size_t content_length;
          char *credential_id, *username, *password, *hostname, *certificate;
          credential_t credential;
          char *tls_cert_workaround_str;
          int tls_cert_workaround;

          /* TLS certificate subject workaround setting */
          tls_cert_workaround_str 
            = alert_data (alert, "method",
                          "tp_sms_tls_workaround");
          if (tls_cert_workaround_str)
            tls_cert_workaround = !!(atoi (tls_cert_workaround_str));
          else
            tls_cert_workaround = 0;
          g_free (tls_cert_workaround_str);

          /* SSL / TLS Certificate */
          certificate = alert_data (alert, "method",
                                    "tp_sms_tls_certificate");

          /* Hostname / IP address */
          hostname = alert_data (alert, "method",
                                 "tp_sms_hostname");

          /* Credential */
          credential_id = alert_data (alert, "method",
                                      "tp_sms_credential");
          if (find_credential_with_permission (credential_id, &credential,
                                               "get_credentials"))
            {
              g_free (certificate);
              g_free (hostname);
              g_free (credential_id);
              return -1;
            }
          else if (credential == 0)
            {
              g_free (certificate);
              g_free (hostname);
              g_free (credential_id);
              return -4;
            }
          else
            {
              g_free (credential_id);
              username = credential_value (credential, "username");
              password = credential_encrypted_value (credential, "password");
            }

          /* Report content */
          extension = NULL;
          ret = report_content_for_alert
                  (alert, report, task, get,
                   NULL, /* Report format not configurable */
                   "a994b278-1f62-11e1-96ac-406186ea4fc5", /* XML fallback */
                   notes_details, overrides_details,
                   &report_content, &content_length, &extension,
                   NULL, NULL, NULL, NULL, &report_format);
          g_free (extension);
          if (ret)
            {
              g_free (username);
              g_free (password);
              g_free (hostname);
              g_free (certificate);
              return ret;
            }

          /* Send report */
          ret = send_to_tippingpoint (report_content, content_length,
                                      username, password, hostname,
                                      certificate, tls_cert_workaround,
                                      script_message);

          g_free (username);
          g_free (password);
          g_free (hostname);
          g_free (certificate);
          g_free (report_content);
          return ret;
        }
      case ALERT_METHOD_VERINICE:
        {
          credential_t credential;
          char *url, *username, *password, *filt_id;
          gchar *credential_id, *report_content, *format_uuid;
          gsize content_length;
          report_format_t report_format;
          int ret;
          filter_t filter;
          get_data_t *alert_filter_get;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              g_warning ("%s: Event \"%s NVTs arrived\" with method"
                         " \"Verinice\" not support",
                         __FUNCTION__,
                         event == EVENT_NEW_SECINFO ? "New" : "Updated");
              return -1;
            }

          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);
          filter = 0;
          if (filt_id)
            {
              if (find_filter_with_permission (filt_id, &filter, "get_filters"))
                return -1;
              if (filter == 0)
                return -3;
            }

          if (filter)
            {
              alert_filter_get = g_malloc0 (sizeof (get_data_t));
              alert_filter_get->details = get->details;
              alert_filter_get->ignore_pagination = get->ignore_pagination;
              alert_filter_get->ignore_max_rows_per_page
                = get->ignore_max_rows_per_page;
              alert_filter_get->filt_id = g_strdup (filt_id);
              alert_filter_get->filter = filter_term (filt_id);
            }
          else
            alert_filter_get = NULL;

          report_content = manage_report (report,
                                          alert_filter_get ? alert_filter_get
                                                           : get,
                                          report_format,
                                          notes_details, overrides_details,
                                          NULL, /* Type. */
                                          &content_length,
                                          NULL,    /* Extension. */
                                          NULL,    /* Content type. */
                                          NULL,
                                          NULL,
                                          NULL);
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          if (report_content == NULL)
            {
              g_warning ("Empty Report");
              return -1;
            }

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

          credential_id = alert_data (alert, "method",
                                      "verinice_server_credential");
          if (find_credential_with_permission (credential_id, &credential,
                                               "get_credentials"))
            {
              free (filt_id);
              free (url);
              g_free (report_content);
              return -1;
            }
          else if (credential == 0)
            {
              free (filt_id);
              free (url);
              g_free (report_content);
              return -4;
            }
          else
            {
              username = credential_value (credential, "username");
              password = credential_encrypted_value (credential, "password");

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

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

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

              return ret;
            }
        }
      case ALERT_METHOD_START_TASK:
        {
          openvas_connection_t connection;
          char *task_id, *report_id;
          omp_authenticate_info_opts_t auth_opts;

          if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
            {
              g_warning ("%s: Event \"%s NVTs arrived\" with method"
                         " \"Start Task\" not support",
                         __FUNCTION__,
                         event == EVENT_NEW_SECINFO ? "New" : "Updated");
              return -1;
            }

          /* Run the callback to fork a child connected to the Manager. */

          if (manage_fork_connection == NULL)
            {
              g_warning ("%s: no connection fork available\n", __FUNCTION__);
              return -1;
            }

          task_id = alert_data (alert, "method", "start_task_task");

          switch (manage_fork_connection (&connection, current_credentials.uuid))
            {
              case 0:
                /* Child.  Break, stop task, exit. */
                break;

              case -1:
                /* Parent on error. */
                g_free (task_id);
                g_warning ("%s: fork failed\n", __FUNCTION__);
                return -1;
                break;

              default:
                /* Parent.  Continue with whatever lead to this escalation. */
                g_free (task_id);
                return 0;
                break;
            }

          /* Start the task. */

          auth_opts = omp_authenticate_info_opts_defaults;
          auth_opts.username = current_credentials.username;
          if (omp_authenticate_info_ext_c (&connection, auth_opts))
            {
              openvas_connection_free (&connection);
              exit (EXIT_FAILURE);
            }

          if (omp_start_task_report_c (&connection, task_id, &report_id))
            {
              g_free (task_id);
              openvas_connection_free (&connection);
              exit (EXIT_FAILURE);
            }

          g_free (task_id);
          g_free (report_id);
          openvas_connection_free (&connection);
          exit (EXIT_SUCCESS);
        }
      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]  report      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[out] script_message  Custom error message from alert script.
 *
 * @return 0 success, -1 error, -2 failed to find report format for alert,
 *         -3 failed to find filter for alert, -4 failed to find credential,
 *         -5 alert script failed.
 */
static int
escalate_1 (alert_t alert, task_t task, report_t report, event_t event,
            const void* event_data, alert_method_t method,
            alert_condition_t condition, gchar **script_message)
{
  int ret;
  get_data_t get;
  char *results_filter;

  memset (&get, 0, sizeof (get_data_t));
  get.details = 1;

  results_filter = setting_filter ("Results");
  if (results_filter && strlen (results_filter))
    {
      get.filt_id = results_filter;
      get.filter = filter_term (results_filter);
    }
  else
    {
      get.filt_id = g_strdup ("0");
      get.filter = g_strdup_printf ("notes=1 overrides=1 sort-reverse=severity"
                                    " rows=%d",
                                    method == ALERT_METHOD_EMAIL ? 1000 : -1);
    }

  ret = escalate_2 (alert, task, report, event, event_data, method, condition,
                    &get, 1, 1, script_message);
  free (results_filter);
  g_free (get.filter);
  return ret;
}

/**
 * @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.
 * @param[out] script_message  Custom error message from alert script.
 *
 * @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, -4 failed to find
 *         credential for alert, -5 alert script failed.
 */
int
manage_alert (const char *alert_id, const char *task_id, event_t event,
              const void* event_data, gchar **script_message)
{
  alert_t alert;
  task_t task;
  alert_condition_t condition;
  alert_method_t method;

  if (acl_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 (task_id == NULL || strcmp (task_id, "0") == 0)
    task = 0;
  else
    {
      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, 0, event, event_data, method, condition,
                     script_message);
}

/**
 * @brief Header for "New NVTs" alert message.
 */
#define NEW_NVTS_HEADER                                                       \
/* Open-Xchange (OX) AppSuite XHTML File HTML Injection Vuln...  NoneAvailable       0.0 100% */ \
  "Name                                                          Solution Type  Severity  QOD\n" \
  "------------------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New NVTs" alert message, when there's an OID.
 */
#define NEW_NVTS_HEADER_OID                                                   \
/* Open-Xchange (OX) AppSuite XHTML File HTML Injection Vuln...  NoneAvailable       0.0 100%  1.3... */ \
  "Name                                                          Solution Type  Severity  QOD  OID\n" \
  "------------------------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New CVEs" alert message.
 */
#define NEW_CVES_HEADER                                                         \
/* CVE-2014-100001       6.8  Cross-site request forgery (CSRF) vulnerability in... */ \
  "Name             Severity  Description\n"                                    \
  "--------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New CPEs" alert message.
 */
#define NEW_CPES_HEADER                                                        \
/* cpe:/a:.joomclan:com_joomclip                                 1024cms... */ \
  "Name                                                          Title\n"      \
  "------------------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New CERT-Bund Advisories" alert message.
 */
#define NEW_CERT_BUNDS_HEADER                                                       \
/* CB-K13/0849  Novell SUSE Linux Enterprise Server: Mehrere Schwachstellen... */   \
  "Name         Title\n"                                                            \
  "------------------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New DFN-CERT Advisories" alert message.
 */
#define NEW_DFN_CERTS_HEADER                                                   \
/* DFN-CERT-2008-1100  Denial of Service Schwachstelle in der... */            \
  "Name                Title\n"                                                \
  "------------------------------------------------------------------------------------------\n"

/**
 * @brief Header for "New CERT-Bund Advisories" alert message.
 */
#define NEW_OVAL_DEFS_HEADER                                                   \
/* oval:org.mitre.oval:def:100116  libtiff Malloc Error Denial of Service */   \
  "OVAL ID                         Title\n"                                    \
  "------------------------------------------------------------------------------------------\n"

/**
 * @brief Test an alert.
 *
 * @param[in]  alert_id    Alert UUID.
 * @param[out] script_message  Custom message from the alert script.
 *
 * @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, -4 failed to find
 *         credential for alert, -5 alert script failed.
 */
int
manage_test_alert (const char *alert_id, gchar **script_message)
{
  int ret;
  alert_t alert;
  task_t task;
  report_t report;
  result_t result;
  char *task_id, *report_id;
  time_t now;
  const char *now_string;
  gchar *clean;

  if (acl_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 (alert_event (alert) == EVENT_NEW_SECINFO
      || alert_event (alert) == EVENT_UPDATED_SECINFO)
    {
      int ret;
      char *alert_event_data;
      gchar *type;

      alert_event_data = alert_data (alert, "event", "secinfo_type");
      type = g_strdup_printf ("%s_example", alert_event_data ?: "NVT");
      free (alert_event_data);

      if (alert_event (alert) == EVENT_NEW_SECINFO)
        ret = manage_alert (alert_id, "0", EVENT_NEW_SECINFO, (void*) type,
                            script_message);
      else
        ret = manage_alert (alert_id, "0", EVENT_UPDATED_SECINFO, (void*) type,
                            script_message);

      g_free (type);

      return ret;
    }

  task = make_task (g_strdup ("Temporary Task for Alert"),
                    g_strdup (""),
                    0,  /* Exclude from assets. */
                    0); /* Skip event and log. */

  report_id = openvas_uuid_make ();
  if (report_id == NULL)
    return -1;
  task_uuid (task, &task_id);
  report = make_report (task, report_id, TASK_STATUS_DONE);
  result = make_result (task, "127.0.0.1", "telnet (23/tcp)",
                        "1.3.6.1.4.1.25623.1.0.10330", "Alarm",
                        "A telnet server seems to be running on this port.");
  if (result == 0)
    {
      ret = -1;
      goto exit;
    }
  now = time (NULL);
  now_string = ctime (&now);
  if (strlen (now_string) == 0)
    {
      ret = -1;
      goto exit;
    }
  clean = g_strdup (now_string);
  if (clean[strlen (clean) - 1] == '\n')
    clean[strlen (clean) - 1] = '\0';
  set_task_start_time_otp (task, g_strdup (clean));
  set_scan_start_time_otp (report, g_strdup (clean));
  set_scan_host_start_time_otp (report, "127.0.0.1", clean);
  report_add_result (report, result);
  set_scan_host_end_time_otp (report, "127.0.0.1", clean);
  set_scan_end_time_otp (report, clean);
  g_free (clean);
  ret = manage_alert (alert_id,
                      task_id,
                      EVENT_TASK_RUN_STATUS_CHANGED,
                      (void*) TASK_STATUS_DONE,
                      script_message);
 exit:
  delete_task (task, 1);
  free (task_id);
  free (report_id);
  return ret;
}

/**
 * @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;
        }
      case EVENT_NEW_SECINFO:
      case EVENT_UPDATED_SECINFO:
        {
          char *alert_event_data;

          alert_event_data = alert_data (alert, "event", "secinfo_type");
          if (alert_event_data == NULL)
            return 0;
          if (strcasecmp (alert_event_data, event_data) == 0)
            return 1;
          return 0;
        }
      default:
        return 0;
    }
}

/**
 * @brief Return whether the condition of an alert is met by a task.
 *
 * @param[in]  task       Task.
 * @param[in]  report     Report.
 * @param[in]  alert      Alert.
 * @param[in]  condition  Condition.
 *
 * @return 1 if met, else 0.
 */
static int
condition_met (task_t task, report_t report, alert_t alert,
               alert_condition_t condition)
{
  switch (condition)
    {
      case ALERT_CONDITION_ALWAYS:
        return 1;
        break;
      case ALERT_CONDITION_FILTER_COUNT_AT_LEAST:
        {
          char *filter_id, *count_string;
          report_t last_report;
          int debugs, holes, infos, logs, warnings, false_positives;
          int count;
          double severity;

          /* True if there are at least the given number of results matched by
           * the given filter in the last finished report. */

          filter_id = alert_data (alert, "condition", "filter_id");
          count_string = alert_data (alert, "condition", "count");
          if (count_string)
            {
              count = atoi (count_string);
              free (count_string);
            }
          else
            count = 0;

          if (task == 0)
            {
              get_data_t get;
              int db_count, uuid_was_null;

              /* NVT event. */

              if (current_credentials.uuid == NULL)
                {
                  current_credentials.uuid = alert_owner_uuid (alert);
                  uuid_was_null = 1;
                }
              else
                uuid_was_null = 0;

              memset (&get, '\0', sizeof (get));
              if (filter_id && strlen (filter_id) && strcmp (filter_id, "0"))
                get.filt_id = filter_id;
              db_count = nvt_info_count (&get);
              if (uuid_was_null)
                {
                  free (current_credentials.uuid);
                  current_credentials.uuid = NULL;
                }
              if (db_count >= count)
                return 1;
              break;
            }

          if (report)
            last_report = report;
          else
            {
              last_report = 0;
              if (task_last_report (task, &last_report))
                g_warning ("%s: failed to get last report\n", __FUNCTION__);
            }

          g_debug ("%s: last_report: %llu", __FUNCTION__, last_report);
          if (last_report)
            {
              int db_count;
              get_data_t get;
              memset (&get, 0, sizeof (get_data_t));
              get.type = "result";
              get.filt_id = filter_id;
              report_counts_id (last_report, &debugs, &holes, &infos, &logs,
                                &warnings, &false_positives, &severity,
                                &get, NULL);

              db_count = debugs + holes + infos + logs + warnings
                         + false_positives;

              g_debug ("%s: count: %i vs %i", __FUNCTION__, db_count, count);
              if (db_count >= count)
                {
                  g_free (filter_id);
                  return 1;
                }
            }
          g_free (filter_id);
          break;
        }
      case ALERT_CONDITION_FILTER_COUNT_CHANGED:
        {
          char *direction, *filter_id, *count_string;
          report_t last_report;
          int debugs, holes, infos, logs, warnings, false_positives;
          int count;
          double severity;

          /* True if the number of results matched by the given filter in the
           * last finished report changed in the given direction with respect
           * to the second last finished report. */

          direction = alert_data (alert, "condition", "direction");
          filter_id = alert_data (alert, "condition", "filter_id");
          count_string = alert_data (alert, "condition", "count");
          if (count_string)
            {
              count = atoi (count_string);
              free (count_string);
            }
          else
            count = 0;

          if (report)
            last_report = report;
          else
            {
              last_report = 0;
              if (task_last_report (task, &last_report))
                g_warning ("%s: failed to get last report\n", __FUNCTION__);
            }

          if (last_report)
            {
              report_t second_last_report;
              int last_count;
              get_data_t get;
              get.type = "result";
              get.filt_id = filter_id;

              report_counts_id (last_report, &debugs, &holes, &infos, &logs,
                                &warnings, &false_positives, &severity,
                                &get, NULL);
              last_count = debugs + holes + infos + logs + warnings
                            + false_positives;

              second_last_report = 0;
              if (task_second_last_report (task, &second_last_report))
                g_warning ("%s: failed to get second last report\n", __FUNCTION__);

              if (second_last_report)
                {
                  int cmp, second_last_count;

                  report_counts_id (second_last_report, &debugs, &holes, &infos,
                                    &logs, &warnings, &false_positives,
                                    &severity, &get, NULL);
                  second_last_count = debugs + holes + infos + logs + warnings
                                      + false_positives;

                  cmp = last_count - second_last_count;
                  g_debug ("cmp: %i (vs %i)\n", cmp, count);
                  g_debug ("direction: %s\n", direction);
                  g_debug ("last_count: %i\n", last_count);
                  g_debug ("second_last_count: %i\n", second_last_count);
                  if (count < 0)
                    {
                      count = -count;
                      if (direction == NULL
                          || strcasecmp (direction, "increased") == 0)
                        {
                          free (direction);
                          direction = g_strdup ("decreased");
                        }
                      else if (strcasecmp (direction, "decreased") == 0)
                        {
                          free (direction);
                          direction = g_strdup ("increased");
                        }
                    }
                  if (direction == NULL)
                    {
                      /* Same as "increased". */
                      if (cmp >= count)
                        return 1;
                    }
                  else if (((strcasecmp (direction, "changed") == 0)
                            && (abs (cmp) >= count))
                           || ((strcasecmp (direction, "increased") == 0)
                               && (cmp >= count))
                           || ((strcasecmp (direction, "decreased") == 0)
                               && (cmp <= count)))
                    {
                      free (direction);
                      free (filter_id);
                      return 1;
                    }
                }
              else
                {
                  g_debug ("direction: %s\n", direction);
                  g_debug ("last_count: %i\n", last_count);
                  g_debug ("second_last_count NULL\n");
                  if (((strcasecmp (direction, "changed") == 0)
                       || (strcasecmp (direction, "increased") == 0))
                      && (last_count > 0))
                    {
                      free (direction);
                      free (filter_id);
                      return 1;
                    }
                }
            }
          free (direction);
          free (filter_id);
          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;
              g_debug ("cmp: %f\n", cmp);
              g_debug ("direction: %s\n", direction);
              g_debug ("last_level: %1.1f\n", last_severity);
              g_debug ("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)
            {
              g_debug ("direction: %s\n", direction);
              g_debug ("last_level: %1.1f\n", last_severity);
              g_debug ("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]  report      Report.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event type specific details.
 */
static void
event (task_t task, report_t report, event_t event, void* event_data)
{
  iterator_t alerts;
  GArray *alerts_triggered;
  guint index;

  g_debug ("   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, report, alert, condition))
            g_array_append_val (alerts_triggered, alert);
        }
    }
  cleanup_iterator (&alerts);

  /* Run the alerts outside the iterator, because they may 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,
                  report,
                  event,
                  event_data,
                  alert_method (alert),
                  condition,
                  NULL);
    }

  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 = acl_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", "false_positive", "log", "low", "medium", "high",         \
   "hosts", "result_hosts", "fp_per_host", "log_per_host", "low_per_host",    \
   "medium_per_host", "high_per_host", "target", NULL }

/**
 * @brief Task iterator columns.
 */
#define TASK_ITERATOR_COLUMNS_INNER                                         \
   { "run_status", NULL, KEYWORD_TYPE_INTEGER },                            \
   {                                                                        \
     "(SELECT count(*) FROM reports"                                        \
     " WHERE task = tasks.id)",                                             \
     "total",                                                               \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   {                                                                        \
     "(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",                                                        \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "run_status_name (run_status)", "status", KEYWORD_TYPE_STRING },       \
   {                                                                        \
     "(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",                                                         \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   {                                                                        \
     "(SELECT count(*) FROM reports"                                        \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " WHERE task = tasks.id AND scan_run_status = 1)",                     \
     NULL,                                                                  \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   { "hosts_ordering", NULL, KEYWORD_TYPE_STRING },                         \
   { "scanner", NULL, KEYWORD_TYPE_INTEGER }

/**
 * @brief Task iterator WHERE columns.
 */
#define TASK_ITERATOR_WHERE_COLUMNS_INNER                                    \
   {                                                                         \
     "task_threat_level (id, opts.override, opts.min_qod)",                  \
     "threat",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "task_trend (id, opts.override, opts.min_qod)",                         \
     "trend",                                                                \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "task_severity (id, opts.override, opts.min_qod)",                      \
     "severity",                                                             \
     KEYWORD_TYPE_DOUBLE                                                     \
   },                                                                        \
   {                                                                         \
     "(SELECT schedules.name FROM schedules"                                 \
     " WHERE schedules.id = tasks.schedule)",                                \
     "schedule",                                                             \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "(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",                                                             \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(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",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(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",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod,"                  \
     "                        'False Positive')"                             \
     " END",                                                                 \
     "false_positive",                                                       \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Log')"           \
     " END",                                                                 \
     "log",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Low')"           \
     " END",                                                                 \
     "low",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Medium')"        \
     " END",                                                                 \
     "medium",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'High')"          \
     " END",                                                                 \
     "high",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_host_count (task_last_report (id))"                            \
     " END",                                                                 \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_result_host_count (task_last_report (id), opts.min_qod)"       \
     " END",                                                                 \
     "result_hosts",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'False Positive') * 1.0"              \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "fp_per_host",                                                          \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Log') * 1.0"                         \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "log_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Low') * 1.0"                         \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "low_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Medium') * 1.0"                      \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "medium_per_host",                                                      \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'High') * 1.0"                        \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "high_per_host",                                                        \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT name FROM targets WHERE id = target)",                         \
     "target",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   }

/**
 * @brief Task iterator WHERE columns.
 */
#define TASK_ITERATOR_WHERE_COLUMNS                                         \
 {                                                                          \
   TASK_ITERATOR_WHERE_COLUMNS_INNER,                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @brief Task iterator columns.
 */
#define TASK_ITERATOR_COLUMNS                                               \
 {                                                                          \
   GET_ITERATOR_COLUMNS (tasks),                                            \
   TASK_ITERATOR_COLUMNS_INNER,                                             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @brief Task iterator minimal columns.
 */
#define TASK_ITERATOR_COLUMNS_MIN                                           \
 {                                                                          \
   GET_ITERATOR_COLUMNS (tasks),                                            \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @brief Task iterator minimal WHERE columns.
 */
#define TASK_ITERATOR_WHERE_COLUMNS_MIN                                     \
 {                                                                          \
   TASK_ITERATOR_COLUMNS_INNER,                                             \
   TASK_ITERATOR_WHERE_COLUMNS_INNER,                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @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.
 * @param[in]  ignore_severity  Whether to ignore severity data.
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
task_iterator_opts_table (int override, int min_qod, int ignore_severity)
{
  return g_strdup_printf (", (SELECT"
                          "   %d AS override,"
                          "   %d AS min_qod,"
                          "   %d AS ignore_severity)"
                          "  AS opts",
                          override,
                          min_qod,
                          ignore_severity);
}

/**
 * @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.
 * @param[in]  ignore_severity  Whether to ignore severity data.
 */
static void
init_user_task_iterator (iterator_t* iterator, int trash, int ignore_severity)
{
  static column_t select_columns[] = TASK_ITERATOR_COLUMNS;
  gchar *extra_tables;
  gchar *columns;

  extra_tables = task_iterator_opts_table (0, MIN_QOD_DEFAULT,
                                           ignore_severity);

  columns = columns_build_select (select_columns);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM tasks%s"
                 " WHERE " ACL_USER_OWNS ()
                 "%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;
  static column_t where_columns[] = TASK_ITERATOR_WHERE_COLUMNS;
  static column_t columns_min[] = TASK_ITERATOR_COLUMNS_MIN;
  static column_t where_columns_min[] = TASK_ITERATOR_WHERE_COLUMNS_MIN;
  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, 0);

  ret = init_get_iterator2 (iterator,
                            "task",
                            get,
                            /* SELECT columns. */
                            get->minimal ? columns_min : columns,
                            get->minimal ? columns_min : columns,
                            /* Filterable columns not in SELECT columns. */
                            get->minimal ? where_columns_min : where_columns,
                            get->minimal ? where_columns_min : where_columns,
                            filter_columns,
                            0,
                            extra_tables,
                            get->trash
                             ? " AND hidden = 2"
                             : " AND hidden = 0",
                            current_credentials.uuid ? TRUE : FALSE,
                            FALSE,
                            NULL);

  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 + 3);
}

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

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

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

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

/**
 * @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 Get the average duration of all finished reports of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Average scan duration in seconds.
 */
int
task_average_scan_duration (task_t task)
{
  return sql_int ("SELECT avg (end_time - start_time) FROM reports"
                  " WHERE task = %llu"
                  "   AND scan_run_status = %d"
                  "   AND coalesce (end_time, 0) != 0"
                  "   AND coalesce (start_time, 0) != 0;",
                  task, TASK_STATUS_DONE);
}

/**
 * @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)
{
  if (sql_is_open ())
    return;

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

  /* Attach the SCAP and CERT databases. */
  manage_attach_databases ();

  if (update_nvt_cache)
    {
      sql ("CREATE TEMPORARY TABLE old_nvts"
           " (oid TEXT, modification_time INTEGER);");
      sql ("INSERT INTO old_nvts (oid, modification_time)"
           " SELECT oid, modification_time FROM nvts;");
    }

  /* Define functions for SQL. */

  /* Lock to avoid an error return from Postgres when multiple processes
   * create a function at the same time. */
  sql_begin_exclusive ();
  if (manage_create_sql_functions ())
    {
      sql_rollback ();
      g_warning ("%s: failed to create functions", __FUNCTION__);
      abort ();
    }
  sql_commit ();
}

/**
 * @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', '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_iterator (&nvts,
                 "SELECT oid, name, family, cvss_base, cve, bid, xref, tag"
                 " FROM nvts;");
  while (next (&nvts))
    {
      nvti_t *nvti = nvti_new ();
      nvti_set_oid (nvti, iterator_string (&nvts, 0));
      nvti_set_name (nvti, iterator_string (&nvts, 1));
      nvti_set_family (nvti, iterator_string (&nvts, 2));
      nvti_set_cvss_base (nvti, iterator_string (&nvts, 3));
      nvti_set_cve (nvti, iterator_string (&nvts, 4));
      nvti_set_bid (nvti, iterator_string (&nvts, 5));
      nvti_set_xref (nvti, iterator_string (&nvts, 6));
      nvti_set_tag (nvti, iterator_string (&nvts, 7));
      nvtis_add (nvti_cache, nvti);
    }
  cleanup_iterator (&nvts);
}

/**
 * @brief Update the memory cache of NVTs, if this has been requested.
 *
 * @return 0 success, 1 failed to get lock, -1 error.
 */
int
manage_update_nvti_cache ()
{
  int ret;

  ret = sql_begin_immediate_giveup ();
  if (ret)
    return ret;
  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 ());
    }
  sql_commit ();
  return 0;
}

/**
 * @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 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)
    {
      sql ("INSERT INTO scanners"
           " (uuid, owner, name, host, port, type, ca_pub, credential,"
           "  creation_time, modification_time)"
           " VALUES ('" SCANNER_UUID_DEFAULT "', NULL, 'OpenVAS Default',"
           " '" OPENVASSD_ADDRESS "', 0, %d, NULL, NULL, m_now (), m_now ());",
           SCANNER_TYPE_OPENVAS);
    }

  if (sql_int ("SELECT count(*) FROM scanners WHERE uuid = '%s';",
               SCANNER_UUID_CVE) == 0)
    sql ("INSERT INTO scanners"
         " (uuid, owner, name, host, port, type, ca_pub, credential,"
         "  creation_time, modification_time)"
         " VALUES ('" SCANNER_UUID_CVE "', NULL, 'CVE',"
         " '', 0, %d, NULL, NULL, m_now (), m_now ());",
         SCANNER_TYPE_CVE);

  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 predefined 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 = predefined_report_format_dir (old);
  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 " ACL_IS_GLOBAL () ";")
      == 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 = '" SETTING_UUID_ROWS_PER_PAGE "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_ROWS_PER_PAGE "', NULL, 'Rows Per Page',"
         "  'The default number of rows displayed in any listing.',"
         "  10);");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_MAX_ROWS_PER_PAGE "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_MAX_ROWS_PER_PAGE "', NULL, 'Max Rows Per Page',"
         "  'The default maximum number of rows displayed in any listing.',"
         "  1000);");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = 'f16bb236-a32d-4cd5-a880-e0fcf2599f59'"
               " AND " ACL_IS_GLOBAL () ";")
      == 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 " ACL_IS_GLOBAL () ";")
      == 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 " ACL_IS_GLOBAL () ";")
      == 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 " ACL_IS_GLOBAL () ";")
      == 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 " ACL_IS_GLOBAL () ";")
      == 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 " ACL_IS_GLOBAL () ";")
      == 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');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '7eda49c5-096c-4bef-b1ab-d080d87300df'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('7eda49c5-096c-4bef-b1ab-d080d87300df', NULL,"
         "  'Default Severity',"
         "  'Severity to use if none is specified or available from SecInfo.',"
         "  '10.0');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = 'a09285b0-2d47-49b6-a4ef-946ee71f1d5c'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('a09285b0-2d47-49b6-a4ef-946ee71f1d5c', NULL,"
         "  'Auto Cache Rebuild',"
         "  'Whether to rebuild report caches on changes affecting severity.',"
         "  '1');");
}

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

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

  /* Service Detection NVTs were all being accidentally removed by the
   * checks below.  Recover them. */

  if (sql_int ("SELECT count (*) FROM nvt_selectors"
               " WHERE name = '" MANAGE_NVT_SELECTOR_UUID_DISCOVERY "'"
               " AND family = 'Service detection'")
      == 0)
    make_config_discovery_service_detection
     (MANAGE_NVT_SELECTOR_UUID_DISCOVERY);

  /* In the Service Detection family, NVTs sometimes move to Product
   * Detection, and once an NVT was removed.  So remove those NVTs
   * from Service Detection in the NVT selector. */

  sql ("DELETE FROM nvt_selectors"
       " WHERE name = '" MANAGE_NVT_SELECTOR_UUID_DISCOVERY "'"
       " AND family = 'Service detection'"
       " AND (SELECT family FROM nvts"
       "      WHERE oid = nvt_selectors.family_or_nvt)"
       "     = 'Product detection';");

  if (sql_int ("SELECT EXISTS (SELECT * FROM nvts);"))
    sql ("DELETE FROM nvt_selectors"
         " WHERE name = '" MANAGE_NVT_SELECTOR_UUID_DISCOVERY "'"
         " AND family = 'Service detection'"
         " AND NOT EXISTS (SELECT * FROM nvts"
         "                 WHERE oid = nvt_selectors.family_or_nvt);");

  check_config_discovery (CONFIG_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);

  check_config_system_discovery (CONFIG_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 ("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)
        {
          /* Old-style "global" report format.  I don't think this is possible
           * with any released version, so ignore. */
          continue;
        }
      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);
          g_debug ("   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;");

  return 0;
}

/**
 * @brief Check that trash report formats are correct.
 *
 * @return 0 success, -1 error.
 */
static int
check_db_trash_report_formats ()
{
  gchar *dir;
  struct stat state;

  dir = g_build_filename (OPENVAS_STATE_DIR,
                          "openvasmd",
                          "report_formats_trash",
                          NULL);

  if (g_lstat (dir, &state))
    {
      iterator_t report_formats;
      int count;

      if (errno != ENOENT)
        {
          g_warning ("%s: g_lstat (%s) failed: %s\n",
                     __FUNCTION__, dir, g_strerror (errno));
          g_free (dir);
          return -1;
        }

      /* Remove all trash report formats. */

      count = 0;
      init_iterator (&report_formats, "SELECT id FROM report_formats_trash;");
      while (next (&report_formats))
        {
          report_format_t report_format;

          report_format = iterator_int64 (&report_formats, 0);

          sql ("DELETE 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);

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

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

          count++;
        }
      cleanup_iterator (&report_formats);

      if (count)
        g_message ("Trash report format directory was missing."
                   " Removed all %i trash report formats.",
                   count);
    }

  g_free (dir);
  return 0;
}

/**
 * @brief Ensure the predefined report formats exist.
 *
 * @return 0 success, -1 error.
 */
static int
check_db_report_formats ()
{
  GError *error;
  GDir *dir;
  gchar *path;
  const gchar *report_format_path;
  iterator_t report_formats;

  if (check_db_trash_report_formats ())
    return -1;

  /* Bring report format UUIDs in database up to date. */
  update_report_format_uuids ();
  if (make_report_format_uuids_unique ())
    return -1;

  /* Open the global report format dir. */

  path = predefined_report_format_dir (NULL);

  dir = g_dir_open (path, 0, &error);
  if (dir == NULL)
    {
      g_warning ("%s: Failed to open directory '%s': %s",
                 __FUNCTION__, path, error->message);
      g_error_free (error);
      g_free (path);
      return -1;
    }
  g_free (path);

  /* Remember existing global report formats. */

  sql ("CREATE TEMPORARY TABLE report_formats_check"
       " AS SELECT id, uuid, name, owner, summary, description, extension,"
       "           content_type, signature, trust, trust_time, flags,"
       "           creation_time, modification_time"
       "    FROM report_formats"
       "    WHERE owner IS NULL;");

  sql ("CREATE TEMPORARY TABLE report_format_params_check"
       " AS SELECT id, name, report_format, type, value, type_min, type_max,"
       "           type_regex, fallback"
       "    FROM report_format_params"
       "    WHERE report_format IN (SELECT id FROM report_formats"
       "                            WHERE owner IS NULL);");

  /* Create or update global report formats from disk. */

  while ((report_format_path = g_dir_read_name (dir)))
    check_report_format (report_format_path);

  /* Remove previous global report formats that were not defined. */

  init_iterator (&report_formats,
                 "SELECT id, uuid, name FROM report_formats"
                 " WHERE uuid IN (SELECT uuid FROM report_formats_check)"
                 " AND (EXISTS (SELECT * FROM alert_method_data_trash"
                 "              WHERE data = report_formats.uuid"
                 "              AND (name = 'notice_attach_format'"
                 "                   OR name = 'notice_report_format'))"
                 "      OR EXISTS (SELECT * FROM alert_method_data"
                 "                 WHERE data = report_formats.uuid"
                 "                 AND (name = 'notice_attach_format'"
                 "                      OR name = 'notice_report_format')));");
  while (next (&report_formats))
    g_warning
     ("Removing old report format %s (%s) which is in use by an alert.\n"
      "Alert will fallback to TXT report format (%s), if TXT exists.",
      iterator_string (&report_formats, 2),
      iterator_string (&report_formats, 1),
      "a3810a62-1f62-11e1-9219-406186ea4fc5");
  cleanup_iterator (&report_formats);

  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 uuid IN (SELECT uuid"
       "                                    FROM report_formats_check)));");

  sql ("DELETE FROM report_format_params"
       " WHERE report_format IN (SELECT id FROM report_formats"
       "                         WHERE uuid IN (SELECT uuid"
       "                                        FROM report_formats_check));");

  sql ("DELETE FROM resources_predefined"
       " WHERE resource_type = 'report_format'"
       " AND resource IN (SELECT id FROM report_formats_check);");

  sql ("DELETE FROM report_formats"
       " WHERE uuid IN (SELECT uuid FROM report_formats_check);");

  /* Forget the old global report formats. */

  sql ("DROP TABLE report_format_params_check;");
  sql ("DROP TABLE report_formats_check;");

  return 0;
}

/**
 * @brief Get trash directory of a report format.
 *
 * @param[in]  report_format_id  UUID of report format.  NULL for the
 *             base dir that holds the report format trash.
 *
 * @return Freshly allocated trash dir.
 */
static gchar *
report_format_trash_dir (const gchar *report_format_id)
{
  if (report_format_id)
    return g_build_filename (OPENVAS_STATE_DIR,
                             "openvasmd",
                             "report_formats_trash",
                             report_format_id,
                             NULL);

  return g_build_filename (OPENVAS_STATE_DIR,
                           "openvasmd",
                           "report_formats_trash",
                           NULL);
}

/**
 * @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 = report_format_trash_dir (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)
    {
      /* 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");
    }

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

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

  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;

      /* 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_AUTH") == 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++;
        }
    }

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

  /* Migration from name LSC_CREDENTIAL to CREDENTIAL missed a case.  In
   * trunk this is done in migrate_185_to_186. */

  sql ("UPDATE permissions SET resource_type = 'credential'"
       " WHERE resource_type = 'lsc_credential';");

  sql ("UPDATE permissions_trash SET resource_type = 'credential'"
       " WHERE resource_type = 'lsc_credential';");
}

/**
 * @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 Cleanup the auth_cache table.
 */
static void
clean_auth_cache ()
{
  sql ("DELETE FROM auth_cache;");
}

/**
 * @brief Ensure that the database is in order.
 *
 * @param[in]  check_encryption_key  Whether to check encryption key.
 *
 * @return 0 success, -1 error.
 */
static int
check_db (int check_encryption_key)
{
  sql_begin_exclusive ();
  create_tables ();
  check_db_sequences ();
  if (progress)
    progress ();
  set_db_version (OPENVASMD_DATABASE_VERSION);
  check_db_nvts ();
  check_db_configs ();
  check_db_port_lists ();
  clean_auth_cache ();
  if (check_db_scanners ())
    goto fail;
  if (check_db_report_formats ())
    goto fail;
  if (check_db_report_formats_trash ())
    goto fail;
  check_db_roles ();
  check_db_permissions ();
  check_db_settings ();
  cleanup_schedule_times ();
  if (check_encryption_key && check_db_encryption_key ())
    goto fail;
  if (progress)
    progress ();

  sql_commit ();
  return 0;

 fail:
  sql_rollback ();
  return -1;
}

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

  /*
   * Remove permissions of deleted users, groups and roles.
   */
  sql ("DELETE FROM permissions"
       " WHERE (subject_type = 'user'"
       "        AND subject NOT IN (SELECT id FROM users))"
       "    OR (subject_type = 'group'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       "        AND subject NOT IN (SELECT id FROM groups))"
       "    OR (subject_type = 'group'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TRASH)
       "        AND subject NOT IN (SELECT id FROM groups_trash))"
       "    OR (subject_type = 'role'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       "        AND subject NOT IN (SELECT id FROM roles))"
       "    OR (subject_type = 'role'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TRASH)
       "        AND subject NOT IN (SELECT id FROM roles_trash));");

  sql ("DELETE FROM permissions_trash"
       " WHERE (subject_type = 'user'"
       "        AND subject NOT IN (SELECT id FROM users))"
       "    OR (subject_type = 'group'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       "        AND subject NOT IN (SELECT id FROM groups))"
       "    OR (subject_type = 'group'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TRASH)
       "        AND subject NOT IN (SELECT id FROM groups_trash))"
       "    OR (subject_type = 'role'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       "        AND subject NOT IN (SELECT id FROM roles))"
       "    OR (subject_type = 'role'"
       "        AND subject_location = " G_STRINGIFY (LOCATION_TRASH)
       "        AND subject NOT IN (SELECT id FROM roles_trash));");
}

/**
 * @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]  max_email_message_size     Max size of email user message text.
 * @param[in]  update_progress     Function to update progress, or NULL. *
 * @param[in]  stop_tasks          Stop any active tasks.
 * @param[in]  fork_connection     Function to fork a connection that will
 *                                 accept OMP requests.  Used to start tasks
 *                                 with OMP when an alert occurs.
 * @param[in]  skip_db_check       Skip DB check.
 * @param[in]  check_encryption_key  Check encryption key if doing DB check.
 *
 * @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,
                      int max_email_message_size,
                      void (*update_progress) (),
                      int stop_tasks,
                      int (*fork_connection)
                             (openvas_connection_t *, gchar *),
                      int skip_db_check,
                      int check_encryption_key)
{
  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
   *                 ...
   *                 event
   *                   fork_connection_for_event
   *                       fork one
   *                           init_ompd_process
   *                               init_manage_process
   *                           serve_client
   *                       fork two
   *                           omp_auth, omp_start_task_report.
   *                 ...
   *             manage_schedule
   *                 fork_connection_for_scheduler
   *                     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) */

  if ((max_ips_per_target <= 0)
      || (max_ips_per_target > MANAGE_ABSOLUTE_MAX_IPS_PER_TARGET))
    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;
  if (max_email_message_size)
    max_email_message_length = max_email_message_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.  */

  if (skip_db_check == 0)
    {
      ret = check_db (check_encryption_key);
      if (ret)
        return ret;

      cleanup_tables ();

      /* Set max_hosts in db, so database server side can access it. */

      sql ("DELETE FROM meta WHERE name = 'max_hosts';");
      sql ("INSERT INTO meta (name, value) VALUES ('max_hosts', %i);", max_hosts);
    }

  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);
  if (fork_connection)
    manage_fork_connection = fork_connection;
  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]  max_email_message_size     Max size of email user message text.
 * @param[in]  update_progress     Function to update progress, or NULL. *
 * @param[in]  fork_connection     Function to fork a connection that will
 *                                 accept OMP requests.  Used to start tasks
 *                                 with OMP when an alert occurs.
 * @param[in]  skip_db_check       Skip DB check.
 *
 * @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, int max_email_message_size,
             void (*update_progress) (),
             int (*fork_connection) (openvas_connection_t*, gchar*),
             int skip_db_check)
{
  return init_manage_internal (log_config,
                               nvt_cache_mode,
                               database,
                               max_ips_per_target,
                               max_email_attachment_size,
                               max_email_include_size,
                               max_email_message_size,
                               update_progress,
                               1,  /* Stop active tasks. */
                               fork_connection,
                               skip_db_check,
                               1); /* Check encryption key if checking db. */
}

/**
 * @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]  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. */
                               0,   /* Default max_email_message_size */
                               update_progress,
                               0,   /* Stop active tasks. */
                               NULL,
                               0,   /* Skip DB check. */
                               0);  /* Dummy. */
}

/**
 * @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_debug ("Received %s signal.\n", sys_siglist[signal]);
  if (sql_is_open ())
    {
      if (current_scanner_task)
        {
          g_warning ("%s: Error exit, setting running task to Internal Error",
                     __FUNCTION__);
          set_task_run_status (current_scanner_task, TASK_STATUS_INTERNAL_ERROR);
        }
      sql_close ();
    }
}

/**
 * @brief Cleanup as immediately as possible.
 */
void
manage_reset_currents ()
{
  current_report = 0;
  current_scanner_task = (task_t) 0;
}

/**
 * @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 *
user_uuid_method (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 Check whether LDAP is enabled.
 *
 * @return 0 no, else yes.
 */
static int
ldap_auth_enabled ()
{
  if (openvas_auth_ldap_enabled ())
    return sql_int ("SELECT coalesce ((SELECT CAST (value AS INTEGER) FROM meta"
                    "                  WHERE name = 'ldap_enable'),"
                    "                 0);");
  return 0;
}

/**
 * @brief Check whether RADIUS is enabled.
 *
 * @return 0 no, else yes.
 */
static int
radius_auth_enabled ()
{
  if (openvas_auth_radius_enabled ())
    return sql_int ("SELECT coalesce ((SELECT CAST (value AS INTEGER) FROM meta"
                    "                  WHERE name = 'radius_enable'),"
                    "                 0);");
  return 0;
}


/**
 * @brief Check if user exists.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 *
 * @return 1 yes, 0 no.
 */
static int
user_exists_method (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 Get user uuid, trying all authentication methods.
 *
 * @param[in]  name    User name.
 *
 * @return UUID.
 */
gchar *
user_uuid_any_method (const gchar *name)
{
  if (ldap_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT))
    return user_uuid_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT);
  if (radius_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT))
    return user_uuid_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT);
  if (user_exists_method (name, AUTHENTICATION_METHOD_FILE))
    return user_uuid_method (name, AUTHENTICATION_METHOD_FILE);
  return NULL;
}

/**
 * @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 Check if user exists.
 *
 * @param[in]  name    User name.
 *
 * @return 1 yes, 0 no.
 */
int
user_exists (const gchar *name)
{
  if (ldap_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT))
    return 1;
  if (radius_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT))
    return 1;
  return user_exists_method (name, AUTHENTICATION_METHOD_FILE);
}

/**
 * @brief Set the address of scanner to connect to.
 *
 * @param[in]  uuid     Scanner UUID.
 *
 * @return 0 if success, -1 if error, -2 scanner has no cert.
 */
int
manage_scanner_set (const char *uuid)
{
  scanner_t scanner = 0;
  char *host;
  int type, ret = 0;

  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);
  if (host && *host == '/')
    {
      /* XXX: Workaround for unix socket case. Should add a flag. */
      openvas_scanner_set_unix (host);
    }
  else
    {
      char *ca_pub, *key_pub, *key_priv;
      int port;

      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);
      ret = set_certs (ca_pub, key_pub, key_priv);
      g_free (ca_pub);
      g_free (key_pub);
      g_free (key_priv);
    }
  g_free (host);
  if (ret)
    return -2;
  return 0;
}

/**
 * @brief Set the default scanner as the scanner to connect to.
 *
 * @return 0 success, -1 error, -2 scanner has no cert.
 */
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"
                 : (acl_user_is_admin (credentials->uuid)
                     ? "Admin"
                     : (acl_user_is_observer (credentials->uuid)
                         ? "Observer"
                         : (acl_user_is_user (credentials->uuid)
                             ? "User"
                             : ""))));

  if (acl_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 " ACL_USER_OWNS ()
                  " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                  credentials->uuid);

  credentials->dynamic_severity
    = sql_int ("SELECT value FROM settings"
                " WHERE name = 'Dynamic Severity'"
                " AND " ACL_USER_OWNS ()
                " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                credentials->uuid);

  credentials->default_severity
    = sql_double ("SELECT value FROM settings"
                  " WHERE name = 'Default Severity'"
                  " AND " ACL_USER_OWNS ()
                  " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                  credentials->uuid);

  return 0;
}

/**
 * @brief Search for LDAP or RADIUS credentials in the recently-used
 * authentication cache.
 *
 * @param[in]  username     Username.
 * @param[in]  password     Password.
 * @param[in]  method       0 for LDAP, 1 for RADIUS.
 *
 * @return 0 on success, -1 on failure.
 */
static int
auth_cache_find (const char *username, const char *password, int method)
{
  char *hash, *quoted_username;
  int ret;

  quoted_username = sql_quote (username);
  hash = sql_string ("SELECT hash FROM auth_cache WHERE username = '%s'"
                     " AND method = %i AND creation_time >= m_now () - 300;",
                     quoted_username, method);
  g_free (quoted_username);
  if (!hash)
    return -1;

  ret = openvas_authenticate_classic (username, password, hash);
  g_free (hash);
  return ret;
}

/**
 * @brief Add LDAP or RADIUS credentials to the recently-used authentication
 * cache.
 *
 * @param[in]  username     Username.
 * @param[in]  password     Password.
 * @param[in]  method       0 for LDAP, 1 for RADIUS.
 */
static void
auth_cache_insert (const char *username, const char *password, int method)
{
  char *hash, *quoted_username;

  quoted_username = sql_quote (username);
  hash = get_password_hashes (GCRY_MD_MD5, password);
  sql ("INSERT INTO auth_cache (username, hash, method, creation_time)"
       " VALUES ('%s', '%s', %i, m_now ());", quoted_username, hash, method);
  /* Cleanup cache */
  sql ("DELETE FROM auth_cache WHERE creation_time < m_now () - 300");
}

/**
 * @brief Authenticate, trying any method.
 *
 * @param[in]  username     Username.
 * @param[in]  password     Password.
 * @param[out] auth_method  Auth method return.
 *
 * @return 0 authentication success, 1 authentication failure, 99 permission
 *         denied, -1 error.
 */
int
authenticate_any_method (const gchar *username, const gchar *password,
                         auth_method_t *auth_method)
{
  int ret;
  gchar *hash;

  if (openvas_auth_ldap_enabled ()
      && ldap_auth_enabled ()
      && user_exists_method (username, AUTHENTICATION_METHOD_LDAP_CONNECT))
    {
      ldap_auth_info_t info;
      int allow_plaintext;
      gchar *authdn, *host, *cacert;

      *auth_method = AUTHENTICATION_METHOD_LDAP_CONNECT;
      /* Search the LDAP authentication cache first. */
      if (auth_cache_find (username, password, 0) == 0)
        return 0;

      manage_get_ldap_info (NULL, &host, &authdn, &allow_plaintext, &cacert);
      info = ldap_auth_info_new (host, authdn, allow_plaintext);
      g_free (host);
      g_free (authdn);
      ret = ldap_connect_authenticate (username, password, info, cacert);
      ldap_auth_info_free (info);
      free (cacert);

      if (ret == 0)
        auth_cache_insert (username, password, 0);
      return ret;
    }
  if (openvas_auth_radius_enabled ()
      && radius_auth_enabled ()
      && user_exists_method (username, AUTHENTICATION_METHOD_RADIUS_CONNECT))
    {
      char *key = NULL, *host = NULL;

      *auth_method = AUTHENTICATION_METHOD_RADIUS_CONNECT;
      if (auth_cache_find (username, password, 1) == 0)
        return 0;

      manage_get_radius_info (NULL, &host, &key);
      ret = radius_authenticate (host, key, username, password);
      g_free (host);
      g_free (key);
      if (ret == 0)
        auth_cache_insert (username, password, 1);
      return ret;
    }
  *auth_method = AUTHENTICATION_METHOD_FILE;
  hash = manage_user_hash (username);
  ret = openvas_authenticate_classic (username, password, hash);
  g_free (hash);
  return ret;
}

/**
 * @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 when Manager makes a connection to itself, for
           * scheduled tasks and alerts.  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 = authenticate_any_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;

  const char *extra_where;

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

  if (strcmp (type, "task") == 0)
    {
      extra_where = (get->trash
                      ? " AND hidden = 2"
                      : " AND hidden = 0");
    }
  else if (strcmp (type, "report") == 0)
    {
      extra_where = " AND (SELECT hidden FROM tasks"
                    "      WHERE tasks.id = task)"
                    "     = 0";
    }
  else if (strcmp (type, "result") == 0)
    {
      extra_where = " AND (severity != " G_STRINGIFY (SEVERITY_ERROR) ")"
                    " AND (SELECT hidden FROM tasks"
                    "      WHERE tasks.id = task)"
                    "     = 0";
    }
  else
    extra_where = NULL;

  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,
                extra_where,
                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;
  static column_t where_columns[] = TASK_ITERATOR_WHERE_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, 0);

  ret = count2 ("task", get,
                columns,
                columns,
                where_columns,
                where_columns,
                extra_columns, 0,
                extra_tables,
                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 g_string_free (observers, FALSE);
}

/**
 * @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);
  if (scanner_type (scanner) == SCANNER_TYPE_CVE)
    sql ("UPDATE tasks SET config = 0 WHERE id = %llu;", task);
}

/**
 * @brief Return whether the scanner of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_scanner_in_trash (task_t task)
{
  return sql_int ("SELECT scanner_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 a report's scheduled flag.
 *
 * Set flag if task was scheduled, else clear flag.
 *
 * @param[in]   report  Report.
 */
void
set_report_scheduled (report_t report)
{
  if (authenticate_allow_all == 1)
    /* The task was scheduled. */
    sql ("UPDATE reports SET flags = 1 WHERE id = %llu;",
         report);
  else
    sql ("UPDATE reports SET flags = 0 WHERE id = %llu;",
         report);
}

/**
 * @brief Get a report's scheduled flag.
 *
 * @param[in]   report  Report.
 */
static int
report_scheduled (report_t report)
{
  return sql_int ("SELECT flags FROM reports WHERE id = %llu;",
                  report);
}

/**
 * @brief Set the run state of a task.
 *
 * @param[in]  task    Task.
 * @param[in]  status  New run status.
 */
static void
set_task_run_status_internal (task_t task, task_status_t status)
{
  if ((task == current_scanner_task) && current_report)
    {
      sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;",
           status,
           current_report);
      if (setting_auto_cache_rebuild_int ())
        report_cache_counts (current_report, 0, 0, NULL);
    }

  sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;",
       status,
       task);
}

/**
 * @brief Set the run state of a task.
 *
 * Logs and generates event.
 *
 * @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;

  set_task_run_status_internal (task, status);

  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,
         (task == current_scanner_task) ? current_report : 0,
         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_commit ();
      *status = run_status;
      return 1;
    }

  set_task_run_status (task, TASK_STATUS_REQUESTED);

  sql_commit ();

  *status = run_status;
  return 0;
}

/**
 * @brief Return number of results in a task.
 *
 * @param[in]  task     Task.
 * @param[in]  min_qod  Minimum QOD.
 *
 * @return Result count.
 */
int
task_result_count (task_t task, int min_qod)
{
  return sql_int ("SELECT count (*) FROM results"
                  " WHERE task = %llu"
                  " AND qod > %i"
                  " AND severity > " G_STRINGIFY (SEVERITY_ERROR) ";",
                  task,
                  min_qod);
}

/**
 * @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]  iterator  Iterator.
 *
 * @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
      || run_status == TASK_STATUS_INTERNAL_ERROR)
    {
      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"
                                     " 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,
                                     TASK_STATUS_INTERNAL_ERROR);
    }
  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;
      int using_sqlite = sql_is_sqlite3 ();
      get_data_t get;
      memset (&get, 0, sizeof (get_data_t));
      get.filter = g_strdup ("min_qod=0");
      count = result_count (&get, report, NULL);
      get_data_reset (&get);

      return sql_int ("SELECT"
                      " %s (%s (((%i * 100) / upload_result_count), 100), -1)"
                      " FROM tasks"
                      " WHERE id = %llu;",
                      using_sqlite ? "max" : "greatest",
                      using_sqlite ? "min" : "least",
                      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,"
       "                                         timezone)"
       "                       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,"
       "                                         timezone)"
       "                       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, given a UUID.
 *
 * 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 Set the schedule periods of a task, given an ID.
 *
 * The task modification time stays the same.
 *
 * @param[in]  task      Task UUID.
 * @param[in]  periods   Schedule periods.
 *
 * @return 0 success, -1 error.
 */
int
set_task_schedule_periods_id (task_t task, int periods)
{
  sql ("UPDATE tasks"
       " SET schedule_periods = %i"
       " WHERE id = %llu;",
       periods, task);
  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_id  ID of 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 (task_t task)
{
  int next_time;

  next_time = sql_int ("SELECT schedule_next_time FROM tasks"
                       " WHERE id = %llu;",
                       task);

  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_uuid (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)
{
  double severity;

  severity = task_severity_double (task, overrides, min_qod, offset);

  if (severity == SEVERITY_MISSING)
    return NULL;
  else
    return g_strdup_printf ("%0.1f", 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)
{
  report_t report;

  if (current_credentials.uuid == NULL
      || task_target (task) == 0 /* Container task. */)
    return SEVERITY_MISSING;

  sql_int64 (&report,
             "SELECT id FROM reports"
             "           WHERE reports.task = %llu"
             "           AND reports.scan_run_status = %u"
             "           ORDER BY reports.date DESC"
             "           LIMIT 1 OFFSET %d",
             task, TASK_STATUS_DONE, offset);

  return report_severity (report, overrides, min_qod);
}

/**
 * @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 (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 = user_uuid_any_method (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,"
       " modification_time = m_now ()"
       " WHERE schedule > 0"
       "%s"
       " AND (SELECT period FROM schedules WHERE schedules.id = schedule) = 0"
       " AND (SELECT period_months 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 next_time (first_time, period, period_months,"
     "                        timezone, -1)"
     "             + duration"
     "      FROM schedules"
     "      WHERE schedules.id = schedule)"
     "     < m_now ()";

  sql ("UPDATE tasks"
       " SET schedule = 0,"
       " schedule_next_time = 0,"
       " modification_time = m_now ()"
       " WHERE schedule > 0"
       "%s"
       " AND schedule_periods = 1"
       " AND ((SELECT period FROM schedules WHERE schedules.id = schedule) > 0"
       "      OR (SELECT period_months 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);
}

/**
 * @brief Auto delete reports.
 */
void
auto_delete_reports ()
{
  iterator_t tasks;

  g_debug ("%s", __FUNCTION__);

  if (sql_begin_exclusive_giveup ())
    return;

  init_iterator (&tasks,
                 "SELECT id, name,"
                 "       (SELECT value FROM task_preferences"
                 "        WHERE name = 'auto_delete_data'"
                 "        AND task = tasks.id)"
                 " FROM tasks"
                 " WHERE owner is NOT NULL"
                 " AND hidden = 0"
                 " AND EXISTS (SELECT * FROM task_preferences"
                 "             WHERE task = tasks.id"
                 "             AND name = 'auto_delete'"
                 "             AND value = 'keep');");
  while (next (&tasks))
    {
      task_t task;
      iterator_t reports;
      const char *keep_string;
      int keep;

      task = iterator_int64 (&tasks, 0);

      keep_string = iterator_string (&tasks, 2);
      if (keep_string == NULL)
        continue;
      keep = atoi (keep_string);
      if (keep < AUTO_DELETE_KEEP_MIN || keep > AUTO_DELETE_KEEP_MAX)
        continue;

      g_debug ("%s: %s (%i)", __FUNCTION__,
              iterator_string (&tasks, 1),
              keep);

      init_iterator (&reports,
                     "SELECT id FROM reports"
                     " WHERE task = %llu"
                     " AND start_time IS NOT NULL"
                     " AND start_time > 0"
                     " ORDER BY start_time DESC LIMIT %s OFFSET %i;",
                     task,
                     sql_select_limit (-1),
                     keep);
      while (next (&reports))
        {
          int ret;
          report_t report;

          report = iterator_int64 (&reports, 0);
          assert (report);

          g_debug ("%s: delete %llu", __FUNCTION__, report);
          ret = delete_report_internal (report);
          if (ret == 2)
            {
              /* Report is in use. */
              g_debug ("%s: %llu is in use", __FUNCTION__, report);
              continue;
            }
          if (ret)
            {
              g_warning ("%s: failed to delete %llu (%i)\n",
                         __FUNCTION__, report, ret);
              sql_rollback ();
            }
        }
      cleanup_iterator (&reports);
    }
  cleanup_iterator (&tasks);
  sql_commit ();
}


/**
 * @brief Get definitions file from a task's config.
 *
 * @param[in]  task  Task.
 *
 * @return Definitions file.
 */
char *
task_definitions_file (task_t task)
{
  assert (task);
  return sql_string ("SELECT value FROM config_preferences"
                     " WHERE name = 'definitions_file' AND config = %llu;",
                     task_config (task));
}

/**
 * @brief Set a task's schedule so that it runs again next scheduling round.
 *
 * @param  task_id  UUID of task.
 */
void
reschedule_task (const gchar *task_id)
{
  task_t task;
  task = 0;
  switch (sql_int64 (&task,
                     "SELECT id FROM tasks WHERE uuid = '%s'"
                     " AND hidden != 2;",
                     task_id))
    {
      case 0:
        g_warning ("%s: rescheduling task '%s'\n", __FUNCTION__, task_id);
        set_task_schedule_next_time (task, time (NULL) - 1);
        break;
      case 1:        /* Too few rows in result of query. */
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        break;
    }
}


/* 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 (acl_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.
 * @param[in]  qod          Quality of detection.
 *
 * @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, int qod)
{
  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);
              if (result_severity == NULL || strcmp (result_severity, "") == 0)
                {
                  g_free (result_severity);
                  result_severity
                    = g_strdup_printf ("%0.1f",
                                       setting_default_severity_dbl ());
                  g_debug ("%s: OSP CVE result without severity for '%s'",
                           __FUNCTION__, nvt);
                }
            }
          else
            {
              /*
              result_severity
                = g_strdup_printf ("%0.1f",
                                   setting_default_severity_dbl ());
              */
              g_warning ("%s: Non-CVE OSP result without severity for test %s",
                         __FUNCTION__, nvt ? nvt : "(unknown)");
              return 0;
            }
        }
    }
  else
    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',"
       "         %d, '', '%s', make_uuid ());",
       task, host ?: "", quoted_port, quoted_nvt, nvt_revision ?: "",
       result_severity ?: "0", type, qod, 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 Identify a host, given an identifier.
 *
 * Find a host which has an identifier of the same name and value, and
 * which has no identifiers of the same name and a different value.
 *
 * @param[in]  host_name         Host name.
 * @param[in]  identifier_name   Host identifier name.
 * @param[in]  identifier_value  Value of host identifier.
 * @param[in]  source_type       Source of identification: result.
 * @param[in]  source            Source identifier.
 *
 * @return Host if exists, else 0.
 */
host_t
host_identify (const char *host_name, const char *identifier_name,
               const char *identifier_value, const char *source_type,
               const char *source)
{
  host_t host;
  gchar *quoted_host_name, *quoted_identifier_name, *quoted_identifier_value;

  quoted_host_name = sql_quote (host_name);
  quoted_identifier_name = sql_quote (identifier_name);
  quoted_identifier_value = sql_quote (identifier_value);

  switch (sql_int64 (&host,
                     "SELECT id FROM hosts"
                     " WHERE name = '%s'"
                     " AND owner = (SELECT id FROM users"
                     "              WHERE uuid = '%s')"
                     " AND (EXISTS (SELECT * FROM host_identifiers"
                     "              WHERE host = hosts.id"
                     "              AND owner = (SELECT id FROM users"
                     "                           WHERE uuid = '%s')"
                     "              AND name = '%s'"
                     "              AND value = '%s')"
                     "      OR NOT EXISTS (SELECT * FROM host_identifiers"
                     "                     WHERE host = hosts.id"
                     "                     AND owner = (SELECT id FROM users"
                     "                                  WHERE uuid = '%s')"
                     "                     AND name = '%s'));",
                     quoted_host_name,
                     current_credentials.uuid,
                     current_credentials.uuid,
                     quoted_identifier_name,
                     quoted_identifier_value,
                     current_credentials.uuid,
                     quoted_identifier_name))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        host = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        host = 0;
        break;
    }


  g_free (quoted_host_name);
  g_free (quoted_identifier_name);
  g_free (quoted_identifier_value);

  return host;
}

/**
 * @brief Notice a host.
 *
 * When a host is detected during a scan, this makes the decision about which
 * asset host is used for the host, as decribed in \ref asset_rules.  This
 * decision is revised at the end of the scan by \ref hosts_set_identifiers if
 * there are any identifiers for the host.
 *
 * @param[in]  host_name         Name of host.
 * @param[in]  identifier_type   Type of host identifier.
 * @param[in]  identifier_value  Value of host identifier.
 * @param[in]  source_type       Type of source identifier
 * @param[in]  source_id         Source identifier.
 * @param[in]  check_add_to_assets  Whether to check the 'Add to Assets'
 *                                  task preference.
 * @param[in]  check_for_existing_identifier  Whether to check for an existing
 *                                            identifier like this one.  Used
 *                                            for slaves, which call this
 *                                            repeatedly.
 *
 * @return Host if existed, else 0.
 */
host_t
host_notice (const char *host_name, const char *identifier_type,
             const char *identifier_value, const char *source_type,
             const char *source_id, int check_add_to_assets,
             int check_for_existing_identifier)
{
  host_t host;
  gchar *quoted_identifier_value, *quoted_identifier_type, *quoted_source_type;
  gchar *quoted_source_id;

  /* Only add to assets if "Add to Assets" is set on the task. */
  if (check_add_to_assets
      && g_str_has_prefix (source_type, "Report")
      && sql_int ("SELECT value = 'no' FROM task_preferences"
                  " WHERE task = (SELECT task FROM reports WHERE uuid = '%s')"
                  " AND name = 'in_assets';",
                  source_id))
    return 0;

  host = host_identify (host_name, identifier_type, identifier_value,
                        source_type, source_id);
  if (host == 0)
    {
      gchar *quoted_host_name;
      quoted_host_name = sql_quote (host_name);
      sql ("INSERT into hosts"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " VALUES"
           " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', '',"
           "  m_now (), m_now ());",
           current_credentials.uuid,
           quoted_host_name);
      g_free (quoted_host_name);

      host = sql_last_insert_id ();
    }

  quoted_identifier_value = sql_quote (identifier_value);
  quoted_source_id = sql_quote (source_id);
  quoted_source_type = sql_quote (source_type);
  quoted_identifier_type = sql_quote (identifier_type);

  if (check_for_existing_identifier
      && sql_int ("SELECT EXISTS (SELECT * FROM host_identifiers"
                  "               WHERE host = %llu"
                  "               AND owner = (SELECT id FROM users WHERE uuid = '%s')"
                  "               AND name = '%s'"
                  "               AND value = '%s'"
                  "               AND source_type = '%s'"
                  "               AND source_id = '%s');",
                  host,
                  current_credentials.uuid,
                  quoted_identifier_type,
                  quoted_identifier_value,
                  quoted_source_type,
                  quoted_source_id))
    return 0;

  sql ("INSERT into host_identifiers"
       " (uuid, host, owner, name, comment, value, source_type, source_id,"
       "  source_data, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), %llu, (SELECT id FROM users WHERE uuid = '%s'), '%s',"
       "  '', '%s', '%s', '%s', '', m_now (), m_now ());",
       host,
       current_credentials.uuid,
       quoted_identifier_type,
       quoted_identifier_value,
       quoted_source_type,
       quoted_source_id);

  sql ("UPDATE hosts SET modification_time = (SELECT modification_time"
       "                                      FROM host_identifiers"
       "                                      WHERE id = %llu)"
       " WHERE id = %llu;",
       sql_last_insert_id (),
       host);

  g_free (quoted_identifier_type);
  g_free (quoted_identifier_value);
  g_free (quoted_source_id);
  g_free (quoted_source_type);

  return host;
}

/**
 * @brief Get a severity string from an nvt and result type.
 *
 * @param[in]  nvt_id   NVT oid.
 * @param[in]  type     Result type.
 *
 * @return A severity string, NULL if unknown type or no nvt id for Alarm type.
 */
char *
nvt_severity (const char *nvt_id, const char *type)
{
  char *severity = NULL;

  if (strcasecmp (type, "Alarm") == 0 && nvt_id)
    severity = sql_string ("SELECT coalesce(cvss_base, '0.0')"
                           " FROM nvts WHERE uuid = '%s';", nvt_id);
  else if (strcasecmp (type, "Alarm") == 0)
    g_warning ("%s result type requires an NVT", type);
  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 ("Invalid result nvt type %s", type);
  return severity;
}

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

      nvt_revision = sql_string ("SELECT version FROM nvts WHERE uuid = '%s';",
                                 nvt);
    }
  else
    {
      qod = QOD_DEFAULT;
      quoted_qod_type = g_strdup ("");
      nvt_revision = g_strdup ("");
    }
  severity = nvt_severity (nvt, type);
  if (!severity)
    return 0;

  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 Make a CVE result.
 *
 * @param[in]  task         The task associated with the result.
 * @param[in]  host         Host.
 * @param[in]  nvt          The OID of the NVT that produced the result.
 * @param[in]  cvss         CVSS base.
 * @param[in]  description  Description of the result.
 *
 * @return A result descriptor for the new result, 0 if error.
 */
result_t
make_cve_result (task_t task, const char* host, const char *nvt, double cvss,
                 const char* description)
{
  gchar *quoted_descr;
  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', '', '%1.1f', '%s',"
       "  '%s', make_uuid (), %i, '');",
       task, host ?: "", nvt, cvss, severity_to_type (cvss),
       quoted_descr, QOD_DEFAULT);

  g_free (quoted_descr);
  return sql_last_insert_id ();
}

/**
 * @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;
  gchar *quoted_location = 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;
  quoted_location = sql_quote (*location);

  *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 name != 'detected_at'"
                         " AND value = '%s';",
                         report, host, *oid, quoted_location);
  if (*product == NULL)
    goto detect_cleanup;

  if (g_str_has_prefix (*oid, "CVE-"))
    *name = g_strdup (*oid);
  else
    *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, quoted_location, quoted_location);
  if (*ref == NULL)
    goto detect_cleanup;

  g_free (report);
  g_free (host);
  g_free (quoted_location);

  return 0;

detect_cleanup:
  g_free (report);
  g_free (host);
  g_free (quoted_location);

  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 Get the location of an App for a report's host.
 *
 * @param[in]  report_host  Report host.
 * @param[in]  app          CPE.
 */
gchar *
app_location (report_host_t report_host, const gchar *app)
{
  gchar *quoted_app, *ret;

  assert (app);

  quoted_app = sql_quote (app);

  ret = sql_string ("SELECT value FROM report_host_details"
                    " WHERE report_host = %llu"
                    " AND name = '%s'"
                    " AND source_type = 'nvt'"
                    " AND source_name"
                    "     = (SELECT source_name FROM report_host_details"
                    "        WHERE report_host = %llu"
                    "        AND source_type = 'nvt'"
                    "        AND name = 'App'"
                    "        AND value = '%s');",
                    report_host,
                    quoted_app,
                    report_host,
                    quoted_app);

  g_free (quoted_app);

  return ret;
}

/**
 * @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 %s '%%%%%s%%%%'"
                              " OR cves.name %s '%%%%%s%%%%'"
                              " OR cpes.name %s '%%%%%s%%%%')",
                              sql_ilike_op (),
                              quoted_search_phrase,
                              sql_ilike_op (),
                              quoted_search_phrase,
                              sql_ilike_op (),
                              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 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 ((sort_field == NULL) || 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"
                            " order_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]  sort_order     Whether to sort in ascending order.
 * @param[in]  sort_field     Name of the field to sort by.
 */
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,
                              int sort_order, const char *sort_field)
{
  GString *phrase_sql, *order_sql;

  if (levels == NULL) levels = "hmlgdf";

  phrase_sql = prognosis_where_search_phrase (search_phrase);
  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"
                 " LIMIT %s OFFSET %i;",
                 report_host,
                 report_host,
                 phrase_sql ? phrase_sql->str : "",
                 prognosis_where_levels (levels),
                 order_sql ? order_sql->str : "",
                 sql_select_limit (max_results),
                 first_result);

  if (phrase_sql) g_string_free (phrase_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[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,
                                int *all, int *holes, int *infos, int *warnings)
{
  GString *phrase_sql;
  int host_holes, host_warnings, host_infos;

  phrase_sql = prognosis_where_search_phrase (search_phrase);

  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;",
                        report_host,
                        phrase_sql ? phrase_sql->str : "",
                        prognosis_where_levels ("h"));

  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;",
                           report_host,
                           phrase_sql ? phrase_sql->str : "",
                           prognosis_where_levels ("m"));

  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;",
                        report_host,
                        phrase_sql ? phrase_sql->str : "",
                        prognosis_where_levels ("l"));

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

/**
 * @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 Whether to ignore the Max Rows Per Page settings.
 */
int ignore_max_rows_per_page = 0;

/**
 * @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 Create a new GHashTable for containing resource rowids.
 *
 * @return The newly allocated GHashTable
 */
GHashTable *
new_resources_hashtable ()
{
  return g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, NULL);
}

/**
 * @brief Add reports affected by an override to an existing GHashtable.
 * This is used to add more reports to the hashtable from reports_for_override.
 *
 * @param[in]  reports_table The GHashtable to contain the report rowids.
 * @param[in]  override The override that selected reports must be affected by.
 */
void
reports_add_for_override (GHashTable *reports_table,
                          override_t override)
{
  result_t result;
  task_t task;
  gchar *nvt_id;
  iterator_t reports;

  if (override == 0)
    return;

  sql_int64 (&result,
             "SELECT result FROM overrides WHERE id = %llu",
             override);
  sql_int64 (&task,
             "SELECT task FROM overrides WHERE id = %llu",
             override);
  nvt_id = sql_string ("SELECT nvt FROM overrides WHERE id = %llu",
                       override);

  if (result)
    {
      report_t *report = g_malloc0 (sizeof (report_t));
      sql_int64 (report,
                 "SELECT report FROM results"
                 " WHERE id = %llu AND nvt = '%s'",
                 result, nvt_id);

      if (*report)
        g_hash_table_add (reports_table, report);
      else
        g_free (report);

      return;
    }
  else if (task)
    {
      init_iterator (&reports,
                     "SELECT DISTINCT report FROM results"
                     " WHERE task = %llu AND nvt = '%s'",
                     task, nvt_id);
    }
  else
    {
      init_iterator (&reports,
                     "SELECT DISTINCT report FROM results"
                     " WHERE nvt = '%s'",
                     nvt_id);
    }

  while (next (&reports))
    {
      report_t *report = g_malloc0 (sizeof (report_t));

      *report = iterator_int64 (&reports, 0);

      if (g_hash_table_contains (reports_table, report) == 0)
        g_hash_table_add (reports_table, report);
      else
        g_free (report);
    }
  cleanup_iterator (&reports);
}

/**
 * @brief Get reports affected by an override in a GHashTable.
 *
 * @param[in]  override The override that selected reports must be affected by.
 *
 * @return A GHashtable containing the affected report rowids.
 */
GHashTable *
reports_for_override (override_t override)
{
  GHashTable *reports_table;
  reports_table = new_resources_hashtable ();

  reports_add_for_override (reports_table, override);

  return reports_table;
}

/**
 * @brief Add all reports to an existing GHashtable.
 *
 * @param[in]  reports_table The GHashtable to contain the report rowids.
 */
void
reports_add_all (GHashTable *reports_table)
{
  iterator_t reports;

  init_iterator (&reports,
                 "SELECT id FROM reports");

  while (next (&reports))
    {
      report_t *report = g_malloc0 (sizeof (report_t));

      *report = iterator_int64 (&reports, 0);

      if (g_hash_table_contains (reports_table, report) == 0)
        g_hash_table_add (reports_table, report);
      else
        g_free (report);
    }

  cleanup_iterator (&reports);
}

/**
 * @brief Get all reports in a GHashTable.
 *
 * @return A GHashtable containing the report rowids.
 */
GHashTable *
reports_hashtable ()
{
  GHashTable *reports_table;
  reports_table = new_resources_hashtable ();

  reports_add_all (reports_table);

  return reports_table;
}

/**
 * @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;
  iterator_t reports;
  changes = 0;

  init_iterator (&reports, "SELECT id FROM reports");

  while (next (&reports))
    {
      report_t report = iterator_int64 (&reports, 0);

      report_cache_counts (report, clear, clear, NULL);
      changes ++;
    }

  cleanup_iterator (&reports);

  if (changes_out)
    *changes_out = changes;
}

/**
 * @brief Initializes an iterator for updating the report cache
 *
 * @param[in]  iterator       Iterator.
 * @param[in]  report         Report to select.
 * @param[in]  min_qod_limit  Limit for min_qod.
 * @param[in]  add_defaults   Whether to add default values.
 * @param[in]  users_where    Optional SQL clause to limit users.
 */
void
init_report_counts_build_iterator (iterator_t *iterator, report_t report,
                                   int min_qod_limit, int add_defaults,
                                   const char *users_where)
{
  gchar *report_id, *users_string;

  report_id = sql_string ("SELECT uuid FROM reports WHERE id = %llu;", report);

  users_string = acl_users_with_access_sql ("report", report_id, users_where);
  if (users_string == NULL)
    {
      init_iterator (iterator, "SELECT NULL WHERE NOT t();");
      g_free (report_id);
      return;
    }

  if (add_defaults && MIN_QOD_DEFAULT <= min_qod_limit)
    {
      init_iterator (iterator,
                     "SELECT * FROM"
                     " (WITH users_with_access (\"user\") AS %s"
                     "  SELECT DISTINCT min_qod, override, \"user\""
                     "  FROM report_counts"
                     "  WHERE report = %llu"
                     "    AND \"user\" IN (SELECT \"user\""
                     "                     FROM users_with_access)"
                     "    AND min_qod <= %d"
                     "  UNION SELECT 0 as min_qod, 0, \"user\""
                     "          FROM users_with_access"
                     "  UNION SELECT 0 as min_qod, 1, \"user\""
                     "          FROM users_with_access"
                     "  UNION SELECT %d as min_qod, 0, \"user\""
                     "          FROM users_with_access"
                     "  UNION SELECT %d as min_qod, 1, \"user\""
                     "          FROM users_with_access) AS inner_query"
                     " ORDER BY \"user\"",
                     users_string,
                     report,
                     min_qod_limit,
                     MIN_QOD_DEFAULT,
                     MIN_QOD_DEFAULT);
    }
  else if (add_defaults)
    {
      init_iterator (iterator,
                     "SELECT * FROM"
                     " (WITH users_with_access (\"user\") AS %s"
                     "  SELECT DISTINCT min_qod, override, \"user\""
                     "  FROM report_counts"
                     "  WHERE report = %llu"
                     "    AND min_qod <= %d"
                     "    AND \"user\" IN (SELECT \"user\""
                     "                     FROM users_with_access)"
                     "  UNION SELECT 0 as min_qod, 0, \"user\""
                     "          FROM users_with_access"
                     "  UNION SELECT 0 as min_qod, 1, \"user\""
                     "          FROM users_with_access) AS inner_query"
                     " ORDER BY \"user\"",
                     users_string,
                     report,
                     min_qod_limit);
    }
  else
    {
      init_iterator (iterator,
                     "WITH users_with_access (\"user\") AS %s"
                     " SELECT DISTINCT min_qod, override, \"user\""
                     " FROM report_counts"
                     " WHERE report = %llu"
                     "   AND min_qod <= %d"
                     "   AND \"user\" IN (SELECT \"user\""
                     "                    FROM users_with_access)"
                     " ORDER BY \"user\"",
                     users_string,
                     report,
                     min_qod_limit);
    }
  g_free (users_string);
  g_free (report_id);
}

/**
 * @brief Get the min_qod from a report_counts build iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The min_qod.
 */
int
report_counts_build_iterator_min_qod (iterator_t *iterator)
{
  return iterator_int (iterator, 0);
}

/**
 * @brief Get the override flag from a report_counts build iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether the report counts are using overrides.
 */
int
report_counts_build_iterator_override (iterator_t *iterator)
{
  return iterator_int (iterator, 1);
}

/**
 * @brief Get the user from a report_counts build iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The min_qod.
 */
user_t
report_counts_build_iterator_user (iterator_t *iterator)
{
  return iterator_int64 (iterator, 2);
}

/**
 * @brief Cache report counts and clear existing caches if requested.
 *
 * If queue_rebuild is true, the cache will not be rebuilt immediately, but
 *  the rebuild will be added to a queue to be handled in a separate process.
 *
 * @param[in]  report             Report to cache counts of.
 * @param[in]  clear_original     Whether to clear existing cache for
 *                                 original severity.
 * @param[in]  clear_overridden   Whether to clear existing cache for
 *                                 overridden severity.
 * @param[in]  users_where        Optional SQL clause to limit users.
 */
void
report_cache_counts (report_t report, int clear_original, int clear_overridden,
                     const char* users_where)
{
  iterator_t cache_iterator;
  int debugs, holes, infos, logs, warnings, false_positives;
  double severity;
  get_data_t *get = NULL;
  gchar *old_user_id;

  old_user_id = current_credentials.uuid;
  init_report_counts_build_iterator (&cache_iterator, report, INT_MAX, 1,
                                     users_where);

  while (next (&cache_iterator))
    {
      int override = report_counts_build_iterator_override (&cache_iterator);
      int min_qod = report_counts_build_iterator_min_qod (&cache_iterator);
      user_t user = report_counts_build_iterator_user (&cache_iterator);

      current_credentials.uuid
        = sql_string ("SELECT uuid FROM users WHERE id = %llu",
                      user);
      manage_session_init (current_credentials.uuid);

      get = report_results_get_data (1, -1, override, 0, min_qod);

      if ((clear_original && override == 0) || (clear_overridden && override))
        {
          sql ("DELETE FROM report_counts"
               " WHERE report = %llu"
               "   AND \"user\" = %llu"
               "   AND override = %d"
               "   AND min_qod = %d",
               report, user, override, min_qod);
        }

      report_counts_id (report, &debugs, &holes, &infos, &logs, &warnings,
                        &false_positives, &severity, get, NULL);

      get_data_reset (get);
      g_free (get);
      g_free (current_credentials.uuid);
    }
  cleanup_iterator (&cache_iterator);
  current_credentials.uuid = old_user_id;
  manage_session_init (current_credentials.uuid);
}

/**
 * @brief Clear report counts .
 *
 * @param[in]  report  Report.
 * @param[in]  clear_original     Whether to clear existing cache for
 *                                 original severity.
 * @param[in]  clear_overridden   Whether to clear existing cache for
 *                                 overridden severity.
 * @param[in]  users_where        Optional SQL clause to limit users.
 */
void
report_clear_count_cache (report_t report,
                          int clear_original, int clear_overridden,
                          const char* users_where)
{
  gchar *extra_where = NULL;
  if (users_where)
    {
      extra_where 
        = g_strdup_printf (" AND \"user\" IN (SELECT id FROM users WHERE %s)",
                           users_where);
    }

  if (clear_original && clear_overridden)
    {
      sql ("DELETE FROM report_counts"
           " WHERE report = %llu"
           "%s",
           report,
           extra_where ? extra_where : "");
    }
  else if (clear_original || clear_overridden)
    {
      int override = clear_overridden ? 1 : 0;
      sql ("DELETE FROM report_counts"
           " WHERE report = %llu"
           "   AND override = %d"
           "%s",
           report,
           override,
           extra_where ? extra_where : "");
    }
}

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

  set_report_scheduled (current_report);

  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 Insert a host detail into a report.
 *
 * @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]   value       The detail's value.
 */
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 Number of results per transaction, when uploading report.
 */
#define CREATE_REPORT_CHUNK_SIZE 10

/**
 * @brief Number of microseconds to sleep between insert chunks.
 */
#define CREATE_REPORT_CHUNK_SLEEP 1000

/**
 * @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]   in_assets     Whether to create assets from the report.
 * @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, -6 permission to create assets denied.
 */
int
create_report (array_t *results, const char *task_id, const char *task_name,
               const char *task_comment, const char *in_assets,
               const char *scan_start, const char *scan_end,
               array_t *host_starts, array_t *host_ends, array_t *details,
               char **report_id)
{
  int index, in_assets_int, count;
  create_report_result_t *result, *end, *start;
  report_t report;
  user_t owner;
  task_t task;
  pid_t pid;
  host_detail_t *detail;

  in_assets_int
    = (in_assets && strcmp (in_assets, "") && strcmp (in_assets, "0"));

  if (in_assets_int && acl_user_may ("create_asset") == 0)
    return -6;

  g_debug ("%s", __FUNCTION__);

  if (acl_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,
                      1,  /* Include in assets. */
                      1); /* Log and generate event. */

  /* 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. */
        g_debug ("%s: %i forked %i", __FUNCTION__, getpid (), pid);
        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 ();
  g_debug ("%s: add hosts", __FUNCTION__);
  index = 0;
  count = 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);

  g_debug ("%s: add results", __FUNCTION__);
  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;

      g_debug ("%s: add results: index: %i", __FUNCTION__, index);

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

      count++;
      if (count == CREATE_REPORT_CHUNK_SIZE)
        {
          sql_commit ();
          openvas_usleep (CREATE_REPORT_CHUNK_SLEEP);
          sql_begin_immediate ();
          count = 0;
        }
    }

  g_debug ("%s: add host ends", __FUNCTION__);
  index = 0;
  count = 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);

        count++;
        if (count == CREATE_REPORT_CHUNK_SIZE)
          {
            sql_commit ();
            openvas_usleep (CREATE_REPORT_CHUNK_SLEEP);
            sql_begin_immediate ();
            count = 0;
          }
      }

  g_debug ("%s: add host details", __FUNCTION__);
  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;

  if (in_assets_int)
    {
      create_asset_report (*report_id, "");
    }

  exit (EXIT_SUCCESS);
  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 port of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave port.
 */
char*
report_slave_port (report_t report)
{
  return sql_string ("SELECT slave_port FROM reports WHERE id = %llu;",
                     report);
}

/**
 * @brief Return the port of a report's slave.
 *
 * @param[in]  report  Report.
 *
 * @return Slave port.
 */
int
report_slave_port_int (report_t report)
{
  return sql_int ("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)
{
  double severity, ov_severity;
  int qod;
  rowid_t rowid;
  iterator_t cache_iterator;
  user_t previous_user = 0;

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

  severity = sql_double ("SELECT severity FROM results WHERE id = %llu;",
                         result);
  ov_severity = severity;

  init_report_counts_build_iterator (&cache_iterator, report, qod, 1, NULL);
  while (next (&cache_iterator))
    {
      int min_qod = report_counts_build_iterator_min_qod (&cache_iterator);
      int override = report_counts_build_iterator_override (&cache_iterator);
      user_t user = report_counts_build_iterator_user (&cache_iterator);

      if (override && user != previous_user)
        {
          char *ov_severity_str;
          gchar *owned_clause;

          owned_clause = acl_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;

          free (ov_severity_str);

          previous_user = user;
        }

      rowid = 0;
      sql_int64 (&rowid,
                 "SELECT id FROM report_counts"
                 " WHERE report = %llu"
                 " AND \"user\" = %llu"
                 " AND override = %d"
                 " AND severity = %1.1f"
                 " AND min_qod = %d",
                 report, user, override,
                 override ? ov_severity : severity,
                 min_qod);
      if (rowid)
        sql ("UPDATE report_counts"
            " SET count = count + 1"
            " WHERE id = %llu;",
            rowid);
      else
        sql ("INSERT INTO report_counts"
             " (report, \"user\", override, min_qod, severity, count, end_time)"
             " VALUES"
             " (%llu, %llu, %d, %d, %1.1f, 1, 0);",
             report, user, override,
             override ? ov_severity : severity,
             min_qod);

    }
  cleanup_iterator (&cache_iterator);

  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",       \
   "hosts", "result_hosts", "fp_per_host", "log_per_host", "low_per_host",     \
   "medium_per_host", "high_per_host", "duration", "duration_per_host",        \
   NULL }

/**
 * @brief Report iterator columns.
 */
#define REPORT_ITERATOR_COLUMNS                                              \
 {                                                                           \
   { "id", NULL, KEYWORD_TYPE_INTEGER },                                     \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "iso_time (start_time)", "name", KEYWORD_TYPE_STRING },                 \
   { "''", NULL, KEYWORD_TYPE_STRING },                                      \
   { "iso_time (start_time)", NULL, KEYWORD_TYPE_STRING },                   \
   { "iso_time (end_time)", NULL, KEYWORD_TYPE_STRING },                     \
   { "start_time", "created", KEYWORD_TYPE_INTEGER },                        \
   { "end_time", "modified", KEYWORD_TYPE_INTEGER },                         \
   { "(SELECT name FROM users WHERE users.id = reports.owner)",              \
     "_owner",                                                               \
     KEYWORD_TYPE_STRING },                                                  \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                                  \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Report iterator columns.
 */
#define REPORT_ITERATOR_WHERE_COLUMNS                                        \
 {                                                                           \
   { "run_status_name (scan_run_status)", "status", KEYWORD_TYPE_STRING },   \
   {                                                                         \
     "(SELECT uuid FROM tasks WHERE tasks.id = task)",                       \
     "task_id",                                                              \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { "date", NULL, KEYWORD_TYPE_INTEGER },                                   \
   { "(SELECT name FROM tasks WHERE tasks.id = task)", "task" },             \
   {                                                                         \
     "report_severity (id, opts.override, opts.min_qod)",                    \
     "severity",                                                             \
     KEYWORD_TYPE_DOUBLE                                                     \
   },                                                                        \
   {                                                                         \
     "(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",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod,"               \
     "                       'False Positive')",                             \
     "false_positive",                                                       \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod, 'Log')",       \
     "log",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod, 'Low')",       \
     "low",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod, 'Medium')",    \
     "medium",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod, 'High')",      \
     "high",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT name FROM users WHERE users.id = reports.owner)",              \
     "_owner",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "report_host_count (id)",                                               \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_result_host_count (id, opts.min_qod)",                          \
     "result_hosts",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'False Positive') * 1.0"              \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "fp_per_host",                                                          \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Log') * 1.0"                         \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "log_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Low') * 1.0"                         \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "low_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Medium') * 1.0"                      \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "medium_per_host",                                                      \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'High') * 1.0"                        \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "high_per_host",                                                        \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(CASE WHEN (start_time IS NULL or end_time IS NULL)"                   \
     " THEN NULL ELSE end_time - start_time END)",                           \
     "duration",                                                             \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(CASE WHEN (start_time IS NULL or end_time IS NULL"                    \
     "            or report_result_host_count (id, opts.min_qod) = 0)"       \
     " THEN NULL"                                                            \
     " ELSE (end_time - start_time)"                                         \
     "        / report_result_host_count (id, opts.min_qod) END)",           \
     "duration_per_host",                                                    \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @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;
  static column_t where_columns[] = REPORT_ITERATOR_WHERE_COLUMNS;
  gchar *extra_tables;
  int ret;

  extra_tables = report_iterator_opts_table (0, MIN_QOD_DEFAULT);

  ret = count2 ("report", get, columns, NULL, where_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;
  static column_t where_columns[] = REPORT_ITERATOR_WHERE_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_iterator2 (iterator,
                            "report",
                            get,
                            /* Columns. */
                            columns,
                            NULL,
                            /* Filterable columns not in SELECT columns. */
                            where_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,
                            FALSE,
                            NULL);
  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++;
    }

  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 ("");
      /* It's not possible to override from or to the error severity, so no
       * need to use the overriden severity here (new_severity_sql).  This
       * helps with the default result counting performance because the
       * overridden severity is complex. */
      g_string_append_printf (levels_sql,
                              " AND severity != " G_STRINGIFY (SEVERITY_ERROR));
    }

  return levels_sql;
}

/**
 * @brief Return SQL WHERE for restricting a SELECT to a minimum QoD.
 *
 * @param[in]  min_qod  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;

      if (atoi (min_qod) == 0)
        return g_strdup ("");

      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 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", "hostname", NULL }

// TODO Combine with RESULT_ITERATOR_COLUMNS.
/**
 * @brief Result iterator filterable columns, for severity only version .
 */
#define RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE                           \
  {                                                                           \
    { "id", NULL, KEYWORD_TYPE_INTEGER },                                     \
    { "uuid", NULL, KEYWORD_TYPE_STRING },                                    \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "name",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "''", "comment", KEYWORD_TYPE_STRING },                                 \
    { " iso_time ( date )", "creation_time", KEYWORD_TYPE_STRING },           \
    { " iso_time ( date )", "modification_time", KEYWORD_TYPE_STRING },       \
    { "date", "created", KEYWORD_TYPE_INTEGER },                              \
    { "date", "modified", KEYWORD_TYPE_INTEGER },                             \
    { "(SELECT name FROM users WHERE users.id = results.owner)",              \
      "_owner",                                                               \
      KEYWORD_TYPE_STRING },                                                  \
    { "owner", NULL, KEYWORD_TYPE_INTEGER },                                  \
    { "host", NULL, KEYWORD_TYPE_STRING },                                    \
    { "port", "location", KEYWORD_TYPE_STRING },                              \
    { "nvt", NULL, KEYWORD_TYPE_STRING },                                     \
    { "severity_to_type (severity)", "original_type", KEYWORD_TYPE_STRING },  \
    { "'Log Message'", /* Adjusted by init_result_get_iterator_severity. */   \
      "type",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "description", NULL, KEYWORD_TYPE_STRING },                             \
    { "task", NULL, KEYWORD_TYPE_INTEGER },                                   \
    { "report", "report_rowid", KEYWORD_TYPE_INTEGER },                       \
    { "(SELECT cvss_base FROM nvts WHERE nvts.oid =  nvt)",                   \
      "cvss_base",                                                            \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "nvt_version", NULL, KEYWORD_TYPE_STRING },                             \
    { "severity", "original_severity", KEYWORD_TYPE_DOUBLE },                 \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "vulnerability",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "date" , NULL, KEYWORD_TYPE_INTEGER },                                  \
    { "(SELECT uuid FROM reports WHERE id = report)",                         \
      "report_id",                                                            \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT solution_type FROM nvts WHERE nvts.oid = nvt)",                \
      "solution_type",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "qod", NULL, KEYWORD_TYPE_INTEGER },                                    \
    { "qod_type", NULL, KEYWORD_TYPE_STRING },                                \
    { "qod_type", NULL, KEYWORD_TYPE_STRING },                                \
    { "(SELECT uuid FROM tasks WHERE id = task)",                             \
      "task_id",                                                              \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT cve FROM nvts WHERE oid = nvt)", "cve", KEYWORD_TYPE_STRING }, \
    { "(SELECT value FROM report_host_details"                                \
      " WHERE name = 'hostname'"                                              \
      "   AND report_host = (SELECT id FROM report_hosts"                     \
      "                       WHERE report_hosts.host=results.host"           \
      "                         AND report_hosts.report = results.report)"    \
      " LIMIT 1)",                                                            \
      "hostname",                                                             \
      KEYWORD_TYPE_STRING                                                     \
    },                                                                        \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Result iterator columns.
 */
#define RESULT_ITERATOR_COLUMNS                                               \
  {                                                                           \
    { "id", NULL, KEYWORD_TYPE_INTEGER },                                     \
    { "uuid", NULL, KEYWORD_TYPE_STRING },                                    \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "name",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "''", "comment", KEYWORD_TYPE_STRING },                                 \
    { " iso_time ( date )", "creation_time", KEYWORD_TYPE_STRING },           \
    { " iso_time ( date )", "modification_time", KEYWORD_TYPE_STRING },       \
    { "date", "created", KEYWORD_TYPE_INTEGER },                              \
    { "date", "modified", KEYWORD_TYPE_INTEGER },                             \
    { "(SELECT name FROM users WHERE users.id = results.owner)",              \
      "_owner",                                                               \
      KEYWORD_TYPE_STRING },                                                  \
    { "owner", NULL, KEYWORD_TYPE_INTEGER },                                  \
    { "host", NULL, KEYWORD_TYPE_STRING },                                    \
    { "port", "location", KEYWORD_TYPE_STRING },                              \
    { "nvt", NULL, KEYWORD_TYPE_STRING },                                     \
    { "severity_to_type (severity)", "original_type", KEYWORD_TYPE_STRING },  \
    { "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",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT autofp FROM results_autofp"                                    \
      " WHERE (result = results.id) AND (autofp_selection = opts.autofp))",   \
      "auto_type",                                                            \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "description", NULL, KEYWORD_TYPE_STRING },                             \
    { "task", NULL, KEYWORD_TYPE_INTEGER },                                   \
    { "report", "report_rowid", KEYWORD_TYPE_INTEGER },                       \
    { "(SELECT cvss_base FROM nvts WHERE nvts.oid =  nvt)",                   \
      "cvss_base",                                                            \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "nvt_version", NULL, KEYWORD_TYPE_STRING },                             \
    { "severity", "original_severity", KEYWORD_TYPE_DOUBLE },                 \
    { "(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",                                                             \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "vulnerability",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "date" , NULL, KEYWORD_TYPE_INTEGER },                                  \
    { "(SELECT uuid FROM reports WHERE id = report)",                         \
      "report_id",                                                            \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT solution_type FROM nvts WHERE nvts.oid = nvt)",                \
      "solution_type",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "qod", NULL, KEYWORD_TYPE_INTEGER },                                    \
    { "qod_type", NULL, KEYWORD_TYPE_STRING },                                \
    { "qod_type", NULL, KEYWORD_TYPE_STRING },                                \
    { "(SELECT uuid FROM tasks WHERE id = task)",                             \
      "task_id",                                                              \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT cve FROM nvts WHERE oid = nvt)", "cve", KEYWORD_TYPE_STRING }, \
    { "(SELECT value FROM report_host_details"                                \
      " WHERE name = 'hostname'"                                              \
      "   AND report_host = (SELECT id FROM report_hosts"                     \
      "                       WHERE report_hosts.host=results.host"           \
      "                         AND report_hosts.report = results.report)"    \
      " LIMIT 1)",                                                            \
      "hostname",                                                             \
      KEYWORD_TYPE_STRING                                                     \
    },                                                                        \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @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 Get extra_where string for a result iterator or count.
 *
 * @param[in]  get              GET data.
 * @param[in]  report           Report to restrict returned results to.
 * @param[in]  host             Host to restrict returned results to.
 * @param[in]  autofp           Whether to apply auto FP filter.
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  dynamic_severity Whether to use dynamic severity.
 * @param[in]  filter           Filter string.
 *
 * @return     Newly allocated extra_where string.
 */
static gchar*
results_extra_where (const get_data_t *get, report_t report, const gchar* host,
                     int autofp, int apply_overrides, int dynamic_severity,
                     const gchar *filter)
{
  gchar *extra_where;
  gchar *levels, *min_qod;
  gchar *report_clause, *host_clause, *min_qod_clause;
  GString *levels_clause;
  gchar *new_severity_sql, *auto_type_sql;

  // Get filter values
  min_qod = filter_term_value (filter, "min_qod");

  levels = filter_term_value (filter, "levels");
  if (levels == NULL)
    levels = g_strdup ("hmlgdf");

  // Build clause fragments

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

  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,
                      apply_overrides,
                      dynamic_severity);

  // Build filter clauses

  report_clause = report ? g_strdup_printf (" AND (report = %llu) ", report)
                         : NULL;

  if (host)
    {
      gchar *quoted_host = sql_quote (host);
      host_clause = g_strdup_printf (" AND (host = '%s') ", quoted_host);
      g_free (quoted_host);
    }
  else
    host_clause = NULL;

  min_qod_clause = where_qod (min_qod);
  g_free (min_qod);

  levels_clause = where_levels_auto (levels ? levels : "hmlgdf",
                                     new_severity_sql, auto_type_sql);
  g_free (levels);
  g_free (new_severity_sql);

  extra_where = g_strdup_printf("%s%s%s%s%s",
                                report_clause ? report_clause : "",
                                host_clause ? host_clause : "",
                                levels_clause->str,
                                min_qod_clause ? min_qod_clause : "",
                                report_clause
                                 ? ""
                                 : get->trash
                                    ? " AND ((SELECT (hidden = 2) FROM tasks"
                                      "       WHERE tasks.id = task))"
                                    : " AND ((SELECT (hidden = 0) FROM tasks"
                                      "       WHERE tasks.id = task))");

  g_free (auto_type_sql);
  g_free (min_qod_clause);
  g_string_free (levels_clause, TRUE);
  g_free (report_clause);
  g_free (host_clause);

  return extra_where;
}

/**
 * @brief Initialise the severity-only result iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 * @param[in]  report      Report to restrict returned results to.
 * @param[in]  host        Host to limit results to.
 * @param[in]  extra_order Extra text for ORDER term in SQL.
 *
 * @return 0 success, 1 failed to find result, failed to find filter (filt_id),
 *         -1 error.
 */
int
init_result_get_iterator_severity (iterator_t* iterator, const get_data_t *get,
                                   report_t report, const char* host,
                                   const gchar *extra_order)
{
  column_t columns[3];
  static column_t static_filterable_columns[]
    = RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE;
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  column_t *filterable_columns;
  int ret;
  gchar *filter, *value;
  int autofp, apply_overrides, dynamic_severity;
  gchar *extra_tables, *extra_where, *owned_clause;
  gchar *pre_sql;
  char *user_id;

  assert (report);

  columns[0].select
    = "(SELECT autofp FROM results_autofp"
      " WHERE (result = results.id) AND (autofp_selection = opts.autofp))";
  columns[0].filter = "auto_type";
  columns[0].type = KEYWORD_TYPE_INTEGER;

  dynamic_severity = setting_dynamic_severity_int ();

  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");
  apply_overrides = value ? strcmp (value, "0") : 0;
  g_free (value);

  filterable_columns = column_array_copy (static_filterable_columns);
  column_array_set
   (filterable_columns,
    "type",
    g_strdup_printf ("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 = %i"
                     "   AND result_new_severities.dynamic = %i"
                     "   LIMIT 1))",
                     apply_overrides,
                     dynamic_severity));

  if (dynamic_severity)
    {
      if (apply_overrides)
        columns[1].select
          = "(SELECT coalesce ((SELECT new_severity FROM valid_overrides"
            "                   WHERE valid_overrides.nvt = results.nvt"
            "                   AND (valid_overrides.result = 0"
            "                        OR valid_overrides.result"
            "                           = results.id)"
            "                   AND (valid_overrides.hosts is NULL"
            "                        OR valid_overrides.hosts = ''"
            "                        OR hosts_contains"
            "                            (valid_overrides.hosts,"
            "                             results.host))"
            "                   AND (valid_overrides.port is NULL"
            "                        OR valid_overrides.port = ''"
            "                        OR valid_overrides.port"
            "                           = results.port)"
            "                   AND severity_matches_ov"
            "                        (coalesce"
            "                          ((CASE WHEN results.severity"
            "                                      > " G_STRINGIFY
                                                             (SEVERITY_LOG)
            "                            THEN (SELECT"
            "                                   CAST (cvss_base"
            "                                         AS double precision)"
            "                                  FROM nvts"
            "                                  WHERE nvts.oid"
            "                                        = results.nvt)"
            "                            ELSE results.severity"
            "                            END),"
            "                           results.severity),"
            "                         valid_overrides.severity)"
            "                   LIMIT 1),"
            "                  coalesce ((CASE WHEN results.severity"
            "                                       > " G_STRINGIFY
                                                              (SEVERITY_LOG)
            "                             THEN (SELECT"
            "                                   CAST (cvss_base"
            "                                         AS double precision)"
            "                                   FROM nvts"
            "                                   WHERE nvts.oid"
            "                                         = results.nvt)"
            "                             ELSE results.severity"
            "                             END),"
            "                            results.severity)))";
      else
        columns[1].select
          = "(SELECT coalesce ((CASE WHEN results.severity"
            "                             > " G_STRINGIFY (SEVERITY_LOG)
            "                        THEN (SELECT CAST (cvss_base"
            "                                           AS double precision)"
            "                              FROM nvts"
            "                              WHERE nvts.oid = results.nvt)"
            "                        ELSE results.severity"
            "                        END),"
            "                  results.severity))";
    }
  else
    {
      if (apply_overrides)
        columns[1].select
          = "(SELECT coalesce ((SELECT new_severity FROM valid_overrides"
            "                   WHERE valid_overrides.nvt = results.nvt"
            "                   AND (valid_overrides.result = 0"
            "                        OR valid_overrides.result"
            "                           = results.id)"
            "                   AND (valid_overrides.hosts is NULL"
            "                        OR valid_overrides.hosts = ''"
            "                        OR hosts_contains"
            "                            (valid_overrides.hosts,"
            "                             results.host))"
            "                   AND (valid_overrides.port is NULL"
            "                        OR valid_overrides.port = ''"
            "                        OR valid_overrides.port"
            "                           = results.port)"
            "                   AND severity_matches_ov"
            "                        (results.severity,"
            "                         valid_overrides.severity)"
            "                   LIMIT 1),"
            "                  results.severity))";
      else
        columns[1].select
          = "(SELECT results.severity)";
    }

  columns[1].filter = "severity";
  columns[1].type = KEYWORD_TYPE_DOUBLE;

  columns[2].select = NULL;
  columns[2].filter = NULL;
  columns[2].type = KEYWORD_TYPE_UNKNOWN;

  value = filter_term_value (filter ? filter : get->filter, "autofp");
  autofp = value ? atoi (value) : 0;
  g_free (value);

  if (autofp == 0)
    {
      columns[0].select = "0";
      extra_tables = NULL;
    }
  else
    extra_tables = result_iterator_opts_table (autofp, apply_overrides,
                                               dynamic_severity);

  extra_where = results_extra_where (get, report, host,
                                     autofp, apply_overrides, dynamic_severity,
                                     filter ? filter : get->filter);

  free (filter);

  user_id = sql_string ("SELECT id FROM users WHERE uuid = '%s';",
                        current_credentials.uuid);
  owned_clause = acl_where_owned_for_get ("override", user_id);
  free (user_id);
  pre_sql = g_strdup_printf ("WITH valid_overrides"
                             " AS (SELECT nvt, hosts, new_severity, port,"
                             "            severity, result"
                             "     FROM overrides"
                             "     WHERE %s"
                             "     AND (task = 0"
                             "          OR task = (SELECT reports.task"
                             "                     FROM reports"
                             "                     WHERE reports.id = %llu))"
                             "     AND ((end_time = 0) OR (end_time >= m_now ()))"
                             "     ORDER BY result DESC, task DESC, port DESC, severity ASC,"
                             "           creation_time DESC)"
                             " ",
                             owned_clause,
                             report);
  g_free (owned_clause);

  table_order_if_sort_not_specified = 1;
  ret = init_get_iterator2_pre (iterator,
                                "result",
                                get,
                                /* SELECT columns. */
                                columns,
                                NULL,
                                /* Filterable columns not in SELECT columns. */
                                filterable_columns,
                                NULL,
                                filter_columns,
                                0,
                                extra_tables,
                                extra_where,
                                TRUE,
                                report ? TRUE : FALSE,
                                extra_order,
                                pre_sql,
                                1);
  table_order_if_sort_not_specified = 0;
  column_array_free (filterable_columns);
  g_free (pre_sql);
  g_free (extra_tables);
  g_free (extra_where);
  return ret;
}

/**
 * @brief Initialise a result iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 * @param[in]  report      Report to restrict returned results to.
 * @param[in]  host        Host to limit results to.
 * @param[in]  extra_order Extra text for ORDER term in SQL.
 *
 * @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,
                          report_t report, const char* host,
                          const gchar *extra_order)
{
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = RESULT_ITERATOR_COLUMNS;
  int ret;
  gchar *filter, *value;
  int autofp, apply_overrides, dynamic_severity;

  gchar *extra_tables, *extra_where;

  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");
  apply_overrides = value ? strcmp (value, "0") : 0;
  g_free (value);

  value = filter_term_value (filter ? filter : get->filter, "autofp");
  autofp = value ? atoi (value) : 0;
  g_free (value);

  dynamic_severity = setting_dynamic_severity_int ();

  extra_tables
    = result_iterator_opts_table (autofp, apply_overrides, dynamic_severity);

  extra_where = results_extra_where (get, report, host,
                                     autofp, apply_overrides, dynamic_severity,
                                     filter ? filter : get->filter);

  free (filter);

  ret = init_get_iterator2 (iterator,
                            "result",
                            get,
                            /* SELECT columns. */
                            columns,
                            NULL,
                            /* Filterable columns not in SELECT columns. */
                            NULL,
                            NULL,
                            filter_columns,
                            0,
                            extra_tables,
                            extra_where,
                            TRUE,
                            report ? TRUE : FALSE,
                            extra_order);
  g_free (extra_tables);
  g_free (extra_where);
  return ret;
}

/**
 * @brief Count the number of results.
 *
 * @param[in]  get     GET params.
 * @param[in]  report  Report to limit results to.
 * @param[in]  host    Host to limit results to.
 *
 * @return Total number of results in filtered set.
 */
int
result_count (const get_data_t *get, report_t report, const char* host)
{
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = RESULT_ITERATOR_COLUMNS;
  int ret;
  gchar *filter, *value;
  int apply_overrides, autofp, dynamic_severity;
  gchar *extra_tables, *extra_where;

  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, "apply_overrides");
  apply_overrides = value && strcmp (value, "0");
  g_free (value);

  value = filter_term_value (filter, "autofp");
  autofp = value ? atoi (value) : 0;
  g_free (value);

  dynamic_severity = setting_dynamic_severity_int ();

  extra_tables
    = result_iterator_opts_table (autofp, apply_overrides, dynamic_severity);

  extra_where = results_extra_where (get, report, host,
                                     autofp, apply_overrides, dynamic_severity,
                                     filter ? filter : get->filter);

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

  /* 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 "";

  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_report_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),"
                       " (SELECT uuid FROM hosts"
                       "  WHERE id = (SELECT host FROM host_identifiers"
                       "              WHERE source_type = 'Report Host'"
                       "              AND name = 'ip'"
                       "              AND source_id = (SELECT uuid"
                       "                               FROM reports"
                       "                               WHERE id = report)"
                       "              AND value = report_hosts.host"
                       "              LIMIT 1))"
                       " FROM report_hosts WHERE id = %llu"
                       " AND report = %llu"
                       "%s%s%s"
                       " ORDER BY order_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),"
                       " (SELECT uuid FROM hosts"
                       "  WHERE id = (SELECT host FROM host_identifiers"
                       "              WHERE source_type = 'Report Host'"
                       "              AND name = 'ip'"
                       "              AND source_id = (SELECT uuid"
                       "                               FROM reports"
                       "                               WHERE id = report)"
                       "              AND value = report_hosts.host"
                       "              LIMIT 1))"
                       " FROM report_hosts WHERE report = %llu"
                       "%s%s%s"
                       " ORDER BY order_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 order_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 order_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 Get the asset UUID from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The UUID of the assset associate with the host.  Caller must use
 *         only before calling cleanup_iterator.
 */
DEF_ACCESS (host_iterator_asset_uuid, 8);

/**
 * @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,"
                   " results.id"
                   " 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 Get the result from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Result.
 */
result_t
report_errors_iterator_result (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, 8);
}

/**
 * @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_classic_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 = acl_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, order_inet (host)"
            "       FROM report_hosts"
            "       ORDER BY order_inet (host))"
            "      AS distinct_host_subquery"
            /* Search IP. */
            " WHERE (distinct_host %s '%%%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 %s '%%%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;",
            sql_ilike_op (),
            quoted_search_phrase,
            last_report_sql,
            sql_ilike_op (),
            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, order_inet (host)"
          "       FROM report_hosts"
          "       ORDER BY order_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 %s '%%%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 %s '%%%s%%')"
                     " ORDER BY order_inet (host)"
                     " LIMIT %s OFFSET %i;",
                     sql_ilike_op (),
                     quoted_search_phrase,
                     sql_ilike_op (),
                     quoted_search_phrase,
                     sql_select_limit (max_results),
                     first_result);
      g_free (quoted_search_phrase);
    }
  else
    init_iterator (iterator,
                   "SELECT DISTINCT host, order_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 order_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 (classic_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 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_epoch (task_t task, time_t time)
{
  if (time)
    sql ("UPDATE tasks SET end_time = %i WHERE id = %llu;", time, task);
  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 Get the start time of a scan.
 *
 * @param[in]  uuid  The report associated with the scan.
 *
 * @return Start time of scan, in a newly allocated string.
 */
char*
scan_start_time_uuid (const char *uuid)
{
  char *time, *quoted_uuid;
  quoted_uuid = sql_quote (uuid);
  time = sql_string ("SELECT iso_time (start_time)"
                     " FROM reports WHERE uuid = '%s';",
                     quoted_uuid);
  return time ? time : g_strdup ("");
}

/**
 * @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 id = %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 id = %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);
}

/**
 * @brief Get the end time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 */
int
scan_host_end_time (report_t report, const char* host)
{
  gchar *quoted_host;
  int ret;

  quoted_host = sql_quote (host);
  ret = sql_int ("SELECT end_time FROM report_hosts"
                 " WHERE report = %llu AND host = '%s';",
                 report, quoted_host);
  g_free (quoted_host);
  return ret;
}

/**
 * @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);
  if (setting_auto_cache_rebuild_int ())
    report_cache_counts (report, 0, 0, NULL);
  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 = acl_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 = acl_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]  host       Host to which to limit the count.  NULL to allow all.
 * @param[in]  get        Report "get" data to retrieve filter info from.
 * @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, const char *host,
                      const get_data_t* get,
                      severity_data_t* severity_data,
                      severity_data_t* filtered_severity_data)
{
  iterator_t results;

  gchar *filter, *value;
  int apply_overrides, autofp;

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

  value = filter_term_value (filter ? filter : get->filter, "apply_overrides");
  apply_overrides = value ? strcmp (value, "0") : 0;
  g_free (value);

  value = filter_term_value (filter ? filter : get->filter, "autofp");
  autofp = value ? atoi (value) : 0;
  g_free (value);

  if (severity_data)
    {
      get_data_t *get_all;

      get_all = report_results_get_data (1, -1, apply_overrides, autofp, 0);
      ignore_max_rows_per_page = 1;
      init_result_get_iterator_severity (&results, get_all, report, host, NULL);
      ignore_max_rows_per_page = 0;
      while (next (&results))
        {
          double severity;

          if (results.done)
            severity = 0.0;
          else if (iterator_int (&results, 0))
            severity = SEVERITY_FP;
          else
            severity = iterator_double (&results, 1);

          severity_data_add (severity_data, severity);
        }
      cleanup_iterator (&results);
      get_data_reset (get_all);
      free (get_all);
    }

  if (filtered_severity_data)
    {
      get_data_t get_filtered;

      memset (&get_filtered, 0, sizeof (get_data_t));
      get_filtered.filt_id = get->filt_id;
      get_filtered.filter = get->filter;
      get_filtered.type = get->type;
      get_filtered.ignore_pagination = 1;

      ignore_max_rows_per_page = 1;
      init_result_get_iterator_severity (&results, &get_filtered, report, host,
                                         NULL);
      ignore_max_rows_per_page = 0;
      while (next (&results))
        {
          double severity;

          if (results.done)
            severity = 0.0;
          else if (iterator_int (&results, 0))
            severity = SEVERITY_FP;
          else
            severity = iterator_double (&results, 1);

          severity_data_add (filtered_severity_data, severity);
        }
      cleanup_iterator (&results);
    }
}

/**
 * @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.
 * @param[in]   min_qod      Min QOD.
 *
 * @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;
  int ret;
  get_data_t *get;
  // TODO Wrap in transaction.
  if (find_report_with_permission (report_id, &report, "get_reports"))
    return -1;
  // TODO Check if report was found.

  get = report_results_get_data (1, -1, override, autofp, min_qod);
  ret = report_counts_id (report, debugs, holes, infos, logs, warnings,
                          false_positives, severity, get, NULL);
  get_data_reset (get);
  free (get);
  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.
 * @param[in]           min_qod   Minimum QoD of results to count.
 *
 * @return 1 if cache exists, 0 otherwise.
 */
static int
report_counts_cache_exists (report_t report, int override, int min_qod)
{
  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 min_qod = %d"
                    "   AND (end_time = 0 OR end_time >= m_now ()));",
                    report, override, current_credentials.uuid, min_qod);
}

/**
 * @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[in]           min_qod   Minimum QoD of results to count.
 * @param[out]          data      The severity_data_t to save counts in.
 */
static void
report_counts_from_cache (report_t report, int override, int min_qod,
                          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 min_qod = %d"
                 "   AND (end_time = 0 OR end_time >= m_now ());",
                 report, override, current_credentials.uuid, min_qod);
  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]   min_qod   The minimum QoD of 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, int min_qod,
                     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 min_qod = %i"
                    "   AND \"user\" = (SELECT id FROM users"
                    "                   WHERE users.uuid = '%s');",
                    report, override, min_qod, 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, min_qod, severity,"
                        "  count, end_time)"
                        " VALUES (%llu,"
                        "         (SELECT id FROM users"
                        "          WHERE users.uuid = '%s'),"
                        "         %d, %d, " G_STRINGIFY (SEVERITY_MISSING) ","
                        "         0, 0);",
                        report, current_credentials.uuid, override, min_qod);
      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, min_qod,"
                                "  severity, count, end_time)"
                                " VALUES (%llu,"
                                "         (SELECT id FROM users"
                                "          WHERE users.uuid = '%s'),"
                                "         %d, %d, %1.1f, %d, %d);",
                                report, current_credentials.uuid, override,
                                min_qod, 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]   get       Get data.
 * @param[in]   host      Host to which to limit the count.
 * @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_full (report_t report, int* debugs, int* holes, int* infos,
                       int* logs, int* warnings, int* false_positives,
                       double* severity,
                       const get_data_t* get, const char* host,
                       int* filtered_debugs, int* filtered_holes,
                       int* filtered_infos, int* filtered_logs,
                       int* filtered_warnings, int* filtered_false_positives,
                       double* filtered_severity)
{
  const char *filter;
  keyword_t **point;
  array_t *split;
  int filter_cacheable, unfiltered_requested, filtered_requested, cache_exists;
  const char *severity_class;
  int override, min_qod_int;
  severity_data_t severity_data, filtered_severity_data;

  unfiltered_requested = (holes || warnings || infos || logs || false_positives
                          || severity);
  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__);

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

  filter_cacheable = TRUE;
  override = 0;
  min_qod_int = MIN_QOD_DEFAULT;

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

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

      keyword = *point;
      if (keyword->column == NULL)
        {
          filter_cacheable = FALSE;
        }
      else if (strcasecmp (keyword->column, "first") == 0
               || strcasecmp (keyword->column, "rows") == 0
               || strcasecmp (keyword->column, "sort") == 0
               || strcasecmp (keyword->column, "sort-reverse") == 0
               || strcasecmp (keyword->column, "notes") == 0
               || strcasecmp (keyword->column, "overrides") == 0)
        {
          // ignore
        }
      else if (strcasecmp (keyword->column, "apply_overrides") == 0)
        {
          if (keyword->string
              && strcmp (keyword->string, "")
              && strcmp (keyword->string, "0"))
            override = 1;
        }
      else if (strcasecmp (keyword->column, "min_qod") == 0)
        {
          if (keyword->string == NULL
              || sscanf (keyword->string, "%d", &min_qod_int) != 1)
            min_qod_int = MIN_QOD_DEFAULT;
        }
      else if (strcasecmp (keyword->column, "autofp") == 0)
        {
          if (keyword->string
              && strcmp (keyword->string, "")
              && strcmp (keyword->string, "0"))
            {
              filter_cacheable = FALSE;
            }
        }
      else
        {
          filter_cacheable = FALSE;
        }
      point++;
    }
  filter_free (split);

  cache_exists = filter_cacheable
                 && report_counts_cache_exists (report, override, min_qod_int);
  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 (cache_exists && filter_cacheable)
    {
      /* Get unfiltered counts from cache. */
      if (unfiltered_requested)
        report_counts_from_cache (report, override, min_qod_int,
                                  &severity_data);
      if (filtered_requested)
        report_counts_from_cache (report, override, min_qod_int,
                                  &filtered_severity_data);
    }
  else
    {
      /* Recalculate. */
      report_severity_data (report, host, get,
                            unfiltered_requested
                              ? &severity_data : NULL,
                            filtered_requested
                              ? &filtered_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 (filter_cacheable && !cache_exists)
    {
      if (unfiltered_requested)
        cache_report_counts (report, override, 0, &severity_data, 0);
      if (filtered_requested)
        cache_report_counts (report, override, min_qod_int,
                             &filtered_severity_data, 0);
    }

  cleanup_severity_data (&severity_data);
  cleanup_severity_data (&filtered_severity_data);

  return 0;
}

/**
 * @brief Get the full, unfiltered 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]   get       Get data.
 * @param[in]   host      Host to which to limit the count.  NULL to allow all.
 *
 * @return 0 on success, -1 on error.
 */
int
report_counts_id_no_filt (report_t report, int* debugs, int* holes, int* infos,
                          int* logs, int* warnings, int* false_positives,
                          double* severity, const get_data_t *get,
                          const char *host)
{
  int ret;
  ret = report_counts_id_full (report, debugs, holes, infos, logs, warnings,
                               false_positives, severity, get, host,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL);
  return ret;
}

/**
 * @brief Get only the filtered 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]   get       Get data.
 * @param[in]   host      Host to which to limit the count.  NULL to allow all.
 *
 * @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, const get_data_t *get, const char *host)
{
  int ret;
  ret = report_counts_id_full (report, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               get, host, debugs, holes, infos, logs, warnings,
                               false_positives, severity);
  return ret;
}

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

  init_iterator (&iterator,
                 "SELECT max(severity)"
                 " FROM report_counts"
                 " WHERE report = %llu"
                 " AND override = %d"
                 " AND user = (SELECT id FROM users WHERE uuid = '%s')"
                 " AND min_qod = %d"
                 " AND (end_time = 0 or end_time >= m_now ());",
                 report, overrides, current_credentials.uuid, min_qod);
  if (next (&iterator)
      && (iterator_null (&iterator, 0) == 0))
    {
      g_debug ("%s: max(severity)=%s", __FUNCTION__,
               iterator_string (&iterator, 0));
      severity = iterator_double (&iterator, 0);
    }
  else
    {
      g_debug ("%s: could not get max from cache", __FUNCTION__);
      get_data_t *get = report_results_get_data (1, -1, overrides, 0, min_qod);
      report_counts_id (report, NULL, NULL, NULL, NULL, NULL,
                        NULL, &severity, get, NULL);
      get_data_reset (get);
      free (get);
    }
  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);
  g_debug ("%s: slave_task_uuid: %s", __FUNCTION__, slave_task_uuid);
  if (slave_task_uuid)
    {
      scanner_t slave;

      /* A stopped report leaves the task on the slave.  Try delete the task. */

      slave = task_scanner (task);
      if (slave)
        {
          char *username, *password;

          username = scanner_login (slave);
          password = scanner_password (slave);
          if (username && password)
            {
              char *host;
              int port;

              /* Try with values stored on report. */
              host = report_slave_host (report);
              port = report_slave_port_int (report);
              if (host)
                delete_slave_task (host, port, username, password,
                                   slave_task_uuid);
              g_free (host);

              /* TODO If that fails, try with the current values from the
               *      slave/scanner stored on the report.  And if that fails,
               *      try with the values from the current slave of the task. */
            }
          free (username);
          free (password);
        }
    }

  /* 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 (acl_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, 2 failed to find report, 99 permission denied, -1 error.
 */
int
delete_report (const char *report_id, int dummy)
{
  report_t report;
  int ret;

  sql_begin_exclusive ();

  if (acl_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 2;
    }

  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 */
  if (setting_auto_cache_rebuild_int ())
    report_cache_counts (report, 1, 1, NULL);
  else
    report_clear_count_cache (report, 1, 1, NULL);
}

/**
 * @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 */
  if (setting_auto_cache_rebuild_int ())
    report_cache_counts (report, 1, 1, NULL);
  else
    report_clear_count_cache (report, 1, 1, NULL);
}

/**
 * @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 Some result info, for sorting.
 */
struct result_buffer
{
  gchar *host;                  ///< Host.
  gchar *port;                  ///< Port.
  gchar *severity;              ///< Severity.
  double severity_double;       ///< Severity.
};

/**
 * @brief Buffer host type.
 */
typedef struct result_buffer result_buffer_t;

/**
 * @brief Create a result buffer.
 *
 * @param[in]  host      Host.
 * @param[in]  port      Port.
 * @param[in]  severity  Severity.
 * @param[in]  severity_double  Severity.
 *
 * @return Freshly allocated result buffer.
 */
static result_buffer_t*
result_buffer_new (const gchar *host, const gchar *port, const gchar *severity,
                   double severity_double)
{
  result_buffer_t *result_buffer;
  result_buffer = g_malloc (sizeof (result_buffer_t));
  result_buffer->host = g_strdup (host);
  result_buffer->port = g_strdup (port);
  result_buffer->severity = g_strdup (severity);
  result_buffer->severity_double = severity_double;
  return result_buffer;
}

/**
 * @brief Free a result buffer.
 *
 * @param[in]  result_buffer  Result buffer.
 */
static void
result_buffer_free (result_buffer_t *result_buffer)
{
  g_free (result_buffer->host);
  g_free (result_buffer->port);
  g_free (result_buffer->severity);
  g_free (result_buffer);
}

/**
 * @brief Compares two buffered results, sorting by host, 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;
  result_buffer_t *one, *two;

  one = *((result_buffer_t**) arg_one);
  two = *((result_buffer_t**) arg_two);

  host = strcmp (one->host, two->host);
  if (host == 0)
    {
      double severity_cmp;
      int port;

      port = strcmp (one->port, two->port);
      if (port != 0)
        return port;

      severity_cmp = two->severity_double - one->severity_double;
      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;
  const char *nvt, *delta_nvt, *name, *delta_name, *descr, *delta_descr;
  int ret;
  double severity, delta_severity;

  if (sort_field == NULL)
    sort_field = "type";

  g_debug ("   delta: %s: sort_order: %i", __FUNCTION__, sort_order);
  g_debug ("   delta: %s: sort_field: %s", __FUNCTION__, sort_field);

  host = result_iterator_host (results);
  delta_host = result_iterator_host (delta_results);

  port = result_iterator_port (results);
  delta_port = result_iterator_port (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);

  name = result_iterator_nvt_name (results);
  delta_name = result_iterator_nvt_name (delta_results);

  descr = result_iterator_descr (results);
  delta_descr = result_iterator_descr (delta_results);

  /*
   * For delta reports to work correctly, the order must be the same as in
   *  init_delta_iterators, except that description should not be checked
   *  unless it is the sort_field.
   */

  /* Check sort_field first, also using sort_order (0 is descending). */
  if (strcmp (sort_field, "ROWID") == 0)
    {
      if (sort_order)
        return result_iterator_result (results)
                > result_iterator_result (delta_results);
      else
        return result_iterator_result (results)
                < result_iterator_result (delta_results);
    }
  else if (strcmp (sort_field, "host") == 0)
    {
      ret = collate_ip (NULL,
                        strlen (host), host, strlen (delta_host), delta_host);
      if (sort_order == 0)
        ret = -ret;
      g_debug ("   delta: %s: host (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               host, delta_host, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "port") == 0
           || strcmp (sort_field, "location") == 0)
    {
      ret = strcmp (port, delta_port);
      if (sort_order == 0)
        ret = -ret;
      g_debug ("   delta: %s: port (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               port, delta_port, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "severity") == 0)
    {
      if (severity > delta_severity)
        ret = sort_order ? 1 : -1;
      else if (severity < delta_severity)
        ret = sort_order ? -1 : 1;
      else
        ret = 0;
      g_debug ("   delta: %s: severity (%s): %f VS %f (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               severity, delta_severity, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "nvt") == 0)
    {
      ret = strcmp (nvt, delta_nvt);
      if (sort_order)
        ret = -ret;
      g_debug ("   delta: %s: nvt (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               nvt, delta_nvt, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "description") == 0)
    {
      ret = strcmp (descr, delta_descr);
      if (sort_order == 0)
        ret = -ret;
      g_debug ("   delta: %s: description (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               descr, delta_descr, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "type") == 0)
    {
      const char *type, *delta_type;

      type = result_iterator_type (results);
      delta_type = result_iterator_type (delta_results);

      ret = strcmp (type, delta_type);
      if (sort_order == 0)
        ret = -ret;
      g_debug ("   delta: %s: type (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               descr, delta_descr, ret);
      if (ret)
        return ret;
    }
  else if (strcmp (sort_field, "original_type") == 0)
    {
      const char *type, *delta_type;

      type = result_iterator_original_type (results);
      delta_type = result_iterator_original_type (delta_results);

      ret = strcmp (type, delta_type);
      if (sort_order == 0)
        ret = -ret;
      g_debug ("   delta: %s: original_type (%s): %s VS %s (%i)",
               __FUNCTION__, sort_order ? "desc" : "asc",
               descr, delta_descr, ret);
      if (ret)
        return ret;
    }
  else
    {
      /* Default to "vulnerability" (a.k.a "name") for unknown sort fields.
       *
       * Also done in print_report_xml_start, so this is just a safety check. */
      ret = strcmp (name, delta_name);
      if (sort_order == 0)
        ret = -ret;
      if (ret)
        return ret;
    }

  /* Check remaining fields */
  if (strcmp (sort_field, "host"))
    {
      ret = collate_ip (NULL,
                        strlen (host), host, strlen (delta_host), delta_host);
      g_debug ("   delta: %s: host: %s VS %s (%i)",
               __FUNCTION__, host, delta_host, ret);
      if (ret)
        return ret;
    }
  if (strcmp (sort_field, "port")
      && strcmp (sort_field, "location"))
    {
      ret = strcmp (port, delta_port);
      g_debug ("   delta: %s: port: %s VS %s (%i)",
               __FUNCTION__, port, delta_port, ret);
      if (ret)
        return ret;
    }
  if (strcmp (sort_field, "severity"))
    {
      if (severity > delta_severity)
        ret = 1;
      else if (severity < delta_severity)
        ret = -1;
      else
        ret = 0;
      g_debug ("   delta: %s: severity: %f VS %f (%i)",
               __FUNCTION__, severity, delta_severity, ret);
      if (ret)
        return ret;
    }
  if (strcmp (sort_field, "nvt"))
    {
      ret = strcmp (nvt, delta_nvt);
      g_debug ("   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;

  g_debug ("   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);

  g_debug ("   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)
              {
                g_debug ("   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)
              {
                g_debug ("   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)
              {
                g_debug ("   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)
              {
                g_debug ("   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;
      g_debug ("  == %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);
  g_debug ("   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;
  g_debug ("   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;
  g_debug ("   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;

  g_debug ("   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;

  g_debug ("   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.
 */
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"
                     " AND NOT EXISTS (SELECT * FROM report_host_details"
                     "                 WHERE report_host = report_hosts.id"
                     "                 AND name = 'CVE Scan')"
                     " 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 = acl_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 %s '%%%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 %s '%%%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);",
                  sql_ilike_op (),
                  quoted_search_phrase,
                  last_report_sql,
                  sql_ilike_op (),
                  quoted_search_phrase,
                  new_severity_sql,
                  last_report_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 %s '%%%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 %s '%%%s%%')"
                      "  ORDER BY order_inet (host))"
                      " AS distinct_host_subquery;",
                      sql_ilike_op (),
                      quoted_search_phrase,
                      sql_ilike_op (),
                      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)"
                  " AS distinct_host_subquery;");
}

/**
 * @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 Count a report's total number of hosts.
 *
 * @return Host count.
 */
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 hosts with results.
 *
 * @param[in]   report         Report.
 * @param[in]   min_qod        Minimum QoD of results to count.
 *
 * @return The number of hosts with results
 */
int
report_result_host_count (report_t report, int min_qod)
{
  return sql_int ("SELECT count (DISTINCT id) FROM report_hosts"
                  " WHERE report_hosts.report = %llu"
                  "   AND EXISTS (SELECT * FROM results"
                  "               WHERE results.host = report_hosts.host"
                  "                 AND results.qod >= %d)",
                  report,
                  min_qod);
}

/**
 * @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 %s 'general/%';",
                  report,
                  sql_ilike_op ());
}

/**
 * @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 %s 'general/%';",
                  report,
                  host,
                  sql_ilike_op ());
}

/**
 * @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.
 *
 * On error close stream.
 *
 * @param[in]   stream    Stream to write to.
 * @param[in]   details   Report host details iterator.
 *
 * @return 0 success, -1 error.
 */
int
print_report_host_detail (FILE *stream, iterator_t *details)
{
  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)
         : "");
  return 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))
    if (print_report_host_detail (stream, &details))
      return -1;
  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.
 * @param[in]   asset_id    Asset ID.
 */
#define PRINT_REPORT_ERROR(stream, errors, asset_id)                       \
  do                                                                       \
    {                                                                      \
      PRINT (stream,                                                       \
             "<error>"                                                     \
             "<host>"                                                      \
             "%s"                                                          \
             "<asset asset_id=\"%s\"/>"                                    \
             "</host>"                                                     \
             "<port>%s</port>"                                             \
             "<description>%s</description>"                               \
             "<nvt oid=\"%s\">"                                            \
             "<type>nvt</type>"                                            \
             "<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),                         \
             asset_id ? asset_id : "",                                     \
             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))
    {
      char *asset_id;

      asset_id = result_host_asset_id (report_errors_iterator_host (&errors),
                                       report_errors_iterator_result (&errors));
      PRINT_REPORT_ERROR (stream, &errors, asset_id);
      free (asset_id);
    }
  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_classic_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 : classic_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_report_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,
                                get, ip);

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

                  if (print_report_host_detail (out, &details))
                    return -1;

                  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,
                               severity_to_level (cvss, 0));
                      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>",
                       severity_to_level (highest_cvss, 0));

              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]  get              Result get data.
 * @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.
 * @param[out] host_ports       Hash table for counting ports per host.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_port_xml (report_t report, FILE *out, const get_data_t *get,
                       int first_result, int max_results,
                       int sort_order, const char *sort_field,
                       GHashTable *host_ports)
{
  iterator_t results;
  result_buffer_t *last_item;
  GArray *ports = g_array_new (TRUE, FALSE, sizeof (gchar*));

  init_result_get_iterator (&results, get, report, NULL, NULL);

  /* Buffer the results, removing duplicates. */

  last_item = NULL;
  while (next (&results))
    {
      const char *port = result_iterator_port (&results);
      const char *host = result_iterator_host (&results);
      double cvss_double;

      cvss_double = result_iterator_severity_double (&results);

      if (last_item
          && strcmp (port, last_item->port) == 0
          && strcmp (host, last_item->host) == 0
          && last_item->severity_double <= cvss_double)
        {
          last_item->severity_double = cvss_double;
          g_free (last_item->severity);
          last_item->severity = g_strdup (result_iterator_severity (&results));
        }
      else
        {
          const char *cvss;
          result_buffer_t *item;

          cvss = result_iterator_severity (&results);
          if (cvss == NULL)
            {
              cvss_double = 0.0;
              cvss = "0.0";
            }
          item = result_buffer_new (host, port, cvss, cvss_double);
          g_array_append_val (ports, item);
          last_item = item;
        }

    }

  /* 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_item = NULL;
      for (index = 0, length = ports->len; index < length; index++)
        {
          result_buffer_t *item;

          item = g_array_index (ports, result_buffer_t*, index);
          if (last_item
              && (strcmp (item->port, last_item->port) == 0)
              && (strcmp (item->host, last_item->host) == 0))
            {
              if (item->severity_double > last_item->severity_double)
                {
                  gchar *severity;
                  severity = last_item->severity;
                  last_item->severity = item->severity;
                  item->severity = severity;
                  last_item->severity_double = item->severity_double;
                }
              g_array_remove_index (ports, index);
              length = ports->len;
              index--;
            }
          else
            last_item = item;
        }

      /* 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));
  {
    result_buffer_t *item;
    int index = 0;

    while ((item = g_array_index (ports, result_buffer_t*, index++)))
      {
        int host_port_count
              = GPOINTER_TO_INT (g_hash_table_lookup (host_ports, item->host));

        PRINT (out,
               "<port>"
               "<host>%s</host>"
               "%s"
               "<severity>%1.1f</severity>"
               "<threat>%s</threat>"
               "</port>",
               item->host,
               item->port,
               item->severity_double,
               severity_to_level (g_strtod (item->severity, NULL), 0));

        if (g_str_has_prefix(item->port, "general/") == FALSE)
          {
            g_hash_table_replace (host_ports,
                                  g_strdup (item->host),
                                  GINT_TO_POINTER (host_port_count + 1));
          }
        result_buffer_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]  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, 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;
  time_t now;
  gchar *scan_start, *scan_end;

  time (&now);

  if (host == NULL)
    {
      host_levels = host_levels ? host_levels : "hmlgd";

      init_classic_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 : classic_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,
                                          &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 (result_hosts_only && !filtered)
            /* Skip this host. */
            report_host = 0;
        }

      if (report_host)
        {
          init_report_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,
                                            sort_order, sort_field);
              while (next (&prognosis))
                {
                  const char *threat;

                  threat = severity_to_level (prognosis_iterator_cvss_double
                                                (&prognosis), 0);

                  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=\"%s\">"
                          "<type>cve</type>"
                          "<name>%s</name>"
                          "<cvss_base>%s</cvss_base>"
                          "<cpe id='%s'/>"
                          "</nvt>"
                          "<threat>%s</threat>"
                          "<description>"
                          "The host carries the product: %s\n"
                          "It is vulnerable according to: %s.\n"
                          "\n"
                          "%s"
                          "</description>"
                          "</result>",
                          ip,
                          prognosis_iterator_cve (&prognosis),
                          prognosis_iterator_cve (&prognosis),
                          prognosis_iterator_cvss (&prognosis),
                          prognosis_iterator_cpe (&prognosis),
                          threat,
                          prognosis_iterator_cpe (&prognosis),
                          prognosis_iterator_cve (&prognosis),
                          prognosis_iterator_description
                           (&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);
    }

  scan_start = g_strdup (iso_time (&now));
  time (&now);
  scan_end = g_strdup (iso_time (&now));

  PRINT (out,
         "<scan_start>%s</scan_start>"
         "<scan_end>%s</scan_end>",
         scan_start,
         scan_end);

  array_terminate (buffer);
  index = 0;
  while ((buffer_host = g_ptr_array_index (buffer, index++)))
    {
      iterator_t report_hosts;
      init_report_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_start>"
                 "<host>%s</host>%s"
                 "</host_start>"
                 "<host_end>"
                 "<host>%s</host>%s"
                 "</host_end>",
                 buffer_host->ip,
                 scan_start,
                 buffer_host->ip,
                 scan_end);

          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,
                            get, buffer_host->ip);

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

  g_free (scan_start);
  g_free (scan_end);
  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, long maximum_hosts, gchar **hosts_xml)
{
  long total = 0, num_hosts = 0, dead_hosts = 0;
  int total_progress;
  iterator_t hosts;
  GString *string;

  string = g_string_new ("");

  init_report_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)
        {
          if (max_port == -1)
            dead_hosts++;
          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 - dead_hosts)
                   ? (total / (maximum_hosts - dead_hosts)) : 0;

#if 1
  g_debug ("   total: %li\n", total);
  g_debug ("   num_hosts: %li\n", num_hosts);
  g_debug ("   maximum_hosts: %li\n", maximum_hosts);
  g_debug ("   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 progress;
  long maximum_hosts;

  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_max (hosts, exclude_hosts, 0) : 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]  zone             Only revert if this is at least one character.
 *                               Freed here always.
 * @param[in]  tz               Original TZ.  Freed here if revert occurs.
 * @param[in]  old_tz_override  Original tz_override.  Freed here on revert.
 *
 * @return 0 success, -1 error.
 */
static int
tz_revert (gchar *zone, char *tz, char *old_tz_override)
{
  if (zone && strlen (zone))
    {
      gchar *quoted_old_tz_override;
      /* 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);
              free (old_tz_override);
              return -1;
            }
        }
      else
        unsetenv ("TZ");

      quoted_old_tz_override = sql_insert (old_tz_override);
      sql ("UPDATE current_credentials SET tz_override = %s",
           quoted_old_tz_override);
      g_free (quoted_old_tz_override);

      free (old_tz_override);
      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_iso            Start time, in ISO format.
 * @param[in]  end_iso              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)
    {
      char start[200], end[200];

      if (start_iso)
        {
          struct tm start_tm;

          memset (&start_tm, 0, sizeof (struct tm));
          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;
            }
        }
      else
        strcpy (start, "(not started)");

      if (end_iso)
        {
          struct tm end_tm;

          memset (&end_tm, 0, sizeof (struct tm));
          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;
            }
        }
      else
        strcpy (end, "(not finished)");

      g_string_append_printf (host_summary_buffer,
                              "   %-15s   %-16s   %s\n",
                              host,
                              start,
                              end);
    }
}

/**
 * @brief Init delta iterators for print_report_xml.
 *
 * @param[in]  report         The report.
 * @param[in]  results        Report result iterator.
 * @param[in]  delta          Delta report.
 * @param[in]  delta_results  Delta report result iterator.
 * @param[in]  get            GET command data.
 * @param[in]  term           Filter term.
 * @param[out] sort_field     Sort field.
 *
 * @return 0 on success, -1 error.
 */
static int
init_delta_iterators (report_t report, iterator_t *results, report_t delta,
                      iterator_t *delta_results, const get_data_t *get,
                      const char *term, const char *sort_field)
{
  int res;
  gchar *order;
  get_data_t delta_get;

  /*
   * Order must be the same as in result_cmp, except for description
   *  which isn't checked there.
   */
  if ((strcmp (sort_field, "name") == 0)
      || (strcmp (sort_field, "vulnerability") == 0))
    order = g_strdup (", host, port, severity, nvt, description");
  else if (strcmp (sort_field, "host") == 0)
    order = g_strdup (", port, severity, nvt, description");
  else if ((strcmp (sort_field, "port") == 0)
           || (strcmp (sort_field, "location") == 0))
    order = g_strdup (", host, severity, nvt, description");
  else if (strcmp (sort_field, "severity") == 0)
    order = g_strdup (", host, port, nvt, description");
  else if (strcmp (sort_field, "nvt") == 0)
    order = g_strdup (", host, port, severity, description");
  else
    order = g_strdup (", host, port, severity, nvt, description");

  delta_get = *get;
  delta_get.filt_id = NULL;
  delta_get.filter = g_strdup_printf ("rows=-1 first=1 %s", term);
  ignore_max_rows_per_page = 1;

#if 0
  iterator_t results2;

  res = init_result_get_iterator (results, &delta_get, report, NULL, order);
  if (res)
    return -1;

  res = init_result_get_iterator (&results2, &delta_get, delta, NULL, order);
  if (res)
    return -1;

  g_debug ("   delta: %s: REPORT 1:", __FUNCTION__);
  while (next (results))
    g_debug ("   delta: %s: %s   %s   %s   %s   %.30s",
            __FUNCTION__,
            result_iterator_nvt_name (results),
            result_iterator_host (results),
            result_iterator_type (results),
            result_iterator_port (results),
            result_iterator_descr (results));
  cleanup_iterator (results);
  g_debug ("   delta: %s: REPORT 1 END", __FUNCTION__);

  g_debug ("   delta: %s: REPORT 2:", __FUNCTION__);
  while (next (&results2))
    g_debug ("   delta: %s: %s   %s   %s   %s   %.30s",
            __FUNCTION__,
            result_iterator_nvt_name (&results2),
            result_iterator_host (&results2),
            result_iterator_type (&results2),
            result_iterator_port (&results2),
            result_iterator_descr (&results2));
  cleanup_iterator (&results2);
  g_debug ("   delta: %s: REPORT 2 END", __FUNCTION__);
#endif

  res = init_result_get_iterator (results, &delta_get, report, NULL, order);
  if (res)
    {
      ignore_max_rows_per_page = 0;
      g_free (order);
      return -1;
    }

  res = init_result_get_iterator (delta_results, &delta_get, delta, NULL, order);
  if (res)
    {
      ignore_max_rows_per_page = 0;
      g_free (order);
      return -1;
    }

  g_free (delta_get.filter);
  ignore_max_rows_per_page = 0;
  g_free (order);

  return 0;
}

/**
 * @brief Print delta results for print_report_xml.
 *
 * @param[in]  out            File stream to write to.
 * @param[in]  results        Report result iterator.
 * @param[in]  delta_results  Delta report result iterator.
 * @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]  first_result   First result.
 * @param[in]  max_results    Max results.
 * @param[in]  task           The task.
 * @param[in]  notes          Whether to include notes.
 * @param[in]  notes_details  Whether to include note details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  Whether to include override details.
 * @param[in]  sort_order         Sort order.
 * @param[in]  sort_field         Sort field.
 * @param[in]  result_hosts_only  Whether to only include hosts with results.
 * @param[in]  orig_filtered_result_count  Result count.
 * @param[in]  filtered_result_count       Result count.
 * @param[in]  orig_f_debugs  Result count.
 * @param[in]  f_debugs       Result count.
 * @param[in]  orig_f_holes   Result count.
 * @param[in]  f_holes        Result count.
 * @param[in]  orig_f_infos   Result count.
 * @param[in]  f_infos        Result count.
 * @param[in]  orig_f_logs    Result count.
 * @param[in]  f_logs         Result count.
 * @param[in]  orig_f_warnings  Result count.
 * @param[in]  f_warnings       Result count.
 * @param[in]  orig_f_false_positives  Result count.
 * @param[in]  f_false_positives       Result count.
 * @param[in]  result_hosts   Result hosts.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_delta_xml (FILE *out, iterator_t *results,
                        iterator_t *delta_results, const char *delta_states,
                        int first_result, int max_results, task_t task,
                        int notes, int notes_details, int overrides,
                        int overrides_details, int sort_order,
                        const char *sort_field, int result_hosts_only,
                        int *orig_filtered_result_count,
                        int *filtered_result_count,
                        int *orig_f_debugs, int *f_debugs,
                        int *orig_f_holes, int *f_holes,
                        int *orig_f_infos, int *f_infos,
                        int *orig_f_logs, int *f_logs,
                        int *orig_f_warnings, int *f_warnings,
                        int *orig_f_false_positives, int *f_false_positives,
                        array_t *result_hosts)
{
  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;
  gchar *msg;

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

  g_debug ("   delta: %s: start", __FUNCTION__);
  g_debug ("   delta: %s: sort_field: %s", __FUNCTION__, sort_field);
  g_debug ("   delta: %s: sort_order: %i", __FUNCTION__, sort_order);
  g_debug ("   delta: %s: max_results: %i", __FUNCTION__, max_results);
  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;

                g_debug ("   delta: %s: extra from report 2: %s",
                        __FUNCTION__,
                        result_iterator_nvt_oid (results));

                if (first_result)
                  {
                    g_debug ("   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)++;
                  }

                g_debug ("   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);
                if (fprintf (out, "%s", buffer->str) < 0)
                  return -1;
                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
              {
                g_debug ("   delta: %s: extra from report 1: %s",
                        __FUNCTION__,
                        result_iterator_nvt_oid (results));
                if (first_result)
                  {
                    g_debug ("   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);
                if (fprintf (out, "%s", buffer->str) < 0)
                  return -1;
                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__);
          return -1;
        }
      if (fprintf (out, "%s", buffer->str) < 0)
        return -1;
      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. */

  g_debug ("   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;

                g_debug ("   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
              {
                g_debug ("   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__);
          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);
    }
  msg = g_markup_printf_escaped ("</results>");
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);

  /* Write ports to file. */

  msg = g_markup_printf_escaped ("<ports"
                                 " start=\"%i\""
                                 " max=\"%i\">",
                                 /* Add 1 for 1 indexing. */
                                 first_result + 1,
                                 max_results);
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);
  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);
  msg = g_markup_printf_escaped ("</ports>");
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);

  return 0;
}

/**
 * @brief Print the main XML content 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_start   File name.
 * @param[in]  get         GET command data.
 * @param[in]  type               Type of report, NULL, "scan" or "assets".
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @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[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_start (report_t report, report_t delta, task_t task,
                        gchar* xml_start, const get_data_t *get,
                        const char *type,
                        int notes_details, int overrides_details,
                        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, gchar **filter_term_return,
                        gchar **zone_return, gchar **host_summary)
{
  int result_hosts_only;
  int notes, overrides;

  int first_result, max_results, sort_order, autofp;

  FILE *out;
  gchar *clean, *term, *sort_field, *levels, *search_phrase;
  gchar *min_qod;
  gchar *delta_states, *timestamp;
  int min_qod_int;
  char *uuid, *tsk_uuid = NULL, *start_time, *end_time;
  int total_result_count, filtered_result_count, run_status;
  array_t *result_hosts;
  iterator_t results, delta_results;
  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;
  char *old_tz_override;
  GString *filters_buffer, *filters_extra_buffer, *host_summary_buffer;
  gchar *term_value;
  GHashTable *f_host_ports;
  GHashTable *f_host_holes, *f_host_warnings, *f_host_infos;
  GHashTable *f_host_logs, *f_host_false_positives;

  /* Init some vars to prevent warnings from older compilers. */
  total_result_count = filtered_result_count = 0;
  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;
  f_host_ports = NULL;
  f_host_holes = NULL;
  f_host_warnings = NULL;
  f_host_infos = NULL;
  f_host_logs = NULL;
  f_host_false_positives = NULL;

  /** @todo Leaks on error in PRINT and PRINT_XML.  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_start, "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_qod, &levels, &delta_states,
                                     &search_phrase, &search_phrase_exact,
                                     &autofp, &notes, &overrides,
                                     &apply_overrides, &zone);
    }
  else
    {
      term = NULL;
      first_result = 0;
      max_results = -1;
      sort_field = NULL;
      sort_order = 1;
      result_hosts_only = 1;
      min_qod = NULL;
      levels = NULL;
      delta_states = NULL;
      search_phrase = NULL;
      search_phrase_exact = 0;
      autofp = 0;
      notes = 0;
      overrides = 0;
      apply_overrides = 0;
      zone = NULL;
    }

  max_results = manage_max_rows (max_results);

  if (delta
      && sort_field
      && strcmp (sort_field, "name")
      && strcmp (sort_field, "vulnerability")
      && strcmp (sort_field, "host")
      && strcmp (sort_field, "port")
      && strcmp (sort_field, "location")
      && strcmp (sort_field, "severity")
      && strcmp (sort_field, "type")
      && strcmp (sort_field, "original_type"))
    {
      if ((strcmp (sort_field, "task") == 0)
          || (strcmp (sort_field, "task_id") == 0)
          || (strcmp (sort_field, "report_id") == 0))
        {
          /* These don't affect delta report, so sort by vulnerability. */
          g_free (sort_field);
          sort_field = g_strdup ("vulnerability");
        }
      else
        {
          /* The remaining filterable fields for the result iterator, all of
           * which may be used as a sort field.  These could be added to
           * result_cmp.  For now sort by vulnerability. */
#if 0
          "uuid", "comment", "created", "modified", "_owner"
          "nvt",
          "auto_type",
          "report", "cvss_base", "nvt_version",
          "original_severity", "date",
          "solution_type", "qod", "qod_type", "cve", "hostname"
#endif
          g_free (sort_field);
          sort_field = g_strdup ("vulnerability");
        }
    }

  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_qod);
      g_free (delta_states);
      return -1;
    }

  if (zone && strlen (zone))
    {
      gchar *quoted_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;
        }

      old_tz_override = sql_string ("SELECT tz_override"
                                    " FROM current_credentials;");

      quoted_zone = sql_insert (zone);
      sql ("UPDATE current_credentials SET tz_override = %s", quoted_zone);
      g_free (quoted_zone);

      tzset ();
    }
  else
    {
      /* Keep compiler quiet. */
      tz = NULL;
      old_tz_override = 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_qod);
          g_free (delta_states);
          tz_revert (zone, tz, old_tz_override);
          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>");
    }

  if (report)
    {
      if (delta == 0)
        {
          int total_debugs, total_holes, total_infos, total_logs;
          int total_warnings, total_false_positives;
          get_data_t *all_results_get;

          all_results_get = report_results_get_data (1, -1, 0, 0, 0);
          report_counts_id (report, &total_debugs, &total_holes, &total_infos,
                            &total_logs, &total_warnings,
                            &total_false_positives, NULL, all_results_get,
                            NULL);
          total_result_count = total_debugs + total_holes + total_infos
                               + total_logs + total_warnings
                               + total_false_positives;
          get_data_reset (all_results_get);
          free (all_results_get);
        }
      report_counts_id (report, &debugs, &holes, &infos, &logs, &warnings,
                        &false_positives, NULL, get, NULL);
      filtered_result_count = debugs + holes + infos + logs + warnings
                              + false_positives;
      report_scan_run_status (report, &run_status);
    }

  clean = manage_clean_filter (term
                                ? term
                                : (get->filter ? get->filter : ""));

  term_value = filter_term_value (clean, "min_qod");
  if (term_value == NULL)
    {
      gchar *new_filter;
      new_filter = g_strdup_printf ("min_qod=%i %s",
                                    MIN_QOD_DEFAULT,
                                    clean);
      g_free (clean);
      clean = new_filter;
    }
  g_free (term_value);

  term_value = filter_term_value (clean, "apply_overrides");
  if (term_value == NULL)
    {
      gchar *new_filter;
      new_filter = g_strdup_printf ("apply_overrides=%i %s",
                                    APPLY_OVERRIDES_DEFAULT,
                                    clean);
      g_free (clean);
      clean = new_filter;
    }
  g_free (term_value);

  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>",
    sort_field ? sort_field : "type",
    sort_order ? "ascending" : "descending");

  filters_extra_buffer = g_string_new ("");

  if (strchr (levels, 'h'))
    g_string_append (filters_extra_buffer, "<filter>High</filter>");
  if (strchr (levels, 'm'))
    g_string_append (filters_extra_buffer, "<filter>Medium</filter>");
  if (strchr (levels, 'l'))
    g_string_append (filters_extra_buffer, "<filter>Low</filter>");
  if (strchr (levels, 'g'))
    g_string_append (filters_extra_buffer, "<filter>Log</filter>");
  if (strchr (levels, 'd'))
    g_string_append (filters_extra_buffer, "<filter>Debug</filter>");
  if (strchr (levels, 'f'))
    g_string_append (filters_extra_buffer, "<filter>False Positive</filter>");

  if (delta)
    {
      gchar *escaped_host = host ? g_markup_escape_text (host, -1) : NULL;
      gchar *escaped_delta_states = g_markup_escape_text (delta_states, -1);
      g_string_append_printf (filters_extra_buffer,
                              "<host><ip>%s</ip></host>"
                              "<delta>"
                              "%s"
                              "<changed>%i</changed>"
                              "<gone>%i</gone>"
                              "<new>%i</new>"
                              "<same>%i</same>"
                              "</delta>",
                              escaped_host ? escaped_host : "",
                              escaped_delta_states,
                              strchr (delta_states, 'c') != NULL,
                              strchr (delta_states, 'g') != NULL,
                              strchr (delta_states, 'n') != NULL,
                              strchr (delta_states, 's') != NULL);
      g_free (escaped_host);
      g_free (escaped_delta_states);
    }
  else if (type && (strcmp (type, "prognostic") == 0))
    {
      gchar *escaped_host = host ? g_markup_escape_text (host, -1) : NULL;
      g_string_append_printf (filters_extra_buffer,
                              "<host><ip>%s</ip></host>",
                              escaped_host ? escaped_host : "");
      g_free (escaped_host);
    }

  filters_buffer = g_string_new ("");
  buffer_get_filter_xml (filters_buffer, "result", get, clean,
                         filters_extra_buffer->str);
  g_string_free (filters_extra_buffer, TRUE);

  PRINT_XML (out, filters_buffer->str);
  g_string_free (filters_buffer, TRUE);

  {
    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;
      iterator_t tags;

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

      PRINT (out,
             "<user_tags>"
             "<count>%i</count>",
             resource_tag_count ("task", task, 1));

      init_resource_tag_iterator (&tags, "task", task, 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>"
             "</task>");

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

      g_free (term);

      ret = print_report_assets_xml (out, host,
                                     ignore_pagination ? 1 : first_result,
                                     ignore_pagination ? -1 : max_results,
                                     levels, search_phrase, pos,
                                     get, apply_overrides, autofp);
      g_free (sort_field);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_qod);
      g_free (delta_states);

      tz_revert (zone, tz, old_tz_override);

      if (ret)
        return ret;

      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;

      g_free (term);

      ret = print_report_prognostic_xml (out, host,
                                         ignore_pagination ? 1 : first_result,
                                         ignore_pagination ? -1 : max_results,
                                         levels, search_phrase, pos, get,
                                         apply_overrides, autofp,
                                         host_search_phrase, host_levels,
                                         host_first_result, host_max_results,
                                         result_hosts_only,
                                         sort_order, sort_field);

      g_free (sort_field);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_qod);
      g_free (delta_states);

      tz_revert (zone, tz, old_tz_override);

      if (ret)
        return ret;

      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);
      g_free (term);
      tz_revert (zone, tz, old_tz_override);
      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. */

  f_host_ports = g_hash_table_new_full (g_str_hash, g_str_equal,
                                        g_free, NULL);

  if (get->details && (delta == 0))
    {
      if (print_report_port_xml (report, out, get, first_result, max_results,
                                 sort_order, sort_field, f_host_ports))
        {
          g_free (term);
          tz_revert (zone, tz, old_tz_override);
          g_hash_table_destroy (f_host_ports);
          return -1;
        }
    }

  /* Prepare result counts. */

  report_counts_id_full (report, &debugs, &holes, &infos, &logs,
                         &warnings, &false_positives, &severity,
                         get, NULL,
                         &f_debugs, &f_holes, &f_infos, &f_logs, &f_warnings,
                         &f_false_positives, &f_severity);

  /* Results. */

  if (min_qod == NULL || sscanf (min_qod, "%d", &min_qod_int) != 1)
    min_qod_int = MIN_QOD_DEFAULT;

  if (delta && get->details)
    {
      if (init_delta_iterators (report, &results, delta, &delta_results, get,
                                term, sort_field))
        {
          g_free (term);
          g_hash_table_destroy (f_host_ports);
          return -1;
        }
      g_free (term);
    }
  else if (get->details)
    {
      int res;
      g_free (term);
      res = init_result_get_iterator (&results, get, report, NULL, NULL);
      if (res)
        {
          g_hash_table_destroy (f_host_ports);
          return -1;
        }
    }
  else
    g_free (term);

  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;

  f_host_holes = g_hash_table_new_full (g_str_hash, g_str_equal,
                                        g_free, NULL);
  f_host_warnings = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           g_free, NULL);
  f_host_infos = g_hash_table_new_full (g_str_hash, g_str_equal,
                                      g_free, NULL);
  f_host_logs = g_hash_table_new_full (g_str_hash, g_str_equal,
                                      g_free, NULL);
  f_host_false_positives = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  g_free, NULL);

  if (delta && get->details)
    {
      if (print_report_delta_xml (out, &results, &delta_results, delta_states,
                                  first_result, max_results, task, notes,
                                  notes_details, overrides, overrides_details,
                                  sort_order, sort_field, result_hosts_only,
                                  &orig_filtered_result_count,
                                  &filtered_result_count,
                                  &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,
                                  result_hosts))
        {
          fclose (out);
          g_free (sort_field);
          g_free (levels);
          g_free (search_phrase);
          g_free (min_qod);
          g_free (delta_states);
          cleanup_iterator (&results);
          cleanup_iterator (&delta_results);
          tz_revert (zone, tz, old_tz_override);
          g_hash_table_destroy (f_host_ports);
          g_hash_table_destroy (f_host_holes);
          g_hash_table_destroy (f_host_warnings);
          g_hash_table_destroy (f_host_infos);
          g_hash_table_destroy (f_host_logs);
          g_hash_table_destroy (f_host_false_positives);

          return -1;
        }
    }
  else if (get->details)
    {
      while (next (&results))
        {
          const char* level;
          GHashTable *f_host_result_counts;
          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));

          level = result_iterator_level (&results);
          if (strcasecmp (level, "log") == 0)
            f_host_result_counts = f_host_logs;
          else if (strcasecmp (level, "high") == 0)
            f_host_result_counts = f_host_holes;
          else if (strcasecmp (level, "medium") == 0)
            f_host_result_counts = f_host_warnings;
          else if (strcasecmp (level, "low") == 0)
            f_host_result_counts = f_host_infos;
          else if (strcasecmp (level, "false positive") == 0)
            f_host_result_counts = f_host_false_positives;
          else
            f_host_result_counts = NULL;

          if (f_host_result_counts)
            {
              const char *host = result_iterator_host (&results);
              int result_count
                    = GPOINTER_TO_INT
                        (g_hash_table_lookup (f_host_result_counts, host));

              g_hash_table_replace (f_host_result_counts,
                                    g_strdup (host),
                                    GINT_TO_POINTER (result_count + 1));
            }

        }
      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>",
             total_result_count,
             total_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_report_host_iterator (&hosts, report, host, 0);
          present = next (&hosts);
          if (delta && (present == FALSE))
            {
              cleanup_iterator (&hosts);
              init_report_host_iterator (&hosts, delta, host, 0);
              present = next (&hosts);
            }
          if (present)
            {
              const char *current_host;
              int ports_count;
              int holes_count, warnings_count, infos_count;
              int logs_count, false_positives_count;

              current_host = host_iterator_host (&hosts);

              ports_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup (f_host_ports, current_host));
              holes_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup ( f_host_holes, current_host));
              warnings_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup ( f_host_warnings, current_host));
              infos_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup ( f_host_infos, current_host));
              logs_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup ( f_host_logs, current_host));
              false_positives_count
                = GPOINTER_TO_INT
                    (g_hash_table_lookup ( f_host_false_positives, current_host));

              host_summary_append (host_summary_buffer,
                                   host,
                                   host_iterator_start_time (&hosts),
                                   host_iterator_end_time (&hosts));
              PRINT (out,
                     "<host>"
                     "<ip>%s</ip>"
                     "<asset asset_id=\"%s\"/>"
                     "<start>%s</start>"
                     "<end>%s</end>"
                     "<port_count><page>%d</page></port_count>"
                     "<result_count>"
                     "<page>%d</page>"
                     "<hole><page>%d</page></hole>"
                     "<warning><page>%d</page></warning>"
                     "<info><page>%d</page></info>"
                     "<log><page>%d</page></log>"
                     "<false_positive><page>%d</page></false_positive>"
                     "</result_count>",
                     host,
                     host_iterator_asset_uuid (&hosts)
                       ? host_iterator_asset_uuid (&hosts)
                       : "",
                     host_iterator_start_time (&hosts),
                     host_iterator_end_time (&hosts)
                       ? host_iterator_end_time (&hosts)
                       : "",
                     ports_count,
                     (holes_count + warnings_count + infos_count
                      + logs_count + false_positives_count),
                     holes_count,
                     warnings_count,
                     infos_count,
                     logs_count,
                     false_positives_count);

              if (print_report_host_details_xml
                   (host_iterator_report_host (&hosts), out))
                {
                  tz_revert (zone, tz, old_tz_override);
                  if (host_summary_buffer)
                    g_string_free (host_summary_buffer, TRUE);
                  g_hash_table_destroy (f_host_ports);
                  g_hash_table_destroy (f_host_holes);
                  g_hash_table_destroy (f_host_warnings);
                  g_hash_table_destroy (f_host_infos);
                  g_hash_table_destroy (f_host_logs);
                  g_hash_table_destroy (f_host_false_positives);
                  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_report_host_iterator (&hosts, report, NULL, 0);
      while (next (&hosts))
        {
          const char *current_host;
          int ports_count;
          int holes_count, warnings_count, infos_count;
          int logs_count, false_positives_count;

          current_host = host_iterator_host (&hosts);

          ports_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_ports, current_host));
          holes_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_holes, current_host));
          warnings_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_warnings, current_host));
          infos_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_infos, current_host));
          logs_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_logs, current_host));
          false_positives_count
            = GPOINTER_TO_INT
                (g_hash_table_lookup (f_host_false_positives, current_host));

          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>"
                 "<asset asset_id=\"%s\"/>"
                 "<start>%s</start>"
                 "<end>%s</end>"
                 "<port_count><page>%d</page></port_count>"
                 "<result_count>"
                 "<page>%d</page>"
                 "<hole><page>%d</page></hole>"
                 "<warning><page>%d</page></warning>"
                 "<info><page>%d</page></info>"
                 "<log><page>%d</page></log>"
                 "<false_positive><page>%d</page></false_positive>"
                 "</result_count>",
                 host_iterator_host (&hosts),
                 host_iterator_asset_uuid (&hosts)
                   ? host_iterator_asset_uuid (&hosts)
                   : "",
                 host_iterator_start_time (&hosts),
                 host_iterator_end_time (&hosts)
                   ? host_iterator_end_time (&hosts)
                   : "",
                 ports_count,
                 (holes_count + warnings_count + infos_count
                  + logs_count + false_positives_count),
                 holes_count,
                 warnings_count,
                 infos_count,
                 logs_count,
                 false_positives_count);

          if (print_report_host_details_xml
               (host_iterator_report_host (&hosts), out))
            {
              tz_revert (zone, tz, old_tz_override);
              if (host_summary_buffer)
                g_string_free (host_summary_buffer, TRUE);
              g_hash_table_destroy (f_host_ports);
              g_hash_table_destroy (f_host_holes);
              g_hash_table_destroy (f_host_warnings);
              g_hash_table_destroy (f_host_infos);
              g_hash_table_destroy (f_host_logs);
              g_hash_table_destroy (f_host_false_positives);
              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_report_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);
    }

  g_hash_table_destroy (f_host_ports);
  g_hash_table_destroy (f_host_holes);
  g_hash_table_destroy (f_host_warnings);
  g_hash_table_destroy (f_host_infos);
  g_hash_table_destroy (f_host_logs);
  g_hash_table_destroy (f_host_false_positives);

  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, old_tz_override);
      if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
      return -1;
    }

  g_free (sort_field);
  g_free (levels);
  g_free (search_phrase);
  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 Completes a report by adding report format info.
 *
 * @param[in]   xml_start      Path of file containing start of report.
 * @param[in]   xml_full       Path to file to print full report to.
 * @param[in]   report_format  Format of report that will be created from XML.
 */
static int
print_report_xml_end (gchar *xml_start, gchar *xml_full,
                      report_format_t report_format)
{
  FILE *out;
  iterator_t params;

  if (openvas_file_copy (xml_start, xml_full) == FALSE)
    {
      g_warning ("%s: failed to copy xml_start file", __FUNCTION__);
      return -1;
    }

  out = fopen (xml_full, "a");
  if (out == NULL)
    {
      g_warning ("%s: fopen failed: %s\n",
                 __FUNCTION__,
                 strerror (errno));
      return -1;
    }

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

  PRINT (out, "</report>");

  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]  get                GET data for report.
 * @param[in]  report_format      Report format.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @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.
 *                                Only defined on success.
 * @param[out] content_type       NULL or location for report format content
 *                                type.  Only defined on success.
 * @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, const get_data_t *get,
               const report_format_t report_format,
               int notes_details, int overrides_details, const char *type,
               gsize *output_length, gchar **extension, gchar **content_type,
               gchar **filter_term_return, gchar **zone_return,
               gchar **host_summary)
{
  task_t task;
  gchar *report_format_id, *xml_start, *xml_file, *output_file;
  char xml_dir[] = "/tmp/openvasmd_XXXXXX";
  int ret;
  GList *used_rfps;
  GError *get_error;
  gchar *output;
  gsize output_len;

  used_rfps = NULL;

  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_start = g_strdup_printf ("%s/report-start.xml", xml_dir);
  ret = print_report_xml_start (report, 0, task, xml_start, get,
                                type, notes_details, overrides_details,
                                NULL, 0, NULL, NULL, 0, 0, /* host params */
                                0 /* ignore_pagination */,
                                filter_term_return, zone_return, host_summary);
  if (ret)
    {
      g_free (xml_start);
      openvas_file_remove_recurse (xml_dir);
      return NULL;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);

  /* Apply report format(s) */
  report_format_id = report_format_uuid (report_format);

  output_file = apply_report_format (report_format_id,
                                     xml_start, xml_file, xml_dir,
                                     &used_rfps);

  if (output_file == NULL)
    {
      g_warning ("%s: No file returned for report format", __FUNCTION__);
    }
  g_free (xml_file);
  g_free (xml_start);

  /* Read the script output from file. */
  if (output_file == NULL)
    {
      g_free (report_format_id);
      openvas_file_remove_recurse (xml_dir);
      return NULL;
    }

  get_error = NULL;
  g_file_get_contents (output_file,
                       &output,
                       &output_len,
                       &get_error);
  g_free (output_file);
  if (get_error)
    {
      g_free (report_format_id);
      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);
      openvas_file_remove_recurse (xml_dir);
      return NULL;
    }

  /* Remove the directory. */

  openvas_file_remove_recurse (xml_dir);

  /* Set convenience return parameters. */

  if (extension || content_type)
    {
      iterator_t formats;
      get_data_t report_format_get;

      memset (&report_format_get, '\0', sizeof (report_format_get));
      report_format_get.id = report_format_id;

      init_report_format_iterator (&formats, &report_format_get);
      if (next (&formats) == FALSE)
        {
          g_free (report_format_id);
          cleanup_iterator (&formats);
          return NULL;
        }

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

      cleanup_iterator (&formats);
    }

  g_free (report_format_id);

  /* Return the output. */

  if (output_length) *output_length = output_len;

  return output;
}

/**
 * @brief Runs the script of a report format.
 *
 * @param[in]   report_format_id    UUID of the report format.
 * @param[in]   xml_file            Path to main part of the report XML.
 * @param[in]   xml_dir             Path of the dir with XML and subreports.
 * @param[in]   report_format_extra Extra data for report format.
 * @param[in]   output_file         Path to write report to.
 *
 * @return 0 success, -1 error.
 */
static int
run_report_format_script (gchar *report_format_id,
                          gchar *xml_file,
                          gchar *xml_dir,
                          gchar *report_format_extra,
                          gchar *output_file)
{
  iterator_t formats;
  report_format_t report_format;
  gchar *script, *script_dir;
  get_data_t report_format_get;

  gchar *command;
  char *previous_dir;
  int ret;

  /* Setup file names and complete report. */

  memset (&report_format_get, '\0', sizeof (report_format_get));
  report_format_get.id = report_format_id;

  init_report_format_iterator (&formats, &report_format_get);
  if (next (&formats) == FALSE)
    {
      cleanup_iterator (&formats);
      return -1;
    }

  report_format = get_iterator_resource (&formats);

  if (report_format_predefined (report_format))
    {
      script_dir = predefined_report_format_dir (report_format_id);
    }
  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,
                                     report_format_id,
                                     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_FILE_TEST_IS_REGULAR))
    {
      g_warning ("%s: No generate script found at %s",
                 __FUNCTION__, script);
      g_free (script);
      g_free (script_dir);
      return -1;
    }
  else if (!g_file_test (script,
                         G_FILE_TEST_IS_EXECUTABLE))
    {
      g_warning ("%s: script %s is not executable",
                 __FUNCTION__, script);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

  /* 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);
      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);
      return -1;
    }
  g_free (script_dir);

  /* Call the script. */

  command = g_strdup_printf ("%s %s '%s' > %s"
                             " 2> /dev/null",
                             script,
                             xml_file,
                             report_format_extra,
                             output_file);
  g_free (script);

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

  if (geteuid () == 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)
          || chown (output_file, nobody->pw_uid, nobody->pw_gid))
        {
          g_warning ("%s: Failed to set dir permissions: %s\n",
                      __FUNCTION__,
                      strerror (errno));
          g_free (previous_dir);
          return -1;
        }

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

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

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

  return 0;
}

/**
 * @brief Applies a report format to an XML report.
 *
 * @param[in]  report_format_id   Report format to apply.
 * @param[in]  xml_start          Path to the main part of the report XML.
 * @param[in]  xml_file           Path to the report XML file.
 * @param[in]  xml_dir            Path to the temporary dir.
 * @param[in]  used_rfps          List of already applied report formats.
 *
 * @return Path to the generated file or NULL.
 */
static gchar*
apply_report_format (gchar *report_format_id,
                     gchar *xml_start,
                     gchar *xml_file,
                     gchar *xml_dir,
                     GList **used_rfps)
{
  report_format_t report_format;
  GHashTable *subreports;
  GList *temp_dirs, *temp_files;
  gchar *rf_dependencies_string, *output_file, *out_file_part, *out_file_ext;
  gchar *files_xml;
  int ret;

  assert (report_format_id);
  assert (xml_start);
  assert (xml_file);
  assert (xml_dir);
  assert (used_rfps);

  /* Check if there would be an infinite recursion loop. */
  if (*used_rfps
      && g_list_find_custom (*used_rfps, report_format_id,
                             (GCompareFunc) strcmp))
    {
      g_message ("%s: Recursion loop for report_format '%s'",
                 __FUNCTION__, report_format_id);
      return NULL;
    }

  /* Check if report format is available. */
  if (find_report_format_with_permission (report_format_id, &report_format,
                                          "get_report_formats")
      || report_format == 0)
    {
      g_message ("%s: Report format '%s' not found",
                 __FUNCTION__, report_format_id);
      return NULL;
    }

  /* Check if report format is active */
  if (report_format_active (report_format) == 0)
    {
      g_message ("%s: Report format '%s' is not active",
                 __FUNCTION__, report_format_id);
      return NULL;
    }

  /* Get subreports. */
  temp_dirs = NULL;
  temp_files = NULL;
  subreports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  rf_dependencies_string
    = sql_string ("SELECT value"
                  "  FROM report_format_params"
                  " WHERE report_format = %llu"
                  "   AND type = %i",
                  report_format,
                  REPORT_FORMAT_PARAM_TYPE_REPORT_FORMAT_LIST);

  if (rf_dependencies_string)
    {
      gchar **rf_dependencies, **current_rf_dependency;
      GString *files_xml_buf;
      GHashTableIter files_iter;
      gchar *key, *value;

      *used_rfps = g_list_append (*used_rfps, report_format_id);

      /* Recursively create subreports for dependencies. */
      rf_dependencies = g_strsplit (rf_dependencies_string, ",", -1);
      current_rf_dependency = rf_dependencies;

      while (*current_rf_dependency)
        {
          gchar *subreport_dir, *subreport_xml, *subreport_file;
          subreport_file = NULL;

          subreport_dir = g_strdup ("/tmp/openvasmd_XXXXXX");

          if (mkdtemp (subreport_dir) == NULL)
            {
              g_warning ("%s: mkdtemp failed\n", __FUNCTION__);
              g_free (subreport_dir);
              break;
            }
          subreport_xml = g_build_filename (subreport_dir, "report.xml", NULL);
          temp_dirs = g_list_append (temp_dirs, subreport_dir);
          temp_files = g_list_append (temp_files, subreport_xml);

          if (g_hash_table_contains (subreports, *current_rf_dependency)
              == FALSE)
            {
              subreport_file = apply_report_format (*current_rf_dependency,
                                                    xml_start,
                                                    subreport_xml,
                                                    subreport_dir,
                                                    used_rfps);
              if (subreport_file)
                {
                  g_hash_table_insert (subreports,
                                       g_strdup (*current_rf_dependency),
                                       subreport_file);
                }
            }

          current_rf_dependency ++;
        }

      g_strfreev (rf_dependencies);

      *used_rfps = g_list_remove (*used_rfps, report_format_id);

      /* Build dependencies XML. */
      files_xml_buf = g_string_new ("<files>");
      xml_string_append (files_xml_buf,
                         "<basedir>%s</basedir>",
                         xml_dir);

      g_hash_table_iter_init (&files_iter, subreports);
      while (g_hash_table_iter_next (&files_iter,
                                     (void**)&key, (void**)&value))
        {
          get_data_t report_format_get;
          iterator_t file_format_iter;

          memset (&report_format_get, '\0', sizeof (report_format_get));
          report_format_get.id = key;

          init_report_format_iterator (&file_format_iter, &report_format_get);
          if (next (&file_format_iter))
            {
              xml_string_append (files_xml_buf,
                                 "<file id=\"%s\""
                                 " content_type=\"%s\""
                                 " report_format_name=\"%s\">"
                                 "%s"
                                 "</file>",
                                 key,
                                 report_format_iterator_content_type
                                  (&file_format_iter),
                                 get_iterator_name (&file_format_iter),
                                 value);
            }
          else
            {
              xml_string_append (files_xml_buf,
                                 "<file id=\"%s\">%s</file>",
                                 key, value);
            }
          cleanup_iterator (&file_format_iter);
        }

      g_string_append (files_xml_buf, "</files>");
      files_xml = g_string_free (files_xml_buf, FALSE);
    }
  else
    {
      GString *files_xml_buf;
      /* Build dependencies XML. */
      files_xml_buf = g_string_new ("<files>");
      xml_string_append (files_xml_buf,
                         "<basedir>%s</basedir>",
                         xml_dir);
      g_string_append (files_xml_buf, "</files>");
      files_xml = g_string_free (files_xml_buf, FALSE);
    }

  /* Generate output file. */
  out_file_ext = report_format_extension (report_format);
  out_file_part = g_strdup_printf ("%s-XXXXXX.%s",
                                   report_format_id, out_file_ext);
  output_file = g_build_filename (xml_dir, out_file_part, NULL);
  ret = mkstemps (output_file, strlen (out_file_ext) + 1);
  if (ret == -1)
    {
      g_warning ("%s: mkstemps failed: %s", __FUNCTION__, strerror (errno));
      g_free (output_file);
      output_file = NULL;
      goto cleanup;
    }
  g_free (out_file_ext);
  g_free (out_file_part);

  /* Add second half of input XML */

  if (print_report_xml_end (xml_start, xml_file, report_format))
    {
      g_free (output_file);
      output_file = NULL;
      goto cleanup;
    }

  run_report_format_script (report_format_id,
                            xml_file, xml_dir, files_xml, output_file);

  /* Clean up and return filename. */
  cleanup:
  while (temp_dirs)
    {
      openvas_file_remove_recurse (temp_dirs->data);
      g_free (temp_dirs->data);
      temp_dirs = g_list_remove (temp_dirs, temp_dirs->data);
    }
  while (temp_files)
    {
      g_free (temp_files->data);
      temp_files = g_list_remove (temp_files, temp_files->data);
    }
  g_free (files_xml);
  g_hash_table_destroy (subreports);

  return output_file;
}

/**
 * @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]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @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.
 *
 * @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 notes_details, int overrides_details,
                    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)
{
  task_t task;
  gchar *xml_start, *xml_file;
  char xml_dir[] = "/tmp/openvasmd_XXXXXX";
  int ret;
  GList *used_rfps;
  gchar *output_file, *report_format_id;
  char chunk[MANAGE_SEND_REPORT_CHUNK_SIZE + 1];
  FILE *stream;

  used_rfps = NULL;

  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,
                        get, notes_details, overrides_details, NULL);
      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_start = g_strdup_printf ("%s/report-start.xml", xml_dir);
  ret = print_report_xml_start (report, delta_report, task, xml_start, get,
                                type, notes_details, overrides_details,
                                host, pos, host_search_phrase, host_levels,
                                host_first_result, host_max_results,
                                ignore_pagination, NULL, NULL, NULL);
  if (ret)
    {
      g_free (xml_start);
      openvas_file_remove_recurse (xml_dir);
      if (ret == 2)
        return 2;
      return -1;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);

  /* Apply report format(s). */
  report_format_id = report_format_uuid (report_format);

  output_file = apply_report_format (report_format_id,
                                     xml_start, xml_file, xml_dir,
                                     &used_rfps);

  if (output_file == NULL)
    {
      g_warning ("%s: No file returned for report format", __FUNCTION__);
    }
  g_free (report_format_id);

  /* Send the report. */

  /* 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));
      openvas_file_remove_recurse (xml_dir);
      return -1;
    }

  if (prefix && send (prefix, send_data_1, send_data_2))
    {
      fclose (stream);
      g_warning ("%s: send prefix error\n", __FUNCTION__);
      openvas_file_remove_recurse (xml_dir);
      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__);
              openvas_file_remove_recurse (xml_dir);
              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__);
                  openvas_file_remove_recurse (xml_dir);
                  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__);
                  openvas_file_remove_recurse (xml_dir);
                  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;
}

/**
 * @brief Get the IP of a host, using the 'hostname' report host details.
 *
 * The most recent host detail takes preference.
 *
 * @param[in]  host  Host name or IP.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
gchar*
report_host_ip (const char *host)
{
  gchar *quoted_host, *ret;
  quoted_host = sql_quote (host);
  ret = sql_string ("SELECT host FROM report_hosts"
                    " WHERE id = (SELECT report_host FROM report_host_details"
                    "             WHERE name = 'hostname'"
                    "             AND value = '%s'"
                    "             ORDER BY id DESC LIMIT 1);",
                    quoted_host);
  g_free (quoted_host);
  return ret;
}

/**
 * @brief Check if a report host is alive and has at least one result.
 *
 * @param[in]  report  Report.
 * @param[in]  host    Host name or IP.
 *
 * @return 0 if dead, else alive.
 */
int
report_host_noticeable (report_t report, const gchar *host)
{
  report_host_t report_host;

  sql_int64 (&report_host,
             "SELECT id FROM report_hosts"
             " WHERE report = %llu AND host = '%s';",
             report,
             host);

  return report_host
         && report_host_dead (report_host) == 0
         && report_host_result_count (report_host) > 0;
}

/**
 * @brief Parse an OSP report.
 *
 * @param[in]  task        Task.
 * @param[in]  report      Report.
 * @param[in]  report_xml  Report XML.
 */
void
parse_osp_report (task_t task, report_t report, const char *report_xml)
{
  entity_t entity, child;
  entities_t results;
  const char *str;
  char *defs_file = NULL;
  time_t start_time, end_time;

  assert (task);
  assert (report);
  assert (report_xml);

  if (parse_entity (report_xml, &entity))
    {
      g_warning ("Couldn't parse %s OSP scan report\n", report_xml);
      return;
    }

  sql_begin_immediate ();
  /* Set the report's start and end times. */
  str = entity_attribute (entity, "start_time");
  if (!str)
    {
      g_warning ("Missing start_time in OSP report %s", report_xml);
      goto end_parse_osp_report;
    }
  start_time = atoi (str);
  set_scan_start_time_epoch (report, start_time);
  str = entity_attribute (entity, "end_time");
  if (!str)
    {
      g_warning ("Missing end_time in OSP report %s", report_xml);
      goto end_parse_osp_report;
    }
  end_time = atoi (str);
  set_scan_end_time_epoch (report, end_time);

  /* Insert results. */
  child = entity_child (entity, "results");
  if (!child)
    {
      g_warning ("Missing results element in OSP report %s", report_xml);
      goto end_parse_osp_report;
    }
  results = child->entities;
  defs_file = task_definitions_file (task);
  while (results)
    {
      result_t result;
      const char *type, *name, *severity, *host, *test_id, *port, *qod;
      char *desc = NULL, *nvt_id = NULL, *severity_str = NULL;
      entity_t r_entity = results->data;
      int qod_int;

      if (strcmp (entity_name (r_entity), "result"))
        {
          g_warning ("Erroneous entry in OSP results %s",
                     entity_name (r_entity));
          results = next_entities (results);
          continue;
        }
      type = entity_attribute (r_entity, "type");
      name = entity_attribute (r_entity, "name");
      severity = entity_attribute (r_entity, "severity");
      test_id = entity_attribute (r_entity, "test_id");
      host = entity_attribute (r_entity, "host");
      port = entity_attribute (r_entity, "port") ?: "";
      qod = entity_attribute (r_entity, "qod") ?: "";
      if (!name || !type || !severity || !test_id || !host)
        {
          GString *string = g_string_new ("");

          print_entity_to_string (r_entity, string);
          g_warning ("Erroneous attribute in OSP result %s", string->str);
          g_string_free (string, TRUE);
          results = next_entities (results);
          continue;
        }

      /* Add report host if it doesn't exist. */
      manage_report_host_add (report, host, start_time, end_time);
      if (!strcmp (type, "Host Detail"))
        {
          insert_report_host_detail (report, host, "osp", "", "OSP Host Detail",
                                     name, entity_text (r_entity));
          results = next_entities (results);
          continue;
        }
      else if (g_str_has_prefix (test_id, "1.3.6.1.4.1.25623.1.0."))
        {
          nvt_id = g_strdup (test_id);
          severity_str = nvt_severity (test_id, type);
          desc = g_strdup (entity_text (r_entity));
        }
      else if (g_str_has_prefix (test_id, "oval:"))
        {
          nvt_id = ovaldef_uuid (test_id, defs_file);
          severity_str = ovaldef_severity (nvt_id);
        }
      else
        {
          nvt_id = g_strdup (name);
          desc = g_strdup (entity_text (r_entity));
        }

      qod_int = atoi (qod);
      if (qod_int <= 0 || qod_int > 100)
        qod_int = QOD_DEFAULT;
      result = make_osp_result (task, host, nvt_id, type, desc, port ?: "",
                                severity_str ?: severity, qod_int);
      report_add_result (report, result);
      g_free (nvt_id);
      g_free (desc);
      g_free (severity_str);
      results = next_entities (results);
    }

 end_parse_osp_report:
  sql_commit ();
  g_free (defs_file);
  free_entity (entity);
}


/* 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]  iterator  Task iterator.
 * @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;
  get_data_t *get;

  /* 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 "";

  /* Count the logs and false positives too, as report_counts_id is faster
   * with all five. */
  get = report_results_get_data (1, -1, override, 0, min_qod);
  if (report_counts_id (last_report, NULL, &holes_a, &infos_a, &logs_a, &warns_a,
                        &false_positives_a, &severity_a, get, NULL))
    /** @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)
    {
      get_data_reset (get);
      free (get);
      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, get, NULL))
    /** @todo Either fail better or abort at SQL level. */
    abort ();

  get_data_reset (get);
  free (get);

  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.
 * @param[in]  in_assets  Whether task must be considered for assets.
 * @param[in]  event      Whether to be generate event and event log.
 *
 * @return A pointer to the new task.
 */
task_t
make_task (char* name, char* comment, int in_assets, int event)
{
  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, config_location, target_location,"
       "  scanner_location, schedule_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, m_now (),"
       "         m_now ());",
       current_credentials.uuid,
       uuid,
       quoted_name ? quoted_name : "",
       quoted_comment ? quoted_comment : "");
  task = sql_last_insert_id ();
  if (event)
    set_task_run_status (task, TASK_STATUS_NEW);
  else
    set_task_run_status_internal (task, TASK_STATUS_NEW);
  sql ("INSERT INTO task_preferences (task, name, value)"
       " VALUES (%llu, 'in_assets', '%s')",
       task,
       in_assets ? "yes" : "no");
  sql ("INSERT INTO task_preferences (task, name, value)"
       " VALUES (%llu, 'assets_apply_overrides', 'yes')",
       task);
  sql ("INSERT INTO task_preferences (task, name, value)"
       " VALUES (%llu, 'assets_min_qod', %d)",
       task,
       MIN_QOD_DEFAULT);
  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, 0, EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_NEW);
}

/**
 * @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, char* value)
{
  /** @todo Free value consistently. */

  g_debug ("   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_periods,"
                            " scanner, schedule_next_time,"
                            " config_location, target_location,"
                            " schedule_location, scanner_location,"
                            " hosts_ordering",
                            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;

  g_debug ("   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 deleted, 1 delete requested, 2 task is hidden, 3 failed to find
 *         task, 99 permission denied, -1 error, -5 scanner is down, -7 no CA
 *         cert.
 */
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. */

  g_debug ("   request delete task %s\n", task_id);

  sql_begin_immediate ();

  if (acl_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;
      case -7:   /* No CA cert. */
        sql_rollback ();
        return -5;
        break;
    }

  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)
{
  g_debug ("   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;

  g_debug ("   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, 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 Fixes the DST offset in schedule_next_time of tasks.
 *
 * @return changes  The number of tasks updated.
 */
int
cleanup_schedule_times ()
{
  sql ("UPDATE tasks"
        " SET schedule_next_time"
        "   = (SELECT next_time (first_time,"
        "                        period,"
        "                        period_months,"
        "                        timezone)"
        "      FROM schedules"
        "      WHERE schedules.id = tasks.schedule)"
        " WHERE schedule_next_time != 0"
        "   AND schedule_next_time"
        "         = (SELECT next_time (first_time,"
        "                              period,"
        "                              period_months)"
        "              FROM schedules"
        "             WHERE schedules.id = tasks.schedule)"
        "   AND schedule_next_time"
        "         != (SELECT next_time (first_time,"
        "                               period,"
        "                               period_months,"
        "                               timezone)"
        "              FROM schedules"
        "             WHERE schedules.id = tasks.schedule);");

  return sql_changes();
}

/**
 * @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 (acl_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 (acl_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)
{
  return manage_count_hosts_max (given_hosts,
                                 exclude_hosts,
                                 manage_max_hosts ());
}

/**
 * @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.
 *
 * May come in values such as 100/foo and 100/foo (IANA: bar).
 * 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 (g_str_has_prefix (port, "cpe:"))
    return 0;

  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 Set login data for a target.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 * @param[in]  credential     The credential or 0 to remove.
 * @param[in]  port           The port to authenticate at with credential.
 *
 * @return  0 on success, -1 on error, 1 target not found, 99 permission denied.
 */
static int
set_target_login_data (target_t target, const char* type,
                       credential_t credential, int port)
{
  gchar *quoted_type;

  if (current_credentials.uuid
      && (acl_user_may ("modify_target") == 0))
    return 99;

  if (type == NULL)
    return -1;

  if (target == 0)
    return 1;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT count (*) FROM targets_login_data"
               " WHERE target = %llu AND type = '%s';",
               target, quoted_type))
    {
      if (credential == 0)
        {
          sql ("DELETE FROM targets_login_data"
               " WHERE target = '%llu' AND type = '%s';",
               target, quoted_type);
        }
      else
        {
          sql ("UPDATE targets_login_data"
               " SET credential = %llu, port = %d"
               " WHERE target = %llu AND type = '%s';",
               credential, port, target, quoted_type);
        }
    }
  else if (credential)
    {
      sql ("INSERT INTO targets_login_data (target, type, credential, port)"
            " VALUES (%llu, '%s', %llu, %i)",
            target, quoted_type, credential, port);
    }

  g_free (quoted_type);
  return 0;
}

/**
 * @brief Get a credential from a target.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
credential_t
target_credential (target_t target, const char* type)
{
  gchar *quoted_type;
  credential_t credential;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  sql_int64 (&credential,
             "SELECT credential FROM targets_login_data"
             " WHERE target = %llu AND type = '%s';",
             target, quoted_type);

  g_free (quoted_type);

  return credential;
}

/**
 * @brief Get a credential from a target in the trashcan.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
credential_t
trash_target_credential (target_t target, const char* type)
{
  gchar *quoted_type;
  credential_t credential;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_trash_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  sql_int64 (&credential,
             "SELECT credential FROM targets_trash_login_data"
             " WHERE target = %llu AND type = '%s';",
             target, quoted_type);

  g_free (quoted_type);

  return credential;
}

/**
 * @brief Get whether a credential of a trash target is in trashcan.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
int
trash_target_credential_location (target_t target, const char* type)
{
  gchar *quoted_type;
  int location;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_trash_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  location = sql_int ("SELECT credential_location FROM targets_trash_login_data"
                      " WHERE target = %llu AND type = '%s';",
                      target, quoted_type);

  g_free (quoted_type);

  return location;
}

/**
 * @brief Get a login port from a target.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
int
target_login_port (target_t target, const char* type)
{
  gchar *quoted_type;
  int port;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  port = sql_int ("SELECT port FROM targets_login_data"
                  " WHERE target = %llu AND type = '%s';",
                  target, quoted_type);

  g_free (quoted_type);

  return port;
}

/**
 * @brief Get a port from a target in the trashcan.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
int
trash_target_login_port (target_t target, const char* type)
{
  gchar *quoted_type;
  int port;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_trash_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  port = sql_int ("SELECT port FROM targets_trash_login_data"
                  " WHERE target = %llu AND type = '%s';",
                  target, quoted_type);

  g_free (quoted_type);

  return port;
}

/**
 * @brief Create a target.
 *
 * @param[in]   name            Name of target.
 * @param[in]   asset_hosts_filter  Asset host filter to select hosts.
 *                                  Overrides \p hosts and \p exclude_hosts.
 * @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_credential  SSH credential.
 * @param[in]   ssh_port        Port for SSH login.
 * @param[in]   smb_credential  SMB credential.
 * @param[in]   esxi_credential ESXi credential.
 * @param[in]   snmp_credential SNMP 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,
 *         8 invalid SSH credential type, 9 invalid SMB credential type,
 *         10 invalid ESXi credential type, 11 invalid SNMP credential type,
 *         99 permission denied, -1 error.
 */
int
create_target (const char* name, const char* asset_hosts_filter,
               const char* hosts, const char* exclude_hosts,
               const char* comment, const char* port_list_id,
               const char* port_range, credential_t ssh_credential,
               const char* ssh_port, credential_t smb_credential,
               credential_t esxi_credential, credential_t snmp_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, *chosen_hosts;
  port_list_t port_list;
  int ret, alive_test, max;
  target_t new_target;

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

  if (asset_hosts_filter)
    {
      iterator_t asset_hosts;
      int previous;
      get_data_t get;
      GString *buffer;

      memset (&get, 0, sizeof (get));
      get.filter = g_strdup (asset_hosts_filter);
      init_asset_host_iterator (&asset_hosts, &get);
      g_free (get.filter);
      previous = 0;
      buffer = g_string_new ("");
      while (next (&asset_hosts))
        {
          g_string_append_printf (buffer,
                                  "%s%s",
                                  previous ? ", " : "",
                                  get_iterator_name (&asset_hosts));
          previous = 1;
        }
      cleanup_iterator (&asset_hosts);
      chosen_hosts = g_string_free (buffer, FALSE);
      quoted_exclude_hosts = g_strdup ("");

      g_debug ("asset chosen_hosts: %s\n", chosen_hosts);
    }
  else
    {
      chosen_hosts = g_strdup (hosts);
      quoted_exclude_hosts = exclude_hosts ? sql_quote (exclude_hosts)
                                           : g_strdup ("");

      g_debug ("manual chosen_hosts: %s\n", chosen_hosts);
    }

  max = manage_count_hosts (chosen_hosts, quoted_exclude_hosts);
  if (max <= 0)
    {
      g_free (chosen_hosts);
      g_free (quoted_exclude_hosts);
      g_free (quoted_name);
      sql_rollback ();
      return 2;
    }
  clean = clean_hosts (chosen_hosts, &max);
  g_free (chosen_hosts);
  if (max > max_hosts)
    {
      g_free (quoted_exclude_hosts);
      g_free (quoted_name);
      g_free (clean);
      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);
          g_free (quoted_exclude_hosts);
          g_free (quoted_hosts);
          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);
          g_free (quoted_exclude_hosts);
          g_free (quoted_hosts);
          sql_rollback ();
          return ret;
        }
    }

  if (ssh_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_quote (comment);
  else
    quoted_comment = sql_quote ("");

  sql ("INSERT INTO targets"
       " (uuid, name, owner, hosts, exclude_hosts, comment, "
       "  port_list, 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', '%s', %i,"
       " m_now (), m_now ());",
        quoted_name, current_credentials.uuid,
        quoted_hosts, quoted_exclude_hosts, quoted_comment, port_list,
        reverse_lookup_only, reverse_lookup_unify, alive_test);

  new_target = sql_last_insert_id ();
  if (target)
    *target = new_target;

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_hosts);
  g_free (quoted_exclude_hosts);

  if (ssh_credential)
    {
      gchar *type = credential_type (ssh_credential);
      if (strcmp (type, "usk") && strcmp (type, "up"))
        {
          sql_rollback ();
          g_free (quoted_ssh_port);
          return 8;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'ssh', %llu, %s);",
           new_target, ssh_credential, quoted_ssh_port);
    }
  g_free (quoted_ssh_port);

  if (smb_credential)
    {
      gchar *type = credential_type (smb_credential);
      if (strcmp (type, "up"))
        {
          sql_rollback ();
          return 9;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'smb', %llu, %s);",
           new_target, smb_credential, "0");
    }

  if (esxi_credential)
    {
      gchar *type = credential_type (esxi_credential);
      if (strcmp (type, "up"))
        {
          sql_rollback ();
          return 10;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'esxi', %llu, %s);",
           new_target, esxi_credential, "0");
    }

  if (snmp_credential)
    {
      gchar *type = credential_type (snmp_credential);
      if (strcmp (type, "snmp"))
        {
          sql_rollback ();
          return 11;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'snmp', %llu, %s);",
           new_target, snmp_credential, "0");
    }

  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)
{
  int ret;
  target_t target;
  assert (new_target);

  ret = copy_resource ("target", name, comment, target_id,
                       "hosts, exclude_hosts, port_list, reverse_lookup_only,"
                       " reverse_lookup_unify, alive_test",
                       1, new_target);
  if (ret)
    return ret;

  if (find_resource ("target", target_id, &target))
    return -1;

  sql ("INSERT INTO targets_login_data (target, type, credential, port)"
       " SELECT %llu, type, credential, port"
       "   FROM targets_login_data"
       "  WHERE target = %llu;",
       *new_target, target);

  return 0;
}

/**
 * @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;
  target_t trash_target;

  sql_begin_immediate ();

  if (acl_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_login_data WHERE target = %llu;", target);
      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,"
           "  port_list, port_list_location,"
           "  reverse_lookup_only, reverse_lookup_unify, alive_test,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, hosts, exclude_hosts, comment,"
           "        port_list, " G_STRINGIFY (LOCATION_TABLE) ","
           "        reverse_lookup_only, reverse_lookup_unify, alive_test,"
           "        creation_time, modification_time"
           " FROM targets WHERE id = %llu;",
           target);

      trash_target = sql_last_insert_id ();

      /* Copy login data */
      sql ("INSERT INTO targets_trash_login_data"
           " (target, type, credential, port, credential_location)"
           " SELECT %llu, type, credential, port, "
           G_STRINGIFY (LOCATION_TABLE)
           "   FROM targets_login_data WHERE target = %llu;",
           trash_target, 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_login_data WHERE target = %llu;", target);
  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_credential_id  SSH credential.
 * @param[in]   ssh_port        Port for SSH login.
 * @param[in]   smb_credential_id  SMB credential.
 * @param[in]   esxi_credential_id  ESXi credential.
 * @param[in]   snmp_credential_id  SNMP 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, 17 failed to find SNMP cred,
 *         18 invalid SSH credential type, 19 invalid SMB credential type,
 *         20 invalid ESXi credential type, 21 invalid SNMP credential type,
 *         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_credential_id,
               const char *ssh_port, const char *smb_credential_id,
               const char *esxi_credential_id, const char* snmp_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 (acl_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_list = %llu,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           port_list,
           target);
    }

  if (ssh_credential_id)
    {
      credential_t ssh_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      ssh_credential = 0;
      if (strcmp (ssh_credential_id, "0"))
        {
          int port_int;
          gchar *type;

          if (find_credential_with_permission (ssh_credential_id,
                                               &ssh_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (ssh_credential == 0)
            {
              sql_rollback ();
              return 7;
            }

          if (ssh_port && strcmp (ssh_port, "0") && strcmp (ssh_port, ""))
            {
              if (validate_port (ssh_port))
                {
                  sql_rollback ();
                  return 5;
                }
              port_int = atoi (ssh_port);
            }
          else
            port_int = 22;

          type = credential_type (ssh_credential);
          if (strcmp (type, "up") && strcmp (type, "usk"))
            {
              sql_rollback ();
              return 18;
            }
          g_free (type);

          set_target_login_data (target, "ssh", ssh_credential, port_int);
        }
      else
        set_target_login_data (target, "ssh", 0, 0);
    }

  if (smb_credential_id)
    {
      credential_t smb_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      smb_credential = 0;
      if (strcmp (smb_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (smb_credential_id,
                                               &smb_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (smb_credential == 0)
            {
              sql_rollback ();
              return 7;
            }

          type = credential_type (smb_credential);
          if (strcmp (type, "up"))
            {
              sql_rollback ();
              return 19;
            }
          g_free (type);

          set_target_login_data (target, "smb", smb_credential, 0);
        }
      else
        set_target_login_data (target, "smb", 0, 0);
    }

  if (esxi_credential_id)
    {
      credential_t esxi_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      esxi_credential = 0;
      if (strcmp (esxi_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (esxi_credential_id,
                                               &esxi_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (esxi_credential == 0)
            {
              sql_rollback ();
              return 16;
            }

          type = credential_type (esxi_credential);
          if (strcmp (type, "up"))
            {
              sql_rollback ();
              return 20;
            }
          g_free (type);

          set_target_login_data (target, "esxi", esxi_credential, 0);
        }
      else
        set_target_login_data (target, "esxi", 0, 0);
    }

  if (snmp_credential_id)
    {
      credential_t snmp_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      snmp_credential = 0;
      if (strcmp (snmp_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (snmp_credential_id,
                                               &snmp_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (snmp_credential == 0)
            {
              sql_rollback ();
              return 17;
            }

          type = credential_type (snmp_credential);
          if (strcmp (type, "snmp"))
            {
              sql_rollback ();
              return 21;
            }
          g_free (type);

          set_target_login_data (target, "snmp", snmp_credential, 0);
        }
      else
        set_target_login_data (target, "snmp", 0, 0);
    }

  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);
          g_free (clean);
          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", "snmp_credential",   \
   NULL }

/**
 * @brief Target iterator columns.
 */
#define TARGET_ITERATOR_COLUMNS                             \
 {                                                          \
   GET_ITERATOR_COLUMNS (targets),                          \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                  \
   { "target_credential (id, 0, CAST ('ssh' AS text))",     \
     NULL,                                                  \
     KEYWORD_TYPE_INTEGER },                                \
   { "target_login_port (id, 0, CAST ('ssh' AS text))",     \
     "ssh_port",                                            \
     KEYWORD_TYPE_INTEGER },                                \
   { "target_credential (id, 0, CAST ('smb' AS text))",     \
     NULL,                                                  \
     KEYWORD_TYPE_INTEGER },                                \
   { "port_list", NULL, KEYWORD_TYPE_INTEGER },             \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                     \
   {                                                        \
     "(SELECT uuid FROM port_lists"                         \
     " WHERE port_lists.id = port_list)",                   \
     NULL,                                                  \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   {                                                        \
     "(SELECT name FROM port_lists"                         \
     " WHERE port_lists.id = port_list)",                   \
     "port_list",                                           \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "exclude_hosts", NULL, KEYWORD_TYPE_STRING },          \
   { "reverse_lookup_only", NULL, KEYWORD_TYPE_INTEGER },   \
   { "reverse_lookup_unify", NULL, KEYWORD_TYPE_INTEGER },  \
   { "alive_test", NULL, KEYWORD_TYPE_INTEGER },            \
   { "target_credential (id, 0, CAST ('esxi' AS text))",    \
     NULL,                                                  \
     KEYWORD_TYPE_INTEGER },                                \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "target_credential (id, 0, CAST ('snmp' AS text))",    \
     NULL,                                                  \
     KEYWORD_TYPE_INTEGER },                                \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                     \
   {                                                        \
     "(SELECT name FROM credentials"                        \
     " WHERE credentials.id"                                \
     "       = target_credential (targets.id, 0,"           \
     "                            CAST ('ssh' AS text)))",  \
     "ssh_credential",                                      \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   {                                                        \
     "(SELECT name FROM credentials"                        \
     " WHERE credentials.id"                                \
     "       = target_credential (targets.id, 0,"           \
     "                            CAST ('smb' AS text)))",  \
     "smb_credential",                                      \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   {                                                        \
     "(SELECT name FROM credentials"                        \
     " WHERE credentials.id"                                \
     "       = target_credential (targets.id, 0,"           \
     "                            CAST ('esxi' AS text)))", \
     "esxi_credential",                                     \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   {                                                        \
     "(SELECT name FROM credentials"                        \
     " WHERE credentials.id"                                \
     "       = target_credential (targets.id, 0,"           \
     "                            CAST ('snmp' AS text)))", \
     "snmp_credential",                                     \
     KEYWORD_TYPE_STRING                                    \
   },                                                       \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                  \
   { "max_hosts (hosts, exclude_hosts)",                    \
     "ips",                                                 \
     KEYWORD_TYPE_INTEGER },                                \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                     \
 }

/**
 * @brief Target iterator columns for trash case.
 */
#define TARGET_ITERATOR_TRASH_COLUMNS                               \
 {                                                                  \
   GET_ITERATOR_COLUMNS (targets_trash),                            \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                          \
   { "target_credential (id, 1, CAST ('ssh' AS text))",             \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "target_login_port (id, 1, CAST ('ssh' AS text))",             \
     "ssh_port",                                                    \
     KEYWORD_TYPE_INTEGER },                                        \
   { "target_credential (id, 1, CAST ('smb' AS text))",             \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "port_list", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "trash_target_credential_location (id, CAST ('ssh' AS text))", \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "trash_target_credential_location (id, CAST ('smb' AS text))", \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   {                                                                \
     "(CASE"                                                        \
     " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH)     \
     " THEN (SELECT uuid FROM port_lists_trash"                     \
     "       WHERE port_lists_trash.id = port_list)"                \
     " ELSE (SELECT uuid FROM port_lists"                           \
     "       WHERE port_lists.id = port_list)"                      \
     " END)",                                                       \
     NULL,                                                          \
     KEYWORD_TYPE_STRING                                            \
   },                                                               \
   {                                                                \
     "(CASE"                                                        \
     " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH)     \
     " THEN (SELECT name FROM port_lists_trash"                     \
     "       WHERE port_lists_trash.id = port_list)"                \
     " ELSE (SELECT name FROM port_lists"                           \
     "       WHERE port_lists.id = port_list)"                      \
     " END)",                                                       \
     NULL,                                                          \
     KEYWORD_TYPE_STRING                                            \
   },                                                               \
   { "port_list_location = " G_STRINGIFY (LOCATION_TRASH),          \
     NULL,                                                          \
     KEYWORD_TYPE_STRING },                                         \
   { "exclude_hosts", NULL, KEYWORD_TYPE_STRING },                  \
   { "reverse_lookup_only", NULL, KEYWORD_TYPE_INTEGER },           \
   { "reverse_lookup_unify", NULL, KEYWORD_TYPE_INTEGER },          \
   { "alive_test", NULL, KEYWORD_TYPE_INTEGER },                    \
   { "target_credential (id, 1, CAST ('esxi' AS text))",            \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "trash_target_credential_location (id, CAST ('esxi' AS text))",\
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "target_credential (id, 1, CAST ('snmp' AS text))",            \
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { "trash_target_credential_location (id, CAST ('snmp' AS text))",\
     NULL,                                                          \
     KEYWORD_TYPE_INTEGER },                                        \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                             \
 }

/**
 * @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 " ACL_USER_OWNS () ";",
                 columns,
                 target,
                 current_credentials.uuid);

  g_free (columns);
}

/**
 * @brief Initialise a target iterator, given a single target.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  target     Single target to iterate.
 */
void
init_target_iterator_one (iterator_t* iterator, target_t target)
{
  get_data_t get;

  assert (target);

  memset (&get, '\0', sizeof (get));
  get.id = target_uuid (target);
  get.filter = "owner=any permission=get_targets";

  init_target_iterator (iterator, &get);
}

/**
 * @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 Get the SNMP LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_snmp_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 16);
  return ret;
}

/**
 * @brief Get the SNMP LSC credential location from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_snmp_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 17);
  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 = 0;

  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)
{
  int port = target_login_port (target, "ssh");
  return port ? g_strdup_printf ("%d", port) : NULL;
}

/**
 * @brief Return the SSH credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return SSH credential if any, else 0.
 */
credential_t
target_ssh_credential (target_t target)
{
  return target_credential (target, "ssh");
}

/**
 * @brief Return the SMB credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return SMB credential if any, else 0.
 */
credential_t
target_smb_credential (target_t target)
{
  return target_credential (target, "smb");
}

/**
 * @brief Return the ESXi credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return ESXi credential if any, else 0.
 */
credential_t
target_esxi_credential (target_t target)
{
  return target_credential (target, "esxi");
}

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

/**
 * @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 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 = acl_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 (acl_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.
 * @param[in]  config_type  Config type.
 *
 * @return 0 success, -1 error, -4 input error.
 */
static int
config_insert_preferences (config_t config,
                           const array_t* preferences /* preference_t. */,
                           const char* config_type)
{
  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
                && (config_type == NULL || strcmp (config_type, "0") == 0))
              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;
            gchar *quoted_default, *quoted_preference_hr_name;

            /* Presume NVT or OSP preference. */

            if (preference->nvt_name == NULL
                && (config_type == NULL || strcmp (config_type, "0") == 0))
              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 = preference->nvt_name
                                ? sql_quote (preference->nvt_name) : NULL;
            quoted_preference_name = sql_quote (preference->name);
            quoted_preference_hr_name
              = preference->hr_name
                  ? sql_quote (preference->hr_name)
                  : NULL;
            quoted_type
              = g_str_has_prefix (preference->type, "osp_")
                  ? sql_quote (preference->type + strlen ("osp_"))
                  : sql_quote (preference->type);
            quoted_value = sql_quote (value->str);
            g_string_free (value, TRUE);
            quoted_default = preference->default_value
                              ? sql_quote (preference->default_value)
                              : NULL;

            if (config_type == NULL || strcmp (config_type, "0") == 0)
              {
                /* NVT preference */
                /* 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);
              }
            else
              {
                /* OSP preference */
                sql ("INSERT into config_preferences"
                     " (config, type, name, value, default_value, hr_name)"
                     " VALUES (%llu, '%s', '%s', '%s', '%s', '%s');",
                     config,
                     quoted_type,
                     quoted_preference_name,
                     quoted_value,
                     quoted_default,
                     quoted_preference_hr_name
                      ? quoted_preference_name : quoted_preference_hr_name);
              }
            g_free (quoted_nvt_name);
            g_free (quoted_preference_name);
            g_free (quoted_type);
            g_free (quoted_value);
            g_free (quoted_default);
            g_free (quoted_preference_hr_name);
          }
        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[in]   config_type    Config type.
 * @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. */,
               const char* config_type, config_t *config, char **name)
{
  int ret;
  gchar *quoted_comment, *candidate_name, *quoted_candidate_name;
  gchar *quoted_type;
  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 (acl_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);
  quoted_type = config_type ? sql_quote (config_type) : g_strdup ("0");

  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', '%s', m_now (), m_now ());",
           quoted_candidate_name,
           current_credentials.uuid,
           selector_uuid,
           quoted_comment,
           quoted_type);
      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', '', '%s', m_now (), m_now ());",
         quoted_candidate_name,
         current_credentials.uuid,
         selector_uuid,
         quoted_type);
  g_free (quoted_candidate_name);
  g_free (quoted_type);

  /* Insert the selectors into the nvt_selectors table. */

  *config = sql_last_insert_id ();

  if (config_type == NULL || strcmp (config_type, "0") == 0)
    {
      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, config_type)))
    {
      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 Insert an OSP parameter into a config if not already present.
 *
 * @param[in]   param   OSP parameter to insert.
 * @param[in]   config  Config to insert parameter into.
 *
 * @return 1 if added, 0 otherwise.
 */
static int
insert_osp_parameter (osp_param_t *param, config_t config)
{
  char *param_id, *param_name, *param_type, *param_def, *param_value = NULL;
  int ret = 0;

  if (!param)
    return ret;
  param_id = sql_quote (osp_param_id (param));
  param_name = sql_quote (osp_param_name (param));
  param_type = sql_quote (osp_param_type_str (param));
  if (!strcmp (param_type, "selection"))
    {
      char **strarray = g_strsplit (osp_param_default (param), "|", 2);

      param_value = sql_quote (strarray[0] ?: "");
      param_def = sql_quote (strarray[1] ?: param_value);
      g_strfreev (strarray);
    }
  else
    param_def = sql_quote (osp_param_default (param));
  if (sql_int ("SELECT count(*) FROM config_preferences"
               " WHERE config = %llu AND name = '%s' AND type = '%s'"
               " AND default_value = '%s';",
               config, param_id, param_type, param_def) == 0)
    {
      sql ("INSERT INTO config_preferences (config, name, type, value,"
           " default_value, hr_name)"
           " VALUES (%llu, '%s', '%s', '%s', '%s', '%s')",
           config , param_id, param_type, param_value ?: param_def,
           param_def, param_name);
      ret = 1;
    }
  g_free (param_name);
  g_free (param_id);
  g_free (param_type);
  g_free (param_def);
  g_free (param_value);
  return ret;
}

/**
 * @brief Create a config from an OSP scanner.
 *
 * @param[in]   scanner_id  UUID of scanner to create config from.
 * @param[in]   name        Name for config.
 * @param[in]   comment     Comment for config.
 * @param[out]  uuid        Config UUID, on success.
 *
 * @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 (acl_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, scanner, creation_time, modification_time)"
       " VALUES (make_uuid (), '%s',"
       " (SELECT id FROM users WHERE users.uuid = '%s'),"
       " '', '%s', 1, %llu, m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment, scanner);
  g_free (quoted_name);
  g_free (quoted_comment);
  config = sql_last_insert_id ();
  *uuid = config_uuid (config);

  element = params;
  while (element)
    {
      insert_osp_parameter (element->data, config);
      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.
 *
 * @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 Return the scanner associated with a config, if any.
 *
 * @param[in]  config   Config.
 *
 * @return Scanner ID if found, 0 otherwise.
 */
scanner_t
config_scanner (config_t config)
{
  scanner_t scanner;

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

/**
 * @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 == 0 && stype == SCANNER_TYPE_OMP)
    return 1;
  if (ctype == 1 && stype == SCANNER_TYPE_OSP)
    return 1;

  return 0;
}

/**
 * @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 0 if config and scanner types match, 1 do not match, 2 failed to
 *         find config, 3 failed to find scanner, -1 error.
 */
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 0;

  if (strcmp (config_id, "0"))
    {
      if (find_config_with_permission (config_id, &config, "get_configs"))
        return -1;
      if (config == 0)
        return 2;
    }
  else
    config = task_config (task);

  if (strcmp (scanner_id, "0"))
    {
      if (find_scanner_with_permission (scanner_id, &scanner, "get_scanners"))
        return -1;
      if (scanner == 0)
        return 3;
    }
  else
    scanner = task_scanner (task);

  stype = scanner_type (scanner);

  /* CVE Scanner. */
  if (stype == SCANNER_TYPE_CVE)
    return strcmp (scanner_id, "0")
            /* Selecting the CVE Scanner will clear the config. */
            ? 0
            /* CVE Scanner is currently selected, so the only option is to
             * leave the config alone. */
            : (config ? 1 : 0);

  ctype = config_type (config);
  /* OSP Scanner with OSP config. */
  if (stype == SCANNER_TYPE_OSP && ctype == 1)
    return 0;

  /* OpenVAS Scanner with OpenVAS config. */
  if (stype == SCANNER_TYPE_OPENVAS && ctype == 0)
    return 0;

  /* OMP Scanner with OpenVAS config. */
  if (stype == SCANNER_TYPE_OMP && ctype == 0)
    return 0;

  /* Default Scanner with OpenVAS Config. */
  if (scanner == 0 && ctype == 0)
    return 0;

  return 1;
}

/**
 * @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, scanner", 1, &new, &old);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  sql ("INSERT INTO config_preferences (config, type, name, value,"
       "                                default_value, hr_name)"
       " SELECT %llu, type, name, value, default_value, hr_name"
       " 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, 3 config is predefined, 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 3;

  sql_begin_immediate ();

  if (acl_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, scanner,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, nvt_selector, comment, family_count,"
           "        nvt_count, families_growing, nvts_growing,"
           "        type, scanner, 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, hr_name)"
           " SELECT %llu, type, name, value, default_value, hr_name"
           " 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 Update a config with a list of parameters.
 *
 * @param[in]  config       Config ID.
 * @param[in]  config_id    Config UUID.
 * @param[in]  params       List of new config parameters.
 *
 */
static void
update_config_params (config_t config, const char *config_id, GSList *params)
{
  GSList *element;
  iterator_t iterator;

  /* Remove parameters not used anymore. */
  init_iterator (&iterator,
                 "SELECT id, name, type, default_value, hr_name"
                 " FROM config_preferences"
                 " WHERE config = %llu;", config);
  while (next (&iterator))
    {
      int found = 0;

      element = params;
      while (element)
        {
          const char *name, *type, *def;

          name = osp_param_id (element->data);
          type = osp_param_type_str (element->data);
          def = osp_param_default (element->data);
          if (!strcmp (name,  iterator_string (&iterator, 1))
              && !strcmp (type, iterator_string (&iterator, 2)))
            {
              const char *iter_def = iterator_string (&iterator, 3);

              if (!strcmp (type, "selection")
                  && !strcmp (strchr (def, '|') + 1, iter_def))
                found = 1;
              else if (strcmp (type, "selection") && !strcmp (def, iter_def))
                found = 1;
              if (found)
                break;
            }
          element = element->next;
        }
      if (!found)
        {
          g_message ("Removing config preference %s from config '%s'",
                     iterator_string (&iterator, 1), config_id);
          sql ("DELETE FROM config_preferences WHERE id = %llu;",
               iterator_int64 (&iterator, 0));
        }
      else if (strcmp (osp_param_name (element->data),
                       iterator_string (&iterator, 4)))
        {
          // Update hr_name (= OSP name)
          gchar *quoted_name;
          quoted_name = sql_quote (osp_param_name (element->data));
          g_message ("Updating name of config preference %s in config '%s'",
                     iterator_string (&iterator, 1), config_id);
          sql ("UPDATE config_preferences SET hr_name='%s' WHERE id = %llu;",
               quoted_name,
               iterator_int64 (&iterator, 0));
          g_free (quoted_name);
        }
    }
  cleanup_iterator (&iterator);
  /* Insert new parameters. */
  element = params;
  while (element)
    {
      if (insert_osp_parameter (element->data, config))
        g_message ("Adding config preference %s to config '%s'",
                   osp_param_id (element->data), config_id);
      element = element->next;
    }
}

/**
 * @brief Synchronize a config.
 *
 * @param[in]  config_id  UUID of config.
 *
 * @return 0 success, 1 failed to find config, 2 config not of OSP type,
 *         3 config has no scanner, 4 couldn't get params from scanner,
 *         99 permission denied, -1 error.
 */
int
sync_config (const char *config_id)
{
  config_t config = 0;
  GSList *params;
  scanner_t scanner;

  assert (config_id);
  assert (current_credentials.uuid);

  sql_begin_immediate ();

  if (acl_user_may ("modify_config") == 0)
    {
      sql_rollback ();
      return 99;
    }
  if (find_config_with_permission (config_id, &config, "modify_config"))
    {
      sql_rollback ();
      return -1;
    }
  if (config == 0)
    {
      sql_rollback ();
      return 1;
    }
  if (config_type (config) != SCANNER_TYPE_OSP)
    {
      sql_rollback ();
      return 2;
    }
  scanner = config_scanner (config);
  if (!scanner)
    {
      sql_rollback ();
      return 3;
    }
  params = get_scanner_params (scanner);
  if (!params)
    {
      sql_rollback ();
      return 4;
    }
  update_config_params (config, config_id, params);

  sql_commit ();
  while (params)
    {
      osp_param_free (params->data);
      params = g_slist_remove_link (params, params);
    }
  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, KEYWORD_TYPE_STRING },                             \
   { "family_count", "families_total", KEYWORD_TYPE_INTEGER },                \
   { "nvt_count", "nvts_total", KEYWORD_TYPE_INTEGER},                        \
   { "families_growing", "families_trend", KEYWORD_TYPE_INTEGER},             \
   { "nvts_growing", "nvts_trend", KEYWORD_TYPE_INTEGER },                    \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                                    \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Scan config iterator columns for trash case.
 */
#define CONFIG_ITERATOR_TRASH_COLUMNS                                         \
 {                                                                            \
   GET_ITERATOR_COLUMNS (configs_trash),                                      \
   { "nvt_selector", NULL, KEYWORD_TYPE_STRING },                             \
   { "family_count", "families_total", KEYWORD_TYPE_INTEGER },                \
   { "nvt_count", "nvts_total", KEYWORD_TYPE_INTEGER},                        \
   { "families_growing", "families_trend", KEYWORD_TYPE_INTEGER},             \
   { "nvts_growing", "nvts_trend", KEYWORD_TYPE_INTEGER },                    \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                                    \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @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 " ACL_USER_OWNS ()
                           " 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 " ACL_USER_OWNS ()
                           " 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 can be modified.
 *
 * @param[in]  config  Config.
 *
 * @return Always 1.
 */
int
config_writable (config_t config)
{
  return 1;
}

/**
 * @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.
 */
void
init_preference_iterator (iterator_t* iterator, config_t config)
{
  gchar* sql;

  sql = g_strdup_printf ("SELECT name, value, type, default_value, hr_name"
                         " 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.
 *
 * Note: For OSP results this corresponds to the "id" field in OSP, not "name".
 *
 * @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 Get the hr_name from a preference iterator.
 *
 * Note: This corresponds to the "name" in OSP and is not defined for classic
 *  OpenVAS config preferences.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The hr_name of the preference iterator, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (preference_iterator_hr_name, 4);

/**
 * @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, comment and scanner of a config.
 *
 * @param[in]  config       Config.
 * @param[in]  name         New name, not updated if NULL.
 * @param[in]  comment      New comment, not updated if NULL.
 * @param[in]  scanner_id   UUID of new scanner, not updated if NULL.
 *
 * @return 0 success, 1 config with new name exists already, 2 scanner doesn't
 *         exist, 3 modification not allowed while config is in use, -1 error.
 */
int
manage_set_config (config_t config, const char*name, const char *comment,
                   const char *scanner_id)
{
  assert (current_credentials.uuid);
  sql_begin_immediate ();
  if (name)
    {
      gchar *quoted_name;
      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);
    }
  if (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);
    }
  if (scanner_id)
    {
      if (config_in_use (config))
        {
          sql_rollback ();
          return 3;
        }
      scanner_t scanner = 0;

      if (find_scanner_with_permission (scanner_id, &scanner, "modify_config")
          || scanner == 0)
        {
          sql_rollback ();
          return 2;
        }
      sql ("UPDATE configs SET scanner = %llu, modification_time = m_now ()"
           " WHERE id = %llu;", scanner, config);
    }
  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 = acl_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 Counter for chunking in insert_nvts_list (--update).
 */
int chunk_count = 0;

/**
 * @brief Size of chunk for insert_nvts_list (--update).
 */
#define CHUNK_SIZE 100

/**
 * @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;
  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)
    {
      if (chunk_count == 0)
        {
          sql_begin_exclusive ();
          chunk_count++;
        }
      else if (chunk_count == CHUNK_SIZE)
        chunk_count = 0;
      else
        chunk_count++;
      sql ("DELETE FROM nvt_cves where oid = '%s';", nvti_oid (nvti));
      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_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 ("INSERT into nvts (oid, version, name, 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', %i, '%s', '%s', %i, %i, '%s', '%s', %d, '%s');",
       nvti_oid (nvti), quoted_version, quoted_name,
       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 && (chunk_count == 0))
    sql_commit ();

  g_free (quoted_version);
  g_free (quoted_name);
  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", "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", KEYWORD_TYPE_STRING },                                 \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                                     \
   { "oid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "version", NULL, KEYWORD_TYPE_STRING },                                \
   { "name", NULL, KEYWORD_TYPE_STRING },                                   \
   { "copyright", NULL, KEYWORD_TYPE_STRING },                              \
   { "cve", NULL, KEYWORD_TYPE_STRING },                                    \
   { "bid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "xref", NULL, KEYWORD_TYPE_STRING },                                   \
   { "tag", NULL, KEYWORD_TYPE_STRING },                                    \
   { "category", NULL, KEYWORD_TYPE_STRING },                               \
   { "family", NULL, KEYWORD_TYPE_STRING },                                 \
   { "cvss_base", NULL, KEYWORD_TYPE_DOUBLE },                              \
   { "cvss_base", "severity", KEYWORD_TYPE_DOUBLE },                        \
   { "cvss_base", "cvss", KEYWORD_TYPE_DOUBLE },                            \
   { "qod", NULL, KEYWORD_TYPE_INTEGER },                                   \
   { "qod_type", NULL, KEYWORD_TYPE_STRING },                               \
   { "solution_type", NULL, KEYWORD_TYPE_STRING },                          \
   { "tag", "script_tags", KEYWORD_TYPE_STRING},                            \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @brief NVT iterator columns.
 */
#define NVT_ITERATOR_COLUMNS_NVTS                                           \
 {                                                                          \
   GET_ITERATOR_COLUMNS_PREFIX ("nvts."),                                   \
   { "''", "_owner", KEYWORD_TYPE_STRING },                                 \
   { "0", NULL, KEYWORD_TYPE_STRING },                                      \
   { "oid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "version", NULL, KEYWORD_TYPE_STRING },                                \
   { "nvts.name", NULL, KEYWORD_TYPE_STRING },                              \
   { "copyright", NULL, KEYWORD_TYPE_STRING },                              \
   { "cve", NULL, KEYWORD_TYPE_STRING },                                    \
   { "bid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "xref", NULL, KEYWORD_TYPE_STRING },                                   \
   { "tag", NULL, KEYWORD_TYPE_STRING },                                    \
   { "category", NULL, KEYWORD_TYPE_STRING },                               \
   { "nvts.family", NULL, KEYWORD_TYPE_STRING },                            \
   { "cvss_base", NULL, KEYWORD_TYPE_DOUBLE },                              \
   { "cvss_base", "severity", KEYWORD_TYPE_DOUBLE },                        \
   { "cvss_base", "cvss", KEYWORD_TYPE_DOUBLE },                            \
   { "qod", NULL, KEYWORD_TYPE_INTEGER },                                   \
   { "qod_type", NULL, KEYWORD_TYPE_STRING },                               \
   { "solution_type", NULL, KEYWORD_TYPE_STRING },                          \
   { "tag", "script_tags", KEYWORD_TYPE_STRING },                           \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @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]  family      Family to limit selection to.  NULL for all NVTs.
 *                         Overridden by \arg config.
 * @param[in]  category    Category to limit selection to.  NULL for all.
 * @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 %s '%%%s%%'"
                 " ORDER BY %s %s;",
                 nvt_iterator_columns (),
                 sql_ilike_op (),
                 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 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 + 3);

/**
 * @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 + 4);

/**
 * @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 + 5);

/**
 * @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 + 6);

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

/**
 * @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 + 8);
  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 + 9);

/**
 * @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 + 10);

/**
 * @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 + 13);

/**
 * @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 + 14);

/**
 * @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 + 15);

/**
 * @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 Insert an NVT from an nvti structure.
 *
 * @param[in] nvti          nvti_t to insert in nvts table.
 * @param[in] mode_pointer  Mode.  -1 updating, -2 rebuilding.
 *
 */
static void
insert_nvt_from_nvti (gpointer nvti, gpointer mode_pointer)
{
  int mode;

  if (nvti == NULL)
    return;

  if (progress)
    progress ();

  mode = GPOINTER_TO_INT (mode_pointer);

  make_nvt_from_nvti (nvti, mode == -1);
}

/**
 * @brief Insert a NVT preferences.
 *
 * @param[in] nvt_preference  Preference.
 * @param[in] mode_pointer    Mode.  -1 updating, -2 rebuilding.
 *
 */
static void
insert_nvt_preference (gpointer nvt_preference, gpointer mode_pointer)
{
  int mode;
  preference_t *preference;

  if (nvt_preference == NULL)
    return;

  if (progress)
    progress ();

  mode = GPOINTER_TO_INT (mode_pointer);
  preference = (preference_t*) nvt_preference;
  manage_nvt_preference_add (preference->name, preference->value, mode == -1);
}

/**
 * @brief Inserts NVTs in DB from a list of nvti_t structures.
 *
 * @param[in]  nvts_list     List of nvts to be inserted.
 * @param[in]  mode          -1 updating, -2 rebuilding.
 */
static void
insert_nvts_list (GList *nvts_list, int mode)
{
  chunk_count = 0;
  g_list_foreach (nvts_list, insert_nvt_from_nvti, GINT_TO_POINTER (mode));
  if ((mode == -1) && (chunk_count > 0))
    sql_commit ();
}

/**
 * @brief Inserts NVT preferences in DB from a list of nvt_preference_t structures.
 *
 * @param[in]  nvt_preferences_list     List of nvts to be inserted.
 * @param[in]  mode                     -1 updating, -2 rebuilding.
 */
static void
insert_nvt_preferences_list (GList *nvt_preferences_list, int mode)
{
  g_list_foreach (nvt_preferences_list, insert_nvt_preference,
                  GINT_TO_POINTER (mode));
}

/**
 * @brief Check for new NVTs after an update.
 */
static void
check_for_new_nvts ()
{
  if (sql_int ("SELECT EXISTS"
               " (SELECT * FROM nvts"
               "  WHERE oid NOT IN (SELECT oid FROM old_nvts));"))
    event (0, 0, EVENT_NEW_SECINFO, "nvt");
}

/**
 * @brief Check for new SCAP SecInfo after an update.
 */
static void
check_for_new_scap ()
{
  if (manage_scap_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cves"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (0, 0, EVENT_NEW_SECINFO, "cve");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cpes"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (0, 0, EVENT_NEW_SECINFO, "cpe");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM ovaldefs"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (0, 0, EVENT_NEW_SECINFO, "ovaldef");
    }
}

/**
 * @brief Check for new CERT SecInfo after an update.
 */
static void
check_for_new_cert ()
{
  if (manage_cert_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert_bund_advs"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (0, 0, EVENT_NEW_SECINFO, "cert_bund_adv");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM dfn_cert_advs"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (0, 0, EVENT_NEW_SECINFO, "dfn_cert_adv");
    }
}

/**
 * @brief Print an URL for a New NVTs alert.
 *
 * @param[in]  url      Format string for url.
 * @param[in]  oid      SecInfo ID.
 * @param[in]  type     SecInfo Type.
 *
 * @return Freshly allocated url.
 */
static gchar *
alert_url_print (const gchar *url, const gchar *oid, const gchar *type)
{
  int formatting;
  const gchar *point, *end;
  GString *new_url;

  assert (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 'o':
              {
                g_string_append (new_url, oid);
                break;
              }
            case 't':
              {
                g_string_append (new_url, type);
                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);

  return g_string_free (new_url, FALSE);
}

/**
 * @brief Create list for New NVTs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_nvts_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  if (details_url && strlen (details_url))
    buffer = g_string_new (NEW_NVTS_HEADER);
  else
    buffer = g_string_new (NEW_NVTS_HEADER_OID);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " WHERE oid NOT IN (SELECT oid FROM old_nvts)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " WHERE modification_time > (SELECT modification_time"
                   "                            FROM old_nvts"
                   "                            WHERE old_nvts.oid = nvts.oid)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name;

      name = iterator_string (&rows, 1);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-57.57s%-3s  %13s  %8s %3s%%%s%s%s",
                              name,
                              strlen (name) > 60
                               ? "..."
                               : (strlen (name) > 57 ? name + 57 : "   "),
                              iterator_string (&rows, 2),
                              iterator_string (&rows, 3),
                              iterator_string (&rows, 4),
                              url ? "\n  " : "  ",
                              url ? url : iterator_string (&rows, 0),
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for New CVEs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated message.
 */
static gchar *
new_cves_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CVES_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, cvss, description FROM cves"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, cvss, description FROM cves"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, cvss, description FROM cves"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'scap_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *desc;

      name = iterator_string (&rows, 1);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      desc = iterator_string (&rows, 3);
      g_string_append_printf (buffer,
                              "%-15.15s  %8s  %50.50s%s%s%s%s",
                              name,
                              iterator_string (&rows, 2),
                              desc,
                              strlen (desc) > 53
                               ? "..."
                               : (strlen (desc) > 50 ? desc + 50 : "   "),
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for New CPEs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_cpes_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CPES_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'scap_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-57.57s%-3s  %-s%s%s%s",
                              name,
                              strlen (name) > 60
                               ? "..."
                               : (strlen (name) > 57 ? name + 57 : "   "),
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for "New CERT-Bund Advisories" event message.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event data.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated string.
 */
static gchar *
new_cert_bunds_list (event_t event, const void* event_data, alert_t alert,
                     int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CERT_BUNDS_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'cert_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-11s  %-s%s%s%s",
                              name,
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for "New DFN-CERT Advisories" event message.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated string.
 */
static gchar *
new_dfn_certs_list (event_t event, const void* event_data, alert_t alert,
                    int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_DFN_CERTS_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'cert_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-18s  %-s%s%s%s",
                              name,
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for "New OVAL Definitions" event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_oval_defs_list (event_t event, const void* event_data, alert_t alert,
                    int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_OVAL_DEFS_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM ovaldefs"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM ovaldefs"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM ovaldefs"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'scap_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-30s  %-s%s%s%s",
                              name,
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create message for New NVTs event.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  alert       Alert.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_secinfo_list (event_t event, const void* event_data, alert_t alert,
                  int *count_return)
{
  g_debug ("%s: event_data: %s", __FUNCTION__, (gchar*) event_data);

  if (strcasecmp (event_data, "nvt_example") == 0)
    return new_nvts_list (event, "nvt", alert, 1, count_return);
  if (strcasecmp (event_data, "nvt") == 0)
    return new_nvts_list (event, "nvt", alert, 0, count_return);

  if (strcasecmp (event_data, "cve_example") == 0)
    return new_cves_list (event, "cve", alert, 1, count_return);
  if (strcasecmp (event_data, "cve") == 0)
    return new_cves_list (event, "cve", alert, 0, count_return);

  if (strcasecmp (event_data, "cpe_example") == 0)
    return new_cpes_list (event, "cpe", alert, 1, count_return);
  if (strcasecmp (event_data, "cpe") == 0)
    return new_cpes_list (event, "cpe", alert, 0, count_return);

  if (strcasecmp (event_data, "cert_bund_adv_example") == 0)
    return new_cert_bunds_list (event, "cert_bund_adv", alert, 1, count_return);
  if (strcasecmp (event_data, "cert_bund_adv") == 0)
    return new_cert_bunds_list (event, "cert_bund_adv", alert, 0, count_return);

  if (strcasecmp (event_data, "dfn_cert_adv_example") == 0)
    return new_dfn_certs_list (event, "dfn_cert_adv", alert, 1, count_return);
  if (strcasecmp (event_data, "dfn_cert_adv") == 0)
    return new_dfn_certs_list (event, "dfn_cert_adv", alert, 0, count_return);

  if (strcasecmp (event_data, "ovaldef_example") == 0)
    return new_oval_defs_list (event, "ovaldef", alert, 1, count_return);
  if (strcasecmp (event_data, "ovaldef") == 0)
    return new_oval_defs_list (event, "ovaldef", alert, 0, count_return);

  if (count_return)
    {
      g_warning ("%s: Type error: %s", __FUNCTION__, (char *) event_data);
      *count_return = 0;
    }

  return g_strdup ("ERROR generating list");
}

/**
 * @brief Create message for New NVTs event.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event type specific details.
 * @param[in]  alert       Alert.
 *
 * @return Freshly allocated message.
 */
static gchar *
new_secinfo_message (event_t event, const void* event_data, alert_t alert)
{
  gchar *type, *list, *message, *name, *point;
  int count, example;

  list = new_secinfo_list (event, event_data, alert, &count);

  assert (count > 0);

  type = g_strdup (event_data);
  if (type && (point = strstr (type, "_example")))
    {
      example = 1;
      point[0] = '\0';
    }
  else
    example = 0;

  name = alert_name (alert);
  message = g_strdup_printf ("%s%i%s%s%s%s, according to the\nalert \"%s\":\n\n%s",
                             example
                              ? "Warning: This is an example alert only.\n\n"
                              : "",
                             count,
                             event == EVENT_NEW_SECINFO
                              ? " new "
                              : " ",
                             count == 1
                              ? type_name (type)
                              : type_name_plural (type),
                             event == EVENT_NEW_SECINFO
                              ? ""
                              : (count == 1 ? " was" : " were"),
                             event == EVENT_NEW_SECINFO
                              ? " appeared in the feed"
                              : " updated in the feed",
                             name,
                             list);
  free (name);
  g_free (list);
  return message;
}

/**
 * @brief Check for updated NVTS after an update.
 */
static void
check_for_updated_nvts ()
{
  if (sql_int ("SELECT EXISTS"
               " (SELECT * FROM nvts"
               "  WHERE modification_time > (SELECT modification_time"
               "                             FROM old_nvts"
               "                             WHERE old_nvts.oid = nvts.oid));"))
    event (0, 0, EVENT_UPDATED_SECINFO, "nvt");
}

/**
 * @brief Check for updated SCAP SecInfo after an update.
 */
static void
check_for_updated_scap ()
{
  if (manage_scap_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cves"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (0, 0, EVENT_UPDATED_SECINFO, "cve");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cpes"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (0, 0, EVENT_UPDATED_SECINFO, "cpe");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM ovaldefs"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (0, 0, EVENT_UPDATED_SECINFO, "ovaldef");
    }
}

/**
 * @brief Check for updated CERT SecInfo after an update.
 */
static void
check_for_updated_cert ()
{
  if (manage_cert_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert_bund_advs"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (0, 0, EVENT_UPDATED_SECINFO, "cert_bund_adv");

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM dfn_cert_advs"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (0, 0, EVENT_UPDATED_SECINFO, "dfn_cert_adv");
    }
}

/**
 * @brief Complete an update of the NVT cache.
 *
 * @param[in]  nvts_list             List of nvti_t to insert.
 * @param[in]  nvt_preferences_list  List of preference_t to insert.
 * @param[in]  mode                  -1 updating, -2 rebuilding.
 */
void
manage_complete_nvt_cache_update (GList *nvts_list, GList *nvt_preferences_list,
                                  int mode)
{
  iterator_t configs;

  if (mode == -2)
    {
      sql_begin_exclusive ();
      if (sql_is_sqlite3 ())
        {
          sql ("DELETE FROM nvt_cves;");
          sql ("DELETE FROM nvts;");
          sql ("DELETE FROM nvt_preferences;");
        }
      else
        {
          sql ("TRUNCATE nvts CASCADE;");
          sql ("TRUNCATE nvt_preferences;");
        }
      if (progress)
        progress ();
    }

  /* NVTs and preferences are buffered, insert them into DB. */
  insert_nvts_list (nvts_list, mode);
  insert_nvt_preferences_list (nvt_preferences_list, mode);

  /* 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 ();
  if (check_config_families ())
    g_warning ("%s: Error updating config families."
               "  One or more configs refer to an outdated family of an NVT.",
               __FUNCTION__);
  if (progress)
    progress ();
  update_all_config_caches ();
  if (progress)
    progress ();

  if (mode == -1)
    sql_begin_exclusive ();
  refresh_nvt_cves ();
  if (mode == -1)
    sql_commit ();

  if (sql_int ("SELECT NOT EXISTS (SELECT * FROM meta"
               "                   WHERE name = 'nvts_check_time')"))
    sql ("INSERT INTO meta (name, value)"
         " VALUES ('nvts_check_time', m_now ());");
  else if (sql_int ("SELECT value = '0' FROM meta"
                    " WHERE name = 'nvts_check_time';"))
    sql ("UPDATE meta SET value = m_now ()"
         " WHERE name = 'nvts_check_time';");
  else
    {
      check_for_new_nvts ();
      check_for_updated_nvts ();
      sql ("UPDATE meta SET value = m_now ()"
           " WHERE name = 'nvts_check_time';");
    }

  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 Set the family of an NVT selector.
 *
 * @param[in]  quoted_selector  SQL-quoted selector name.
 * @param[in]  family_or_nvt    Family name or NVT OID of selector.
 * @param[in]  type             Selector type to remove.
 * @param[in]  family           New family.
 *
 * @return 0 success, -1 error.
 */
static void
nvt_selector_set_family (const char* quoted_selector,
                         const char* family_or_nvt,
                         int type,
                         const char *family)
{
  gchar *quoted_family_or_nvt, *quoted_family;

  quoted_family_or_nvt = sql_quote (family_or_nvt);
  quoted_family = sql_quote (family);
  sql ("UPDATE nvt_selectors SET family = '%s'"
       " WHERE name = '%s'"
       " AND family_or_nvt = '%s'"
       " AND type = %i;",
       quoted_family,
       quoted_selector,
       quoted_family_or_nvt,
       type);
  g_free (quoted_family);
  g_free (quoted_family_or_nvt);
}

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

/**
 * @brief Change the family of an NVT in a config.
 *
 * @param[in]  config      Config.
 * @param[in]  oid         NVT OID.
 * @param[in]  old_family  Name of old family.
 * @param[in]  new_family  Name of new family.
 *
 * @return 0 success, -1 error.
 */
static int
config_update_nvt_family (resource_t config, const char *oid,
                          const char *old_family, const char *new_family)
{
  int constraining;
  char* selector;
  gchar *quoted_selector;

  selector = config_nvt_selector (config);
  if (selector == NULL)
    {
      g_warning ("%s: Failed to get config selector", __FUNCTION__);
      return -1;
    }
  quoted_selector = sql_quote (selector);

  constraining = config_families_growing (config);

  g_debug ("%s: Updating NVT family for selector '%s'", __FUNCTION__, selector);

  if (constraining)
    {
      /* Constraining the universe. */

      g_debug ("%s:   Selector constrains universe", __FUNCTION__);

      if (nvt_selector_family_growing (selector, old_family, constraining))
        {
          /* Old family is growing. */

          g_debug ("%s:   Old family is growing", __FUNCTION__);

          if (nvt_selector_has (quoted_selector, oid, NVT_SELECTOR_TYPE_NVT,
                                0 /* Included. */))
            {
              /* NVT explicitly included in old family, which is redundant, so
               * drop selector. */
              g_debug ("%s:   Drop selector", __FUNCTION__);
              nvt_selector_remove_selector (quoted_selector,
                                            oid,
                                            NVT_SELECTOR_TYPE_NVT);
            }
          else if (nvt_selector_has (quoted_selector, oid,
                                     NVT_SELECTOR_TYPE_NVT,
                                     1 /* Excluded. */))
            {
              /* NVT explicitly excluded from old family. */

              g_debug ("%s:   NVT excluded from old family", __FUNCTION__);

              if (nvt_selector_family_growing (selector, new_family,
                                               constraining))
                {
                  /* New family is growing, change NVT to new family. */
                  g_debug ("%s:   Change family", __FUNCTION__);
                  nvt_selector_set_family (quoted_selector,
                                           oid,
                                           NVT_SELECTOR_TYPE_NVT,
                                           new_family);
                }
              else
                {
                  /* New family static, NVT excluded already, so drop NVT
                   * selector. */
                  g_debug ("%s:   Remove selector", __FUNCTION__);
                  nvt_selector_remove_selector (quoted_selector,
                                                oid,
                                                NVT_SELECTOR_TYPE_NVT);
                }
            }
        }
      else
        {
          /* Old family is static. */

          g_debug ("%s:   Old family is static", __FUNCTION__);

          if (nvt_selector_has (quoted_selector, oid, NVT_SELECTOR_TYPE_NVT,
                                0 /* Included. */))
            {
              /* NVT explicitly included in old family. */

              g_debug ("%s:   NVT included in old family", __FUNCTION__);

              if (nvt_selector_family_growing (selector, new_family,
                                               constraining))
                {
                  /* New family is growing so it already includes the NVT.
                   * Remove the NVT selector. */
                  g_debug ("%s:   Remove selector", __FUNCTION__);
                  nvt_selector_remove_selector (quoted_selector,
                                                oid,
                                                NVT_SELECTOR_TYPE_NVT);
                }
              else
                {
                  /* New family static, change NVT to new family. */
                  g_debug ("%s:   Change family", __FUNCTION__);
                  nvt_selector_set_family (quoted_selector,
                                           oid,
                                           NVT_SELECTOR_TYPE_NVT,
                                           new_family);
                }
            }
          else if (nvt_selector_has (quoted_selector, oid,
                                     NVT_SELECTOR_TYPE_NVT,
                                     1 /* Excluded. */))
            {
              /* NVT explicitly excluded from old family, which is redundant, so
               * remove NVT selector. */
              g_debug ("%s:   Remove selector", __FUNCTION__);
              nvt_selector_remove_selector (quoted_selector,
                                            oid,
                                            NVT_SELECTOR_TYPE_NVT);
            }
        }
    }
  else
    {
      /* Generating from empty. */

      g_debug ("%s:   Selector generates from empty", __FUNCTION__);

      if (nvt_selector_family_growing (selector, old_family, constraining))
        {
          /* Old family is growing. */

          g_debug ("%s:   Old family is growing", __FUNCTION__);

          if (nvt_selector_has (quoted_selector, oid, NVT_SELECTOR_TYPE_NVT,
                                0 /* Included. */))
            {
              /* NVT explicitly included in old family.  This is redundant, so
               * just remove the NVT selector. */
              g_debug ("%s:   Remove selector", __FUNCTION__);
              nvt_selector_remove_selector (quoted_selector,
                                            oid,
                                            NVT_SELECTOR_TYPE_NVT);
            }
          else if (nvt_selector_has (quoted_selector, oid,
                                     NVT_SELECTOR_TYPE_NVT,
                                     1 /* Excluded. */))
            {
              /* NVT explicitly excluded from old family. */

              g_debug ("%s:   NVT excluded from old family", __FUNCTION__);

              if (nvt_selector_family_growing (selector, new_family,
                                               constraining))
                {
                  /* New family is growing, change NVT to new family. */
                  g_debug ("%s:   Change family", __FUNCTION__);
                  nvt_selector_set_family (quoted_selector,
                                           oid,
                                           NVT_SELECTOR_TYPE_NVT,
                                           new_family);
                }
              else
                {
                  /* New family static, so the NVT is already excluded from the
                   * new family.  Remove the NVT selector. */
                  g_debug ("%s:   Remove selector", __FUNCTION__);
                  nvt_selector_remove_selector (quoted_selector,
                                                oid,
                                                NVT_SELECTOR_TYPE_NVT);
                }
            }
        }
      else
        {
          /* Old family is static. */

          g_debug ("%s:   Old family is static", __FUNCTION__);

          if (nvt_selector_has (quoted_selector, oid, NVT_SELECTOR_TYPE_NVT,
                                0 /* Included. */))
            {
              /* NVT explicitly included in old family. */

              g_debug ("%s:   NVT included in old family", __FUNCTION__);

              if (nvt_selector_family_growing (selector, new_family,
                                               constraining))
                {
                  /* New family growing, so the NVT is already in there.  Remove
                   * the NVT selector. */
                  g_debug ("%s:   Remove selector", __FUNCTION__);
                  nvt_selector_remove_selector (quoted_selector,
                                                oid,
                                                NVT_SELECTOR_TYPE_NVT);
                }
              else
                {
                  /* New family is static, change NVT to new family. */
                  g_debug ("%s:   Change family", __FUNCTION__);
                  nvt_selector_set_family (quoted_selector,
                                           oid,
                                           NVT_SELECTOR_TYPE_NVT,
                                           new_family);
                }
            }
          else if (nvt_selector_has (quoted_selector, oid,
                                     NVT_SELECTOR_TYPE_NVT,
                                     1 /* Excluded. */))
            {
              /* NVT explicitly excluded from old family.  This is redundant,
               * so just remove the NVT selector. */
              g_debug ("%s:   NVT exclude from old family, remove selector",
                       __FUNCTION__);
              nvt_selector_remove_selector (quoted_selector,
                                            oid,
                                            NVT_SELECTOR_TYPE_NVT);
            }
        }
    }

  g_free (quoted_selector);
  free (selector);
  return 0;
}

/**
 * @brief Change the family of an NVT in all configs.
 *
 * @param[in]  oid         NVT OID.
 * @param[in]  old_family  Name of old family.
 * @param[in]  new_family  Name of new family.
 *
 * @return 0 success, -1 error.
 */
static int
update_nvt_family (const char *oid, const char *old_family,
                   const char *new_family)
{
  int ret;
  iterator_t rows;

  ret = 0;
  init_iterator (&rows, "SELECT id FROM configs WHERE type = 0;");
  while (next (&rows))
    if (config_update_nvt_family (iterator_int64 (&rows, 0), oid, old_family,
                                  new_family))
      ret = -1;
  cleanup_iterator (&rows);
  return ret;
}

/**
 * @brief Ensure that all configs refer to the right NVT families.
 *
 * When the family of an NVT is changed in the feed, then the config
 * refers to the wrong family.
 *
 * @return 0 success, -1 error.
 */
static int
check_config_families ()
{
  int ret;
  iterator_t selectors;

  ret = 0;
  /* Get all NVT selectors that have the wrong family. */
  init_iterator (&selectors,
                 "SELECT DISTINCT family_or_nvt, family,"
                 "       (SELECT family FROM nvts WHERE oid = family_or_nvt)"
                 " FROM nvt_selectors"
                 " WHERE type = 2"
                 " AND family != (SELECT family FROM nvts"
                 "                WHERE oid = family_or_nvt);");
  while (next (&selectors))
    /* Update the family of the NVT selector. */
    if (update_nvt_family (iterator_string (&selectors, 0),
                           iterator_string (&selectors, 1),
                           iterator_string (&selectors, 2)))
      ret = -1;
  cleanup_iterator (&selectors);
  return ret;
}


/* 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 %s '%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 %s 'server_info_%%'"
                     /* Task preferences. */
                     " AND name != 'max_checks'"
                     " AND name != 'max_hosts'"
                     " ORDER BY name ASC",
                     sql_ilike_op (),
                     quoted_name,
                     sql_ilike_op ());
      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 %s 'server_info_%%'"
                   /* Task preferences. */
                   " AND name != 'max_checks'"
                   " AND name != 'max_hosts'"
                   " ORDER BY name ASC",
                   sql_ilike_op ());
}

/**
 * @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 %s '%s[%%';",
                     sql_ilike_op (),
                     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);
  g_free (quoted_name);
  return value;
}

/**
 * @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.
 *
 * @return 0 success, 1 invalid auto_delete value, 2 auto_delete_data out of
 *         range.
 */
int
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;

                  if ((strcmp (pair->name, "auto_delete") == 0)
                      && (strcmp (pair->value, "keep"))
                      && (strcmp (pair->value, "no")))
                    {
                      return 1;
                    }

                  if (strcmp (pair->name, "auto_delete_data") == 0)
                    {
                      int keep;
                      keep = atoi (pair->value);
                      if (keep < AUTO_DELETE_KEEP_MIN
                          || keep > AUTO_DELETE_KEEP_MAX)
                        return 2;
                    }

                  if ((strcmp (pair->name, "in_assets") == 0)
                      && scanner_type (task_scanner (task)) == SCANNER_TYPE_CVE)
                    quoted_value = g_strdup ("no");
                  else
                    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 task = %llu AND name = '%s';",
                     task, quoted_name);
              g_free (quoted_name);
              sql ("UPDATE tasks SET modification_time = m_now ()"
                   " WHERE id = %llu;",
                   task);
            }
        }
    }
  return 0;
}


/* Credentials. */

/**
 * @brief Ensure that there is an encryption key.
 *
 * This prevents contention problems that can happen when the key is
 * created on the fly during an OMP operation.
 *
 * Up to caller to create transaction.
 *
 * @return 0 success, -1 error.
 */
static int
check_db_encryption_key ()
{
  lsc_crypt_ctx_t crypt_ctx;
  gchar *secret;

  crypt_ctx = lsc_crypt_new ();
  /* The encryption layer creates the key if it does not exist. */
  secret = lsc_crypt_encrypt (crypt_ctx, "dummy", "dummy", NULL);
  lsc_crypt_release (crypt_ctx);
  if (secret == NULL)
    return -1;
  g_free (secret);
  return 0;
}

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

/**
 * @brief Set data for a credential.
 *
 * @param[in]  credential     The credential.
 * @param[in]  type           The data type (e.g. "username" or "secret").
 * @param[in]  value          The value to set or NULL to remove data entry.
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
int
set_credential_data (credential_t credential,
                     const char* type,
                     const char* value)
{
  gchar *quoted_type;

  if (current_credentials.uuid
      && (acl_user_may ("modify_credential") == 0))
    return 99;

  if (type == NULL)
    return -1;

  if (credential == 0)
    return 1;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT count (*) FROM credentials_data"
               " WHERE credential = '%llu' AND type = '%s';",
               credential, quoted_type))
    {
      if (value == NULL)
        {
          sql ("DELETE FROM credentials_data"
               " WHERE credential = '%llu' AND type = '%s';",
               credential, quoted_type);
        }
      else
        {
          gchar *quoted_value;
          quoted_value = sql_quote (value);
          sql ("UPDATE credentials_data SET value = '%s'"
              " WHERE credential = %llu AND type = '%s';",
                quoted_value, credential, quoted_type);
          g_free (quoted_value);
        }
    }
  else if (value != NULL)
    {
      gchar *quoted_value;
      quoted_value = sql_quote (value);
      sql ("INSERT INTO credentials_data (credential, type, value)"
            " VALUES (%llu, '%s', '%s')",
            credential, quoted_type, quoted_value);
      g_free (quoted_value);
    }

  g_free (quoted_type);
  return 0;
}

/**
 * @brief Length of password generated in create_credential.
 */
#define PASSWORD_LENGTH 10

/**
 * @brief Create a 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[in]  certificate     Certificate, or NULL.
 * @param[in]  community          SNMP community string, or NULL.
 * @param[in]  auth_algorithm     SNMP authentication algorithm, or NULL.
 * @param[in]  privacy_password   SNMP privacy password.
 * @param[in]  privacy_algorithm  SNMP privacy algorithm.
 * @param[in]  given_type      Credential type or NULL.
 * @param[in]  allow_insecure  Whether to allow insecure uses.
 * @param[out] credential      Created Credential.
 *
 * @return 0 success, 1 LSC credential exists already, 2 name contains space,
 *         3 Failed to create public key from private key/password,
 *         4 Invalid credential type, 5 login username missing,
 *         6 password missing, 7 private key missing, 8 certificate missing,
 *         10 autogenerate not supported, 11 community missing,
 *         12 auth algorithm missing, 13 privacy password missing, 14 privacy
 *         algorithm missing, 15 invalid auth algorithm, 16 invalid privacy
 *         algorithm, 17 invalid certificate, 99 permission denied, -1 error.
 */
int
create_credential (const char* name, const char* comment, const char* login,
                   const char* given_password, const char* key_private,
                   const char* certificate, const char* community,
                   const char* auth_algorithm, const char* privacy_password,
                   const char* privacy_algorithm,
                   const char* given_type, const char* allow_insecure,
                   credential_t *credential)
{
  gchar *quoted_name, *quoted_comment, *quoted_type;
  int i;
  GRand *rand;
  gchar generated_password[PASSWORD_LENGTH];
  gchar *generated_private_key;
  credential_t new_credential;
  int auto_generate, allow_insecure_int;
  int ret;

  assert (name && strlen (name) > 0);
  assert (current_credentials.uuid);
  assert (comment);

  sql_begin_immediate ();

  if (acl_user_may ("create_credential") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (resource_with_name_exists (name, "credential", 0))
    {
      sql_rollback ();
      return 1;
    }

  if (allow_insecure
      && strcmp (allow_insecure, "")
      && strcmp (allow_insecure, "0"))
    allow_insecure_int = 1;
  else
    allow_insecure_int = 0;

  if (given_type && strcmp (given_type, ""))
    {
      if (strcmp (given_type, "cc")
          && strcmp (given_type, "snmp")
          && strcmp (given_type, "up")
          && strcmp (given_type, "usk"))
        {
          sql_rollback ();
          return 4;
        }
      else
        quoted_type = g_strdup (given_type);
    }
  else if (community
           || (login && given_password && auth_algorithm))
    quoted_type = g_strdup ("snmp");
  else if (certificate && key_private)
    quoted_type = g_strdup ("cc");
  else if (login && key_private)
    quoted_type = g_strdup ("usk");
  else if (login && given_password)
    quoted_type = g_strdup ("up");
  else if (login && key_private == NULL && given_password == NULL)
    quoted_type = g_strdup ("usk"); /* auto-generate */
  else
    {
      g_warning ("%s: Cannot determine type of new credential", __FUNCTION__);
      return -1;
    }

  /* Validate credential data */
  auto_generate = ((given_password == NULL) && (key_private == NULL)
                   && (certificate == NULL) && (community == NULL));
  ret = 0;

  if (auto_generate
      && (strcmp (quoted_type, "cc") == 0
          || strcmp (quoted_type, "snmp") == 0))
    ret = 10; // Type does not support autogenerate

  if (login == NULL
      && strcmp (quoted_type, "cc")
      && strcmp (quoted_type, "snmp"))
    ret = 5;
  else if (given_password == NULL && auto_generate == 0
           && strcmp (quoted_type, "up") == 0)
      // username password requires a password
    ret = 6;
  else if (key_private == NULL && auto_generate == 0
           && (strcmp (quoted_type, "cc") == 0
               || strcmp (quoted_type, "usk") == 0))
    ret = 7;
  else if (certificate == NULL && auto_generate == 0
           && (strcmp (quoted_type, "cc") == 0))
    ret = 8;
  else if (strcmp (quoted_type, "snmp") == 0)
    {
      int using_snmp_v3 = 0;
      if (login || given_password || auth_algorithm
          || privacy_password || privacy_algorithm)
        using_snmp_v3 = 1;

      if (community == NULL)
        {
          if (login == NULL || given_password == NULL)
            ret = 11;
        }
      else if (auth_algorithm == NULL && using_snmp_v3)
        ret = 12;
      else if (privacy_password == NULL && privacy_algorithm)
        ret = 13;
      else if (privacy_algorithm == NULL && privacy_password)
        ret = 14;
      else if (auth_algorithm
               && strcmp (auth_algorithm, "md5")
               && strcmp (auth_algorithm, "sha1"))
        ret = 15;
      else if (privacy_algorithm
               && strcmp (privacy_algorithm, "aes")
               && strcmp (privacy_algorithm, "des"))
        ret = 16;
    }

  if (ret)
    {
      g_free (quoted_type);
      sql_rollback ();
      return ret;
    }

  /* Create base credential */
  quoted_name = sql_quote (name);
  quoted_comment = sql_quote (comment ? comment : "");

  sql ("INSERT INTO credentials"
       " (uuid, name, owner, comment, creation_time, modification_time,"
       "  type, allow_insecure)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "   '%s', m_now (), m_now (), '%s', %d);",
       quoted_name,
       current_credentials.uuid,
       quoted_comment,
       quoted_type,
       allow_insecure_int);

  g_free (quoted_name);
  g_free (quoted_comment);

  new_credential = sql_last_insert_id ();

  /* Add non-secret data */
  if (login)
    set_credential_data (new_credential,
                         "username", login);
  if (certificate)
    {
      gchar *certificate_truncated;
      certificate_truncated = truncate_certificate (certificate);
      if (certificate_truncated)
        set_credential_data (new_credential,
                             "certificate", certificate_truncated);
      else
        return 17;
      g_free (certificate_truncated);
    }
  if (auth_algorithm)
    set_credential_data (new_credential,
                         "auth_algorithm", auth_algorithm);
  if (privacy_algorithm)
    set_credential_data (new_credential,
                         "privacy_algorithm", privacy_algorithm);

  g_free (quoted_type);

  /* Add secret data like passwords and private keys */
  /* Private key with optional passphrase */
  if (key_private)
    {
      lsc_crypt_ctx_t crypt_ctx;
      gchar *key_private_truncated, *key_public;

      /* Try truncate the private key, but if that fails try get the public
       * key anyway, in case it's a key type that truncate_private_key does
       * not understand. */

      if (key_private)
        key_private_truncated = truncate_private_key (key_private);
      else
        return 3;

      key_public = openvas_ssh_public_from_private (key_private_truncated
                                                     ? key_private_truncated
                                                     : key_private,
                                                    given_password);
      if (key_public == NULL)
        {
          g_free (key_private_truncated);
          return 3;
        }
      g_free (key_public);

      /* 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)
        {
          gchar *secret;
          crypt_ctx = lsc_crypt_new ();
          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "password", given_password,
                                      "private_key", key_private, NULL);
          if (!secret)
            {
              lsc_crypt_release (crypt_ctx);
              sql_rollback ();
              return -1;
            }
          set_credential_data (new_credential, "secret", secret);
          g_free (secret);
        }
      else
        {
          crypt_ctx = NULL;
          set_credential_data (new_credential, "password", given_password);
          set_credential_data (new_credential, "private_key", key_private);
        }
      lsc_crypt_release (crypt_ctx);

      if (credential)
        *credential = new_credential;

      sql_commit ();
      return 0;
    }

  /* SNMP passwords */
  if (community)
    {
      lsc_crypt_ctx_t crypt_ctx;

      /* Encrypt passwords.  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)
        {
          gchar *secret;
          crypt_ctx = lsc_crypt_new ();
          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "community", community,
                                      "password", given_password,
                                      "privacy_password", privacy_password,
                                      NULL);
          if (!secret)
            {
              lsc_crypt_release (crypt_ctx);
              sql_rollback ();
              return -1;
            }
          set_credential_data (new_credential, "secret", secret);
          g_free (secret);
        }
      else
        {
          crypt_ctx = NULL;
          set_credential_data (new_credential, "community", community);
          set_credential_data (new_credential, "password", given_password);
          set_credential_data (new_credential, "priv_password", privacy_password);
        }
      lsc_crypt_release (crypt_ctx);

      if (credential)
        *credential = new_credential;

      sql_commit ();
      return 0;
    }

  /* Password only */
  if (given_password)
    {
      lsc_crypt_ctx_t crypt_ctx;

      /* Password-only credential. */

      if (!disable_encrypted_credentials)
        {
          crypt_ctx = lsc_crypt_new ();
          gchar *secret = lsc_crypt_encrypt (crypt_ctx,
                                             "password", given_password,
                                             NULL);
          if (!secret)
            {
              lsc_crypt_release (crypt_ctx);
              sql_rollback ();
              return -1;
            }
          set_credential_data (new_credential, "secret", secret);
          g_free (secret);
        }
      else
        {
          crypt_ctx = NULL;
          set_credential_data (new_credential, "password", given_password);
        }

      if (credential)
        *credential = new_credential;

      sql_commit ();
      return 0;
    }

  /*
   * Auto-generate credential
   */

  /* Ensure the login is alphanumeric, to help the package generation. */

  if (login)
    {
      const char *s;
      s = login;
      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++)
    {
      generated_password[i] = (gchar) g_rand_int_range (rand, '0', 'z');
      if (generated_password[i] == '\\')
        generated_password[i] = '{';
    }
  generated_password[PASSWORD_LENGTH - 1] = '\0';
  g_rand_free (rand);

  if (given_type == NULL || strcmp (given_type, "usk") == 0)
    {
      if (lsc_user_keys_create (generated_password, &generated_private_key))
        {
          sql_rollback ();
          return -1;
        }
    }
  else
    generated_private_key = NULL;

  {
    lsc_crypt_ctx_t crypt_ctx;

    /* Generated key credential. */

    if (!disable_encrypted_credentials)
      {
        gchar *secret;
        crypt_ctx = lsc_crypt_new ();
        if (generated_private_key)
          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "password", generated_password,
                                      "private_key", generated_private_key,
                                      NULL);
        else
          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "password", generated_password,
                                      NULL);

        if (!secret)
          {
            lsc_crypt_release (crypt_ctx);
            sql_rollback ();
            return -1;
          }
        set_credential_data (new_credential, "secret", secret);
        g_free (secret);
      }
    else
      {
        set_credential_data (new_credential, "password", generated_password);
        if (generated_private_key)
          set_credential_data (new_credential,
                               "private_key", generated_private_key);
        crypt_ctx = NULL;
      }
    lsc_crypt_release (crypt_ctx);
    g_free (generated_private_key);
  }

  if (credential)
    *credential = new_credential;

  sql_commit ();

  return 0;
}

/**
 * @brief Create an LSC Credential from an existing one.
 *
 * @param[in]  name                 Name of new Credential. NULL to copy
 *                                  from existing.
 * @param[in]  comment              Comment on new Credential. NULL to copy
 *                                  from existing.
 * @param[in]  credential_id        UUID of existing Credential.
 * @param[out] new_credential       New Credential.
 *
 * @return 0 success, 1 Credential exists already, 2 failed to find
 *         existing Credential, -1 error.
 */
int
copy_credential (const char* name, const char* comment,
                 const char *credential_id,
                 credential_t* new_credential)
{
  int ret;
  credential_t credential;

  assert (new_credential);
  ret = copy_resource ("credential", name, comment, credential_id,
                       "type", 1, new_credential);
  if (ret)
    return ret;

  if (find_resource ("credential", credential_id, &credential))
    return -1;

  sql ("INSERT INTO credentials_data (credential, type, value)"
       " SELECT %llu, type, value FROM credentials_data"
       " WHERE credential = %llu;",
       *new_credential, credential);

  return 0;
}

/**
 * @brief Modify a Credential.
 *
 * @param[in]   credential_id       UUID of Credential.
 * @param[in]   name                Name of Credential.
 * @param[in]   comment             Comment on Credential.
 * @param[in]   login               Login of Credential.
 * @param[in]   password            Password or passphrase of Credential.
 * @param[in]   key_private         Private key of Credential.
 * @param[in]   certificate         Certificate of Credential.
 * @param[in]   community           SNMP Community of Credential.
 * @param[in]   auth_algorithm      Authentication algorithm of Credential.
 * @param[in]   privacy_password    Privacy password of Credential.
 * @param[in]   privacy_algorithm   Privacy algorithm of Credential.
 * @param[in]   allow_insecure      Whether to allow insecure use.
 *
 * @return 0 success, 1 failed to find credential, 2 credential with new name
 *         exists, 3 credential_id required, 4 invalid login name,
 *         5 invalid certificate, 6 invalid auth_algorithm,
 *         7 invalid privacy_algorithm, 8 invalid private key,
 *         99 permission denied,
 *         -1 internal error.
 */
int
modify_credential (const char *credential_id,
                   const char *name, const char *comment,
                   const char *login, const char* password,
                   const char* key_private, const char* certificate,
                   const char* community, const char* auth_algorithm,
                   const char* privacy_password, const char* privacy_algorithm,
                   const char* allow_insecure)
{
  credential_t credential;
  iterator_t iterator;
  int ret;

  if (credential_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (acl_user_may ("modify_credential") == 0)
    {
      sql_rollback ();
      return 99;
    }

  credential = 0;
  if (find_credential_with_permission (credential_id, &credential,
                                       "modify_credential"))
    {
      sql_rollback ();
      return -1;
    }

  if (credential == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* Check whether a credential with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "credential", credential))
        {
          sql_rollback ();
          return 2;
        }
    }

  /* Update values */

  if (name)
    set_credential_name (credential, name);

  if (comment)
    set_credential_comment (credential, comment);

  if (allow_insecure)
    {
      if (strcmp (allow_insecure, "") && strcmp (allow_insecure, "0"))
        sql ("UPDATE credentials SET allow_insecure = 1 WHERE id = %llu;",
             credential);
      else
        sql ("UPDATE credentials SET allow_insecure = 0 WHERE id = %llu;",
             credential);
    }

  ret = 0;

  if (login && ret == 0)
    {
      const char *s;
      s = login;
      // Check if login contains only alphanumeric characters
      if (strcmp (login, "") == 0)
        ret = 4;
      while (*s && ret == 0)
        if (isalnum (*s))
          s++;
        else
          ret = 4;

      if (ret == 0)
        set_credential_login (credential, login);
    }

  if (certificate && ret == 0)
    {
      // Truncate certificate which also validates it.
      gchar *certificate_truncated;
      certificate_truncated = truncate_certificate (certificate);
      if (certificate_truncated == NULL)
        {
          set_credential_certificate (credential, certificate_truncated);
          g_free (certificate_truncated);
        }
      else
        ret = 5;
    }

  if (auth_algorithm && ret == 0)
    {
      if (strcmp (auth_algorithm, "md5")
          && strcmp (auth_algorithm, "sha1"))
        ret = 6;
      else
        set_credential_auth_algorithm (credential, auth_algorithm);
    }

  if (privacy_algorithm && ret == 0)
    {
      if (strcmp (auth_algorithm, "aes")
          && strcmp (auth_algorithm, "des"))
        ret = 7;
      else
        set_credential_privacy_algorithm (credential, privacy_algorithm);
    }

  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  init_credential_iterator_one (&iterator, credential);
  if (next (&iterator))
    {
      gchar *key_private_truncated = NULL;
      const char *key_private_to_use;
      const char *type = credential_iterator_type (&iterator);

      if (key_private)
        {
          char *key_public = NULL;
          /* Try truncate the private key, but if that fails try get the
           * public key anyway, in case it's a key type that
           * truncate_private_key does not understand. */
          key_private_truncated = truncate_private_key (key_private);
          key_private_to_use = key_private_truncated ? key_private_truncated
                                                     : key_private;

          if (strcmp (type, "cc") == 0)
            {
              key_public = openvas_ssh_public_from_private
                              (key_private_to_use,
                               NULL);
            }
          else if (strcmp (type, "usk") == 0)
            {
              key_public = openvas_ssh_public_from_private
                              (key_private_to_use,
                               password
                                ? password
                                : credential_iterator_password (&iterator));
            }

          if (key_public == NULL)
            {
              sql_rollback ();
              cleanup_iterator (&iterator);
              g_free (key_public);
              return 8;
            }
          g_free (key_public);
        }
      else
        key_private_to_use = NULL;

      if (strcmp (type, "cc") == 0)
        {
          if (key_private_to_use)
            set_credential_private_key (credential,
                                        key_private_to_use,
                                        NULL);
        }
      else if (strcmp (type, "up") == 0)
        {
          if (password)
            set_credential_password (credential, password);
        }
      else if (strcmp (type, "usk") == 0)
        {
          if (key_private_to_use || password)
            {
              set_credential_private_key
                (credential,
                 key_private_truncated
                  ? key_private_to_use
                  : credential_iterator_private_key (&iterator),
                password
                  ? password
                  : credential_iterator_password (&iterator));
            }
        }
      else if (strcmp (type, "snmp") == 0)
        {
          if (community || password || privacy_password)
            {
              set_credential_snmp_secret
                (credential,
                 community
                  ? community
                  : credential_iterator_community (&iterator),
                 password
                  ? password
                  : credential_iterator_password (&iterator),
                 privacy_password
                  ? privacy_password
                  : credential_iterator_privacy_password (&iterator));
            }
        }
      else
        {
          g_warning ("%s: Unknown credential type: %s", __FUNCTION__, type);
          sql_rollback ();
          cleanup_iterator (&iterator);
        }

      g_free (key_private_truncated);
    }
  else
    {
      g_warning ("%s: credential iterator next() failed", __FUNCTION__);
      sql_rollback ();
      cleanup_iterator (&iterator);
      return -1;
    }

  cleanup_iterator (&iterator);

  sql_commit ();

  return 0;
}

/**
 * @brief Delete a Credential.
 *
 * @param[in]  credential_id      UUID of Credential.
 * @param[in]  ultimate           Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because the credential is in use,
 *         2 failed to find credential, 99 permission denied, -1 error.
 */
int
delete_credential (const char *credential_id, int ultimate)
{
  credential_t credential = 0;

  sql_begin_immediate ();

  if (acl_user_may ("delete_credential") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (find_credential_with_permission (credential_id, &credential,
                                       "delete_credential"))
    {
      sql_rollback ();
      return -1;
    }

  if (credential == 0)
    {
      if (find_trash ("credential", credential_id, &credential))
        {
          sql_rollback ();
          return -1;
        }
      if (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 another resource in the trashcan. */
      if (trash_credential_in_use (credential))
        {
          sql_rollback ();
          return 1;
        }

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

      sql ("DELETE FROM credentials_trash_data WHERE credential = %llu;",
           credential);
      sql ("DELETE FROM credentials_trash WHERE id = %llu;", credential);
      sql_commit ();
      return 0;
    }

  /* Check if it's in use by another resource. */
  if (credential_in_use (credential))
    {
      sql_rollback ();
      return 1;
    }

  if (ultimate == 0)
    {
      credential_t trash_credential;

      sql ("INSERT INTO credentials_trash"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, type)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, type"
           " FROM credentials WHERE id = %llu;",
           credential);
      trash_credential = sql_last_insert_id ();

      sql ("INSERT INTO credentials_trash_data"
           " (credential, type, value)"
           " SELECT %llu, type, value"
           " FROM credentials_data"
           " WHERE credential = %llu",
           trash_credential, credential);

      /* Update the credential references in any trashcan targets or scanners.
       * This situation is possible if the user deletes the credential when
       * the target or scanner is in the trashcan. */
      sql ("UPDATE targets_trash_login_data"
           " SET credential_location = " G_STRINGIFY (LOCATION_TRASH) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           "   AND credential_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_credential,
           credential);
      sql ("UPDATE scanners_trash"
           " SET credential_location = " G_STRINGIFY (LOCATION_TRASH) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           "   AND credential_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_credential,
           credential);

      permissions_set_locations ("credential", credential,
                                 trash_credential,
                                 LOCATION_TRASH);
      tags_set_locations ("credential", credential,
                          trash_credential,
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("credential", credential, LOCATION_TABLE);
      tags_set_orphans ("credential", credential, LOCATION_TABLE);
    }

  sql ("DELETE FROM credentials_data WHERE credential = %llu;", credential);
  sql ("DELETE FROM credentials WHERE id = %llu;", credential);

  sql_commit ();
  return 0;
}

/**
 * @brief Filter columns for LSC Credential iterator.
 */
#define CREDENTIAL_ITERATOR_FILTER_COLUMNS                                    \
 { GET_ITERATOR_FILTER_COLUMNS, "login", "type", "allow_insecure", NULL }

/**
 * @brief LSC Credential iterator columns.
 */
#define CREDENTIAL_ITERATOR_COLUMNS                                           \
 {                                                                            \
   GET_ITERATOR_COLUMNS (credentials),                                        \
   /* public generic data */                                                  \
   { "type", NULL, KEYWORD_TYPE_STRING },                                     \
   { "allow_insecure", NULL, KEYWORD_TYPE_INTEGER },                          \
   /* public type specific data */                                            \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'username')",             \
     "login",                                                                 \
     KEYWORD_TYPE_STRING                                                      \
   },                                                                         \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'certificate')",          \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'auth_algorithm')",       \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'privacy_algorithm')",    \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   /* private data */                                                         \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'secret')",               \
     "secret",                                                                \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'password')",             \
     "password",                                                              \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'private_key')",          \
     "private_key",                                                           \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'community')",            \
     "community",                                                             \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'privacy_password')",     \
     "privacy_password",                                                      \
     KEYWORD_TYPE_STRING },                                                   \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief LSC Credential iterator columns for trash case.
 */
#define CREDENTIAL_ITERATOR_TRASH_COLUMNS                                     \
 {                                                                            \
   GET_ITERATOR_COLUMNS (credentials_trash),                                  \
   /* public generic data */                                                  \
   { "type", NULL, KEYWORD_TYPE_STRING },                                     \
   { "allow_insecure", NULL, KEYWORD_TYPE_INTEGER },                          \
   /* public type specific data */                                            \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'username')",       \
     "login",                                                                 \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'certificate')",    \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id"                               \
     "   AND type = 'auth_algorithm')",                                       \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id"                               \
     "   AND type = 'privacy_algorithm')",                                    \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   /* private data */                                                         \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'secret')",         \
     "secret",                                                                \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'password')",       \
     "password",                                                              \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'private_key')",    \
     "private_key",                                                           \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'community')",      \
     "community",                                                             \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id"                               \
     " AND type = 'privacy_password')",                                       \
     "privacy_password",                                                      \
     KEYWORD_TYPE_STRING },                                                   \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Count number of LSC Credentials.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of LSC Credentials in filtered set.
 */
int
credential_count (const get_data_t *get)
{
  static const char *filter_columns[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CREDENTIAL_ITERATOR_COLUMNS;
  static column_t trash_columns[] = CREDENTIAL_ITERATOR_TRASH_COLUMNS;
  return count ("credential", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Check whether a Credential is in use.
 *
 * @param[in]  credential  Credential.
 *
 * @return 1 yes, 0 no.
 */
int
credential_in_use (credential_t credential)
{
  int ret;
  char *uuid = credential_uuid (credential);

  ret = !!(sql_int ("SELECT count (*) FROM targets_login_data"
                    " WHERE credential = %llu;",
                    credential)
           || sql_int ("SELECT count (*) FROM scanners"
                       " WHERE credential = %llu;",
                       credential)
           || sql_int ("SELECT count (*) FROM alert_method_data"
                       " WHERE (name = 'scp_credential'"
                       "        OR name = 'smb_credential'"
                       "        OR name = 'tp_sms_credential'"
                       "        OR name = 'verinice_server_credential')"
                       " AND data = '%s'",
                       uuid));

  free (uuid);
  return ret;
}

/**
 * @brief Check whether a trashcan Credential is in use.
 *
 * @param[in]  credential  Credential.
 *
 * @return 1 yes, 0 no.
 */
int
trash_credential_in_use (credential_t credential)
{
  int ret;
  char *uuid = trash_credential_uuid (credential);

  ret = !!(sql_int ("SELECT count (*) FROM targets_trash_login_data"
                     " WHERE credential = %llu"
                     " AND credential_location"
                     "      = " G_STRINGIFY (LOCATION_TRASH) ";",
                     credential)
           || sql_int ("SELECT count (*) FROM scanners_trash"
                       " WHERE credential = %llu"
                       " AND credential_location"
                       "      = " G_STRINGIFY (LOCATION_TRASH) ";",
                       credential)
           || sql_int ("SELECT count (*) FROM alert_method_data_trash"
                       " WHERE (name = 'scp_credential'"
                       "        OR name = 'smb_credential'"
                       "        OR name = 'tp_sms_credential'"
                       "        OR name = 'verinice_server_credential')"
                       " AND data = '%s'",
                       uuid));

  free (uuid);
  return ret;
}

/**
 * @brief Check whether a Credential is writable.
 *
 * @param[in]  credential  Credential.
 *
 * @return 1 yes, 0 no.
 */
int
credential_writable (credential_t credential)
{
  return 1;
}

/**
 * @brief Check whether a trashcan Credential is writable.
 *
 * @param[in]  credential  Credential.
 *
 * @return 1 yes, 0 no.
 */
int
trash_credential_writable (credential_t credential)
{
  return 1;
}

/**
 * @brief Get a value from a credential.
 *
 * @param[in]  credential     The Credential.
 * @param[in]  value_name     Name of the value.
 */
gchar *
credential_value (credential_t credential, const char* value_name)
{
  if (credential == 0)
    return NULL;

  return sql_string ("SELECT value FROM credentials_data"
                     " WHERE credential = %llu"
                     "   AND type = '%s';",
                     credential, value_name);
}

/**
 * @brief Get a possibly encrypted credential value in decrypted form.
 *
 * @param[in]  credential     The Credential.
 * @param[in]  value_name     Name of the value.
 */
gchar *
credential_encrypted_value (credential_t credential, const char* value_name)
{
  gchar *value;
  value = sql_string ("SELECT value FROM credentials_data"
                      " WHERE credential = %llu"
                      "   AND type = '%s';",
                      credential, value_name);

  if (value == NULL)
    {
      gchar *secret;
      const char* decrypted;
      lsc_crypt_ctx_t crypt_ctx;
      crypt_ctx = lsc_crypt_new ();

      secret = sql_string ("SELECT value FROM credentials_data"
                           " WHERE credential = %llu"
                           "   AND type = 'secret';",
                           credential);

      decrypted = lsc_crypt_decrypt (crypt_ctx, secret, value_name);
      if (decrypted)
        value = g_strdup (decrypted);
      lsc_crypt_release (crypt_ctx);
      g_free (secret);
    }

  return value;
}

/**
 * @brief Set the name of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  name            Name.
 */
void
set_credential_name (credential_t credential, const char *name)
{
  gchar *quoted_name = sql_quote (name);
  sql ("UPDATE credentials SET name = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       credential);
  g_free (quoted_name);
}

/**
 * @brief Set the comment of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  comment         Comment.
 */
void
set_credential_comment (credential_t credential,
                        const char *comment)
{
  gchar *quoted_comment = sql_quote (comment);
  sql ("UPDATE credentials SET comment = '%s', modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_comment,
       credential);
  g_free (quoted_comment);
}

/**
 * @brief Set the login of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  login           Login.
 */
void
set_credential_login (credential_t credential, const char *login)
{
  set_credential_data (credential, "username", login);
  sql ("UPDATE credentials SET"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
}

/**
 * @brief Set the certificate of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  certificate     Certificate.
 */
void
set_credential_certificate (credential_t credential, const char *certificate)
{
  set_credential_data (credential, "certificate", certificate);
  sql ("UPDATE credentials SET"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
}

/**
 * @brief Set the auth_algorithm of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  algorithm       Authentication algorithm.
 */
void
set_credential_auth_algorithm (credential_t credential, const char *algorithm)
{
  set_credential_data (credential, "auth_algorithm", algorithm);
  sql ("UPDATE credentials SET"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
}

/**
 * @brief Set the privacy_algorithm of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  algorithm       Privacy algorithm.
 */
void
set_credential_privacy_algorithm (credential_t credential,
                                  const char *algorithm)
{
  set_credential_data (credential, "privacy_algorithm", algorithm);
  sql ("UPDATE credentials SET"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
}

/**
 * @brief Set the password of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  password        Password.
 */
void
set_credential_password (credential_t credential, const char *password)
{
  lsc_crypt_ctx_t crypt_ctx;

  if (!disable_encrypted_credentials)
    {
      gchar *encrypted_blob;
      crypt_ctx = lsc_crypt_new ();
      encrypted_blob = lsc_crypt_encrypt (crypt_ctx,
                                          "password", password, NULL);
      if (!encrypted_blob)
        {
          g_critical ("%s: encryption failed", G_STRFUNC);
          lsc_crypt_release (crypt_ctx);
          return;
        }
      set_credential_data (credential, "secret", encrypted_blob);
      set_credential_data (credential, "password", NULL);
      set_credential_data (credential, "private_key", NULL);
      g_free (encrypted_blob);
    }
  else
    {
      crypt_ctx = NULL;
      set_credential_data (credential, "secret", NULL);
      set_credential_data (credential, "password", password);
      set_credential_data (credential, "private_key", NULL);
    }

  sql ("UPDATE credentials SET modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
  lsc_crypt_release (crypt_ctx);
}

/**
 * @brief Set the private key and passphrase of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  private_key     Private key.
 * @param[in]  passphrase      Passphrase.
 */
void
set_credential_private_key (credential_t credential,
                            const char *private_key, const char *passphrase)
{
  lsc_crypt_ctx_t crypt_ctx;

  if (!disable_encrypted_credentials)
    {
      gchar *encrypted_blob;
      crypt_ctx = lsc_crypt_new ();
      encrypted_blob = lsc_crypt_encrypt (crypt_ctx,
                                          "private_key", private_key,
                                          "password", passphrase,
                                          NULL);
      if (!encrypted_blob)
        {
          g_critical ("%s: encryption failed", G_STRFUNC);
          lsc_crypt_release (crypt_ctx);
          return;
        }
      set_credential_data (credential, "secret", encrypted_blob);
      set_credential_data (credential, "password", NULL);
      set_credential_data (credential, "private_key", NULL);
      g_free (encrypted_blob);
    }
  else
    {
      crypt_ctx = NULL;
      set_credential_data (credential, "secret", NULL);
      set_credential_data (credential, "password", passphrase);
      set_credential_data (credential, "private_key", private_key);
    }

  sql ("UPDATE credentials SET modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
  lsc_crypt_release (crypt_ctx);
}

/**
 * @brief Set the community, password and privacy password of a Credential.
 *
 * @param[in]  credential         The Credential.
 * @param[in]  community          SNMP community.
 * @param[in]  password           Authentication password.
 * @param[in]  privacy_password   Privacy password.
 */
void
set_credential_snmp_secret (credential_t credential, const char* community,
                            const char *password, const char *privacy_password)
{
  lsc_crypt_ctx_t crypt_ctx;

  if (!disable_encrypted_credentials)
    {
      gchar *encrypted_blob;
      crypt_ctx = lsc_crypt_new ();
      encrypted_blob = lsc_crypt_encrypt (crypt_ctx,
                                          "community", community,
                                          "password", password,
                                          "privacy_password", privacy_password,
                                          NULL);
      if (!encrypted_blob)
        {
          g_critical ("%s: encryption failed", G_STRFUNC);
          lsc_crypt_release (crypt_ctx);
          return;
        }
      set_credential_data (credential, "secret", encrypted_blob);
      set_credential_data (credential, "community", NULL);
      set_credential_data (credential, "password", NULL);
      set_credential_data (credential, "privacy_password", NULL);
      g_free (encrypted_blob);
    }
  else
    {
      crypt_ctx = NULL;
      set_credential_data (credential, "secret", NULL);
      set_credential_data (credential, "community", community);
      set_credential_data (credential, "password", password);
      set_credential_data (credential, "privacy_password", privacy_password);
    }

  sql ("UPDATE credentials SET modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
  lsc_crypt_release (crypt_ctx);
}

/**
 * @brief Initialise a Credential iterator, given a single Credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  credential      Single Credential to iterate.
 */
void
init_credential_iterator_one (iterator_t* iterator,
                              credential_t credential)
{
  get_data_t get;

  assert (credential);

  memset (&get, '\0', sizeof (get));
  get.id = credential_uuid (credential);
  get.filter = "owner=any permission=get_credentials";

  init_credential_iterator (iterator, &get);
}

/**
 * @brief Initialise a 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_credential_iterator (iterator_t* iterator, const get_data_t *get)
{
  static const char *filter_columns[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = CREDENTIAL_ITERATOR_COLUMNS;
  static column_t trash_columns[] = CREDENTIAL_ITERATOR_TRASH_COLUMNS;

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

/*
 * Common code for getting possibly encrypted data from credentials.
 */
static const char*
credential_iterator_encrypted_data (iterator_t* iterator, const char* type)
{
  const char *secret, *unencrypted;

  if (iterator->done)
    return NULL;
  secret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
  if (type == NULL)
    {
      g_warning ("%s: NULL data type given", __FUNCTION__);
      return NULL;
    }
  else if (strcmp (type, "password") == 0)
    unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 7);
  else if (strcmp (type, "private_key") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
  else if (strcmp (type, "community") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
  else if (strcmp (type, "privacy_password") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10);
  else
    {
      g_warning ("%s: unknown data type \"%s\"", __FUNCTION__, type);
      return NULL;
    }
  /* If we do not have a private key, there is no encrypted data.
     Return the password as is or NULL.  */
  if (secret)
    {
      /* This is an encrypted credential.  */
      if (!iterator->crypt_ctx)
        iterator->crypt_ctx = lsc_crypt_new ();

      return lsc_crypt_decrypt (iterator->crypt_ctx, secret, type);
    }
  else
    {
      return unencrypted;
    }
}

/**
 * @brief Get the login from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Login, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_type, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the login from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Login, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
int credential_iterator_allow_insecure (iterator_t* iterator)
{
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the credential type abbreviation from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Credential type, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_login, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the certificate from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Certificate, or NULL if iteration is complete. Freed by
 *          cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_certificate, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the authentication algorithm from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Auth algorithm, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_auth_algorithm, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the authentication algorithm from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Auth algorithm, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_privacy_algorithm,
            GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the password from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Password, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
credential_iterator_password (iterator_t* iterator)
{
  return credential_iterator_encrypted_data (iterator, "password");
}


/**
 * @brief Get the private_key from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Private_key, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
credential_iterator_private_key (iterator_t* iterator)
{
  return credential_iterator_encrypted_data (iterator, "private_key");
}


/**
 * @brief Get the SNMP community from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SNMP community, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
credential_iterator_community (iterator_t* iterator)
{
  return credential_iterator_encrypted_data (iterator, "community");
}


/**
 * @brief Get the privacy password from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SNMP community, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
const char*
credential_iterator_privacy_password (iterator_t* iterator)
{
  return credential_iterator_encrypted_data (iterator, "privacy_password");
}


/**
 * @brief Get the rpm from a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Rpm, or NULL if iteration is complete. Free with g_free().
 */
char*
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 = credential_iterator_private_key (iterator);
  pass = credential_iterator_password (iterator);
  public_key = openvas_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = 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 a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Deb, or NULL if iteration is complete. Free with g_free().
 */
char*
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 = credential_iterator_private_key (iterator);
  pass = credential_iterator_password (iterator);
  public_key = openvas_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = credential_iterator_login (iterator);
  if (lsc_user_rpm_recreate (login, public_key, &rpm, &rpm_size))
    {
      g_warning ("%s: Failed to create RPM base for DEB\n", __FUNCTION__);
      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 a Credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Exe, or NULL if iteration is complete. Free with g_free().
 */
char*
credential_iterator_exe (iterator_t *iterator)
{
  const char *login, *password;
  void *exe;
  gsize exe_size;
  gchar *exe64;

  if (iterator->done) return NULL;

  login = credential_iterator_login (iterator);
  password = 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 a Credential.
 *
 * @param[in]  credential  Credential.
 *
 * @return UUID.
 */
char*
credential_uuid (credential_t credential)
{
  return sql_string ("SELECT uuid FROM credentials WHERE id = %llu;",
                     credential);
}

/**
 * @brief Get the UUID of a Credential in the trashcan.
 *
 * @param[in]  credential  Credential.
 *
 * @return UUID.
 */
char*
trash_credential_uuid (credential_t credential)
{
  return sql_string ("SELECT uuid FROM credentials_trash"
                     " WHERE id = %llu;",
                     credential);
}

/**
 * @brief Get the name of an LSC credential.
 *
 * @param[in]  credential  Credential.
 *
 * @return Name.
 */
char*
credential_name (credential_t credential)
{
  return sql_string ("SELECT name FROM credentials WHERE id = %llu;",
                     credential);
}

/**
 * @brief Get the name of an LSC credential in the trashcan.
 *
 * @param[in]  credential  Credential.
 *
 * @return Name.
 */
char*
trash_credential_name (credential_t credential)
{
  return sql_string ("SELECT name FROM credentials_trash"
                     " WHERE id = %llu;",
                     credential);
}

/**
 * @brief Get the type of a Credential.
 *
 * @param[in]  credential  Credential.
 *
 * @return Credential type.
 */
char*
credential_type (credential_t credential)
{
  return sql_string ("SELECT type FROM credentials WHERE id = %llu;",
                     credential);
}

/**
 * @brief Return whether a trashcan credential is readable.
 *
 * @param[in]  credential  Credential.
 *
 * @return 1 if readable, else 0.
 */
int
trash_credential_readable (credential_t credential)
{
  char *uuid;
  credential_t found = 0;

  if (credential == 0)
    return 0;
  uuid = credential_uuid (credential);
  if (find_trash ("credential", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Initialise a Credential target iterator.
 *
 * Iterates over all targets that use the credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  credential      Name of credential.
 * @param[in]  ascending       Whether to sort ascending or descending.
 */
void
init_credential_target_iterator (iterator_t* iterator,
                                 credential_t credential,
                                 int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (credential);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_targets"));
  available = acl_where_owned ("target", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT uuid, name, %s FROM targets"
                 " WHERE id IN"
                 "   (SELECT target FROM targets_login_data"
                 "    WHERE credential = %llu)"
                 " ORDER BY name %s;",
                 available,
                 credential,
                 ascending ? "ASC" : "DESC");

  g_free (available);
}

/**
 * @brief Get the uuid from an Credential Target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Uuid, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_target_iterator_uuid, 0);

/**
 * @brief Get the name from an Credential Target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (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
credential_target_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}

/**
 * @brief Initialise a Credential scanner iterator.
 *
 * Iterates over all scanners that use the credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  credential      Name of credential.
 * @param[in]  ascending       Whether to sort ascending or descending.
 */
void
init_credential_scanner_iterator (iterator_t* iterator,
                                  credential_t credential,
                                  int ascending)
{
  gchar *available;
  get_data_t get;
  array_t *permissions;

  assert (credential);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_scanners"));
  available = acl_where_owned ("scanner", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT uuid, name, %s FROM scanners"
                 " WHERE credential = %llu"
                 " ORDER BY name %s;",
                 available,
                 credential,
                 ascending ? "ASC" : "DESC");

  g_free (available);
}

/**
 * @brief Get the uuid from an Credential Scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Uuid, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_scanner_iterator_uuid, 0);

/**
 * @brief Get the name from an Credential Scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_scanner_iterator_name, 1);

/**
 * @brief Get the read permission status from a Credential Scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
credential_scanner_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.  Private
 *                                  directory is only checked if this is given.
 *
 * @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;

  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);
      g_debug ("signature_filename: %s\n", signature_filename);

      g_file_get_contents (signature_filename, signature, signature_size,
                           &error);
      if (error)
        {
          if (uuid && (error->code == G_FILE_ERROR_NOENT))
            {
              char *real;
              gchar **split;

              /* Note: this only happens if uuid is given, so it does not
               * happen for agents. */

              g_error_free (error);
              error = NULL;
              signature_filename = g_build_filename (OPENVAS_STATE_DIR,
                                                     "openvasmd",
                                                     "signatures",
                                                     location,
                                                     signature_basename,
                                                     NULL);
              g_debug ("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);
              g_debug ("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));
              g_debug ("*uuid: %s\n", *uuid);
              g_strfreev (split);
              free (real);
              return 0;
            }
          else
            {
              g_debug ("%s: failed to read %s: %s\n", __FUNCTION__,
                       signature_filename, error->message);
              g_free (signature_filename);
            }

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

      g_debug ("%s: finding signature\n", __FUNCTION__);

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

              g_debug ("%s: trying database signature\n", __FUNCTION__);

              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)
            {
              g_debug ("%s: trying feed signature\n", __FUNCTION__);

              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, KEYWORD_TYPE_STRING },                        \
   { "installer_64", NULL, KEYWORD_TYPE_STRING },                     \
   { "installer_filename", NULL, KEYWORD_TYPE_STRING },               \
   { "installer_signature_64", NULL, KEYWORD_TYPE_STRING },           \
   { "installer_trust" , NULL, KEYWORD_TYPE_STRING },                 \
   { "installer_trust_time", NULL, KEYWORD_TYPE_STRING },             \
   { "howto_install", NULL, KEYWORD_TYPE_STRING },                    \
   { "howto_use", NULL, KEYWORD_TYPE_STRING },                        \
   {                                                                  \
     "(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",                                                         \
    KEYWORD_TYPE_STRING                                               \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

/**
 * @brief Agent iterator columns for trash case.
 */
#define AGENT_ITERATOR_TRASH_COLUMNS                                  \
 {                                                                    \
   GET_ITERATOR_COLUMNS (agents_trash),                               \
   { "installer", NULL, KEYWORD_TYPE_STRING },                        \
   { "installer_64", NULL, KEYWORD_TYPE_STRING },                     \
   { "installer_filename", NULL, KEYWORD_TYPE_STRING },               \
   { "installer_signature_64", NULL, KEYWORD_TYPE_STRING },           \
   { "installer_trust" , NULL, KEYWORD_TYPE_STRING },                 \
   { "installer_trust_time", NULL, KEYWORD_TYPE_STRING },             \
   { "howto_install", NULL, KEYWORD_TYPE_STRING },                    \
   { "howto_use", NULL, KEYWORD_TYPE_STRING },                        \
   {                                                                  \
     "(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",                                                         \
    KEYWORD_TYPE_STRING                                               \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

/**
 * @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 (acl_user_may ("create_note") == 0)
    return 99;

  if (nvt == NULL)
    return -1;

  quoted_nvt = sql_quote (nvt);
  if (g_str_has_prefix (nvt, "CVE-"))
    {
      if (sql_int ("SELECT count (*) FROM cves WHERE uuid = '%s'", quoted_nvt)
          == 0)
        {
          g_free (quoted_nvt);
          return 1;
        }
    }
  else 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 (acl_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",   \
   "end_time", "active_days", NULL }

/**
 * @brief Note iterator columns.
 */
#define NOTE_ITERATOR_COLUMNS                                              \
 {                                                                         \
   { "notes.id", "id", KEYWORD_TYPE_INTEGER },                             \
   { "notes.uuid", "uuid", KEYWORD_TYPE_STRING },                          \
   {                                                                       \
     "(CASE"                                                               \
     " WHEN notes.nvt LIKE 'CVE-%%'"                                       \
     " THEN notes.nvt"                                                     \
     " ELSE (SELECT name FROM nvts WHERE oid = notes.nvt)"                 \
     " END)",                                                              \
     "name",                                                               \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                     \
   { "iso_time (notes.creation_time)", NULL, KEYWORD_TYPE_STRING },        \
   { "iso_time (notes.modification_time)", NULL, KEYWORD_TYPE_STRING },    \
   { "notes.creation_time", "created", KEYWORD_TYPE_INTEGER },             \
   { "notes.modification_time", "modified", KEYWORD_TYPE_INTEGER },        \
   { "(SELECT name FROM users WHERE users.id = notes.owner)",              \
     "_owner",                                                             \
     KEYWORD_TYPE_STRING },                                                \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                                \
   /* Columns specific to notes. */                                        \
   { "notes.nvt", "oid", KEYWORD_TYPE_STRING },                            \
   { "notes.text", "text", KEYWORD_TYPE_STRING },                          \
   { "notes.hosts", "hosts", KEYWORD_TYPE_STRING },                        \
   { "notes.port", "port", KEYWORD_TYPE_STRING },                          \
   { "severity_to_level (notes.severity, 1)",                              \
     "threat",                                                             \
     KEYWORD_TYPE_STRING },                                                \
   { "notes.task", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "notes.result", "result", KEYWORD_TYPE_INTEGER },                     \
   { "notes.end_time", "end_time", KEYWORD_TYPE_INTEGER },                 \
   { "(notes.end_time = 0) OR (notes.end_time >= m_now ())",               \
     "active",                                                             \
     KEYWORD_TYPE_INTEGER },                                               \
   {                                                                       \
     "(CASE"                                                               \
     " WHEN notes.nvt LIKE 'CVE-%%'"                                       \
     " THEN notes.nvt"                                                     \
     " ELSE (SELECT name FROM nvts WHERE oid = notes.nvt)"                 \
     " END)",                                                              \
     "nvt",                                                                \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   { "notes.nvt", "nvt_id", KEYWORD_TYPE_STRING },                         \
   { "(SELECT uuid FROM tasks WHERE id = notes.task)",                     \
     "task_id",                                                            \
     KEYWORD_TYPE_STRING },                                                \
   { "(SELECT name FROM tasks WHERE id = notes.task)",                     \
     "task_name",                                                          \
     KEYWORD_TYPE_STRING },                                                \
   { "notes.severity", "severity", KEYWORD_TYPE_DOUBLE },                  \
   { "(SELECT name FROM users WHERE users.id = notes.owner)",              \
     "_owner",                                                             \
     KEYWORD_TYPE_STRING },                                                \
   { "days_from_now (notes.end_time)",                                     \
     "active_days",                                                        \
     KEYWORD_TYPE_INTEGER },                                               \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @brief Note iterator columns for trash case.
 */
#define NOTE_ITERATOR_TRASH_COLUMNS                                              \
 {                                                                               \
   { "notes_trash.id", "id", KEYWORD_TYPE_INTEGER },                             \
   { "notes_trash.uuid", "uuid", KEYWORD_TYPE_STRING },                          \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                           \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                           \
   { "iso_time (notes_trash.creation_time)", NULL, KEYWORD_TYPE_STRING },        \
   { "iso_time (notes_trash.modification_time)", NULL, KEYWORD_TYPE_STRING },    \
   { "notes_trash.creation_time", "created", KEYWORD_TYPE_INTEGER },             \
   { "notes_trash.modification_time", "modified", KEYWORD_TYPE_INTEGER },        \
   { "(SELECT name FROM users WHERE users.id = notes_trash.owner)",              \
     "_owner",                                                                   \
     KEYWORD_TYPE_STRING },                                                      \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                                      \
   /* Columns specific to notes_trash. */                                        \
   { "notes_trash.nvt", "oid", KEYWORD_TYPE_STRING },                            \
   { "notes_trash.text", "text", KEYWORD_TYPE_STRING  },                         \
   { "notes_trash.hosts", "hosts", KEYWORD_TYPE_STRING },                        \
   { "notes_trash.port", "port", KEYWORD_TYPE_STRING },                          \
   { "severity_to_level (notes_trash.severity, 1)",                              \
     "threat",                                                                   \
     KEYWORD_TYPE_STRING },                                                      \
   { "notes_trash.task", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "notes_trash.result", "result", KEYWORD_TYPE_INTEGER },                     \
   { "notes_trash.end_time", NULL, KEYWORD_TYPE_INTEGER },                       \
   { "(notes_trash.end_time = 0) OR (notes_trash.end_time >= m_now ())",         \
     "active",                                                                   \
     KEYWORD_TYPE_INTEGER },                                                     \
   {                                                                             \
     "(CASE"                                                                     \
     " WHEN notes_trash.nvt LIKE 'CVE-%%'"                                       \
     " THEN notes_trash.nvt"                                                     \
     " ELSE (SELECT name FROM nvts WHERE oid = notes_trash.nvt)"                 \
     " END)",                                                                    \
     "nvt",                                                                      \
     KEYWORD_TYPE_STRING                                                         \
   },                                                                            \
   { "notes_trash.nvt", "nvt_id", KEYWORD_TYPE_STRING },                         \
   { "(SELECT uuid FROM tasks WHERE id = notes_trash.task)",                     \
     "task_id",                                                                  \
     KEYWORD_TYPE_STRING },                                                      \
   { "(SELECT name FROM tasks WHERE id = notes_trash.task)",                     \
     "task_name",                                                                \
     KEYWORD_TYPE_STRING },                                                      \
   { "notes_trash.severity", "severity", KEYWORD_TYPE_DOUBLE },                  \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                          \
 }

/**
 * @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 CAST (nvts.cvss_base AS real)"
                                        " 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))",
                        nvt);
    }
  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 CAST (nvts.cvss_base AS real)"
                                        " 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))",
                        nvt);
    }
  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 NVT type from a note iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT type, or NULL.  Static string.
 */
const char *
note_iterator_nvt_type (iterator_t *iterator)
{
  const char *oid;

  oid = note_iterator_nvt_oid (iterator);
  if (oid == NULL)
    return NULL;

  if (g_str_has_prefix (oid, "CVE-"))
    return "cve";

  if (g_str_has_prefix (oid, "oval:"))
    return "ovaldef";

  return "nvt";
}

/**
 * @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;
  GHashTable *reports;
  GHashTableIter reports_iter;
  report_t *reports_ptr;
  gchar *override_id, *users_where;
  int auto_cache_rebuild;
  override_t new_override;

  if (acl_user_may ("create_override") == 0)
    return 99;

  if (nvt == NULL)
    return -1;

  if (text == NULL)
    return -1;

  quoted_nvt = sql_quote (nvt);
  if (g_str_has_prefix (nvt, "CVE-"))
    {
      if (sql_int ("SELECT count (*) FROM cves WHERE uuid = '%s'", quoted_nvt)
          == 0)
        {
          g_free (quoted_nvt);
          return 1;
        }
    }
  else 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 ();
  new_override = sql_last_insert_id ();

  override_uuid (new_override, &override_id);
  users_where = acl_users_with_access_where ("override", override_id, NULL,
                                             "id");

  reports = reports_for_override (new_override);
  reports_ptr = NULL;
  g_hash_table_iter_init (&reports_iter, reports);
  auto_cache_rebuild = setting_auto_cache_rebuild_int ();
  while (g_hash_table_iter_next (&reports_iter,
                                 ((gpointer*)&reports_ptr), NULL))
    {
      if (auto_cache_rebuild)
        report_cache_counts (*reports_ptr, 0, 1, users_where);
      else
        report_clear_count_cache (*reports_ptr, 0, 1, users_where);
    }
  g_hash_table_destroy (reports);
  g_free (override_id);
  g_free (users_where);

  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;
  GHashTable *reports;
  GHashTableIter reports_iter;
  report_t *reports_ptr;
  gchar *users_where;
  int auto_cache_rebuild;

  sql_begin_immediate ();

  if (acl_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;
    }

  reports = reports_for_override (override);

  users_where = acl_users_with_access_where ("override", override_id, NULL,
                                             "id");

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

  g_hash_table_iter_init (&reports_iter, reports);
  reports_ptr = NULL;
  auto_cache_rebuild = setting_auto_cache_rebuild_int ();
  while (g_hash_table_iter_next (&reports_iter,
                                 ((gpointer*)&reports_ptr), NULL))
    {
      if (auto_cache_rebuild)
        report_cache_counts (*reports_ptr, 0, 1, users_where);
      else
        report_clear_count_cache (*reports_ptr, 0, 1, users_where);
    }
  g_hash_table_destroy (reports);
  g_free (users_where);

  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;
  GHashTable *reports;
  GString *cache_invalidated_sql;
  int cache_invalidated;

  reports = NULL;
  cache_invalidated = 0;

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

  // Tests if a cache rebuild is necessary.
  //  The "active" status is checked separately
  cache_invalidated_sql = g_string_new ("");

  g_string_append_printf (cache_invalidated_sql,
                          "SELECT (cast (new_severity AS numeric) != %1.1f)",
                          new_severity_dbl);

  g_string_append_printf (cache_invalidated_sql,
                          " OR (task != %llu)",
                          task);

  g_string_append_printf (cache_invalidated_sql,
                          " OR (result != %llu)",
                          result);

  if (strcmp (quoted_severity, "NULL") == 0)
    g_string_append_printf (cache_invalidated_sql,
                            " OR (severity IS NOT NULL)");
  else
    g_string_append_printf (cache_invalidated_sql,
                            " OR (cast (severity AS numeric) != %1.1f)",
                            severity_dbl);

  if (strcmp (quoted_hosts, "NULL") == 0)
    g_string_append_printf (cache_invalidated_sql,
                            " OR (hosts IS NOT NULL)");
  else
    g_string_append_printf (cache_invalidated_sql,
                            " OR (hosts != %s)",
                            quoted_hosts);

  if (strcmp (quoted_port, "NULL") == 0)
    g_string_append_printf (cache_invalidated_sql,
                            " OR (hosts IS NOT NULL)");
  else
    g_string_append_printf (cache_invalidated_sql,
                            " OR (port != %s)",
                            quoted_port);

  g_string_append_printf (cache_invalidated_sql,
                          " FROM overrides WHERE id = %llu",
                          override);

  if (sql_int ("%s", cache_invalidated_sql->str))
    {
      cache_invalidated = 1;
    }

  g_string_free (cache_invalidated_sql, TRUE);

  // Check active status for changes, get old reports for rebuild if necessary
  //  and update override.
  if ((active == NULL) || (strcmp (active, "-2") == 0))
    {
      if (cache_invalidated)
        reports = reports_for_override (override);

      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;
      int new_end_time;

      if (strcmp (point, "-1"))
        {
          while (*point && isdigit (*point)) point++;
          if (*point)
            {
              return 1;
            }
        }

      new_end_time = (strcmp (active, "-1")
                        ? (strcmp (active, "0")
                            ? (time (NULL) + atoi (active) * 60 * 60 * 24)
                            : 1)
                        : 0);

      if (cache_invalidated == 0
          && sql_int ("SELECT end_time != %d FROM overrides"
                      " WHERE id = %llu",
                      new_end_time, override))
        cache_invalidated = 1;

      if (cache_invalidated)
        reports = reports_for_override (override);

      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;",
           new_end_time,
           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);

  if (cache_invalidated)
    {
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      gchar *override_id, *users_where;
      int auto_cache_rebuild;

      override_uuid (override, &override_id);
      users_where = acl_users_with_access_where ("override", override_id, NULL,
                                                 "id");

      reports_add_for_override (reports, override);

      g_hash_table_iter_init (&reports_iter, reports);
      reports_ptr = NULL;
      auto_cache_rebuild = setting_auto_cache_rebuild_int ();
      while (g_hash_table_iter_next (&reports_iter,
                                    ((gpointer*)&reports_ptr), NULL))
        {
          if (auto_cache_rebuild)
            report_cache_counts (*reports_ptr, 0, 1, users_where);
          else
            report_clear_count_cache (*reports_ptr, 0, 1, users_where);
        }
      g_free (override_id);
      g_free (users_where);
    }

  if (reports)
    g_hash_table_destroy (reports);

  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", "active_days", NULL }

/**
 * @brief Override iterator columns.
 */
#define OVERRIDE_ITERATOR_COLUMNS                                           \
 {                                                                          \
   { "overrides.id", "id", KEYWORD_TYPE_INTEGER },                          \
   { "overrides.uuid", "uuid", KEYWORD_TYPE_STRING },                       \
   {                                                                        \
     "(CASE"                                                                \
     " WHEN overrides.nvt LIKE 'CVE-%%'"                                    \
     " THEN overrides.nvt"                                                  \
     " ELSE (SELECT name FROM nvts WHERE oid = overrides.nvt)"              \
     " END)",                                                               \
     "name",                                                                \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                      \
   { "iso_time (overrides.creation_time)", NULL, KEYWORD_TYPE_STRING },     \
   { "iso_time (overrides.modification_time)", NULL, KEYWORD_TYPE_STRING }, \
   { "overrides.creation_time", "created", KEYWORD_TYPE_INTEGER },          \
   { "overrides.modification_time", "modified", KEYWORD_TYPE_INTEGER },     \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides.owner)",           \
     "_owner",                                                              \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                                 \
   /* Columns specific to overrides. */                                     \
   { "overrides.nvt", "oid", KEYWORD_TYPE_STRING },                         \
   { "overrides.text", "text", KEYWORD_TYPE_STRING },                       \
   { "overrides.hosts", "hosts", KEYWORD_TYPE_STRING },                     \
   { "overrides.port", "port", KEYWORD_TYPE_STRING },                       \
   { "severity_to_level (overrides.severity, 1)",                           \
     "threat",                                                              \
     KEYWORD_TYPE_STRING },                                                 \
   { "severity_to_level (overrides.new_severity, 0)",                       \
     "new_threat",                                                          \
     KEYWORD_TYPE_STRING },                                                 \
   { "overrides.task", NULL, KEYWORD_TYPE_STRING },                         \
   { "overrides.result", "result", KEYWORD_TYPE_INTEGER },                  \
   { "overrides.end_time", NULL, KEYWORD_TYPE_INTEGER },                    \
   {                                                                        \
     "(overrides.end_time = 0) OR (overrides.end_time >= m_now ())",        \
     "active",                                                              \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   {                                                                        \
     "(CASE"                                                                \
     " WHEN overrides.nvt LIKE 'CVE-%%'"                                    \
     " THEN overrides.nvt"                                                  \
     " ELSE (SELECT name FROM nvts WHERE oid = overrides.nvt)"              \
     " END)",                                                               \
     "name",                                                                \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "overrides.nvt", "nvt_id", KEYWORD_TYPE_STRING },                      \
   { "(SELECT uuid FROM tasks WHERE id = overrides.task)",                  \
     "task_id",                                                             \
     KEYWORD_TYPE_STRING },                                                 \
   { "(SELECT name FROM tasks WHERE id = overrides.task)",                  \
     "task_name",                                                           \
     KEYWORD_TYPE_STRING },                                                 \
   { "overrides.severity", "severity", KEYWORD_TYPE_DOUBLE },               \
   { "overrides.new_severity", "new_severity", KEYWORD_TYPE_DOUBLE },       \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides.owner)",           \
     "_owner",                                                              \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "days_from_now (overrides.end_time)",                                  \
     "active_days",                                                         \
     KEYWORD_TYPE_INTEGER },                                                \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @brief Override iterator columns for trash case.
 */
#define OVERRIDE_ITERATOR_TRASH_COLUMNS                                     \
 {                                                                          \
   { "overrides_trash.id", "id", KEYWORD_TYPE_INTEGER },                    \
   { "overrides_trash.uuid", "uuid", KEYWORD_TYPE_STRING },                 \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                      \
   { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING },                      \
   { "iso_time (overrides_trash.creation_time)",                            \
     NULL,                                                                  \
     KEYWORD_TYPE_STRING },                                                 \
   { "iso_time (overrides_trash.modification_time)",                        \
     NULL,                                                                  \
     KEYWORD_TYPE_STRING },                                                 \
   { "overrides_trash.creation_time",                                       \
     "created",                                                             \
     KEYWORD_TYPE_INTEGER },                                                \
   { "overrides_trash.modification_time",                                   \
     "modified",                                                            \
     KEYWORD_TYPE_INTEGER },                                                \
   {                                                                        \
     "(SELECT name FROM users WHERE users.id = overrides_trash.owner)",     \
     "_owner",                                                              \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "owner", NULL, KEYWORD_TYPE_STRING },                                  \
   /* Columns specific to overrides_trash. */                               \
   { "overrides_trash.nvt", "oid", KEYWORD_TYPE_STRING },                   \
   { "overrides_trash.text", "text", KEYWORD_TYPE_STRING },                 \
   { "overrides_trash.hosts", "hosts", KEYWORD_TYPE_STRING },               \
   { "overrides_trash.port", "port", KEYWORD_TYPE_STRING },                 \
   { "severity_to_level (overrides_trash.severity, 1)",                     \
     "threat",                                                              \
     KEYWORD_TYPE_STRING },                                                 \
   { "severity_to_level (overrides_trash.new_severity, 0)",                 \
     "new_threat",                                                          \
     KEYWORD_TYPE_STRING },                                                 \
   { "overrides_trash.task", NULL, KEYWORD_TYPE_INTEGER },                  \
   { "overrides_trash.result", "result", KEYWORD_TYPE_INTEGER },            \
   { "overrides_trash.end_time", NULL, KEYWORD_TYPE_INTEGER },              \
   {                                                                        \
     "(overrides_trash.end_time = 0)"                                       \
     " OR (overrides_trash.end_time >= m_now ())",                          \
     "active",                                                              \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   {                                                                        \
     "(CASE"                                                                \
     " WHEN overrides_trash.nvt LIKE 'CVE-%%'"                              \
     " THEN overrides_trash.nvt"                                            \
     " ELSE (SELECT name FROM nvts WHERE oid = overrides_trash.nvt)"        \
     " END)",                                                               \
     "nvt",                                                                 \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "overrides_trash.nvt", "nvt_id", KEYWORD_TYPE_STRING },                \
   {                                                                        \
     "(SELECT uuid FROM tasks WHERE id = overrides_trash.task)",            \
     "task_id",                                                             \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   {                                                                        \
     "(SELECT name FROM tasks WHERE id = overrides_trash.task)",            \
     "task_name",                                                           \
     KEYWORD_TYPE_STRING                                                    \
   },                                                                       \
   { "overrides_trash.severity", NULL, KEYWORD_TYPE_DOUBLE },               \
   { "overrides_trash.new_severity", NULL, KEYWORD_TYPE_DOUBLE },           \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                     \
 }

/**
 * @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 CAST (nvts.cvss_base AS real)"
                                        " 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)
    {
      result_clause = g_strdup_printf
                       (" AND (overrides.nvt"
                        "      = (SELECT oid FROM nvts WHERE nvts.id = %llu))",
                        nvt);
    }
  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 CAST (nvts.cvss_base AS real)"
                                        " 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)
    {
      result_clause = g_strdup_printf
                       (" AND (overrides.nvt = (SELECT oid FROM nvts"
                       "                        WHERE nvts.id = %llu))",
                        nvt);
    }
  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 NVT type from a override iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NVT type, or NULL.  Static string.
 */
const char *
override_iterator_nvt_type (iterator_t *iterator)
{
  const char *oid;

  oid = override_iterator_nvt_oid (iterator);
  if (oid == NULL)
    return NULL;

  if (g_str_has_prefix (oid, "CVE-"))
    return "cve";

  if (g_str_has_prefix (oid, "oval:"))
    return "ovaldef";

  return "nvt";
}

/**
 * @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)
{
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;
  credential_t new_credential;
  gchar *credential_id;
  gchar *name_for_credential;

  g_info ("   Creating scanner.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  current_credentials.uuid = "";

  if (!g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error))
    {
      fprintf (stderr, "%s.\n", error->message);
      g_error_free (error);
      manage_option_cleanup ();
      return -1;
    }
  if (!g_file_get_contents (key_pub_path, &key_pub, NULL, &error))
    {
      fprintf (stderr, "%s.\n", error->message);
      g_error_free (error);
      g_free (ca_pub);
      manage_option_cleanup ();
      return -1;
    }
  if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error))
    {
      fprintf (stderr, "%s.\n", error->message);
      g_error_free (error);
      g_free (ca_pub);
      g_free (key_pub);
      manage_option_cleanup ();
      return -1;
    }

  name_for_credential = sql_quote (name);

  if (sql_int ("SELECT count(*) FROM credentials"
               " WHERE name = 'Credential for Scanner %s'"
               "   AND owner = NULL;",
               name_for_credential))
    sql ("INSERT INTO credentials"
         " (uuid, name, owner, comment, type,"
         "  creation_time, modification_time)"
         " VALUES"
         " (make_uuid (),"
         "  uniquify ('scanner', 'Credential for Scanner %s',"
         "            NULL, ''),"
         "  NULL, 'Autogenerated', 'cc',"
         "  m_now (), m_now ());",
         name_for_credential);
  else
    sql ("INSERT INTO credentials"
         " (uuid, name, owner, comment, type,"
         "  creation_time, modification_time)"
         " VALUES"
         " (make_uuid (), 'Credential for Scanner %s',"
         "  NULL, 'Autogenerated', 'cc',"
         "  m_now (), m_now ());",
         name_for_credential);

  g_free (name_for_credential);

  new_credential = sql_last_insert_id();

  set_credential_data (new_credential, "certificate", key_pub);

  if (disable_encrypted_credentials)
    {
      set_credential_data (new_credential, "private_key", key_priv);
    }
  else
    {
      lsc_crypt_ctx_t crypt_ctx;
      char *secret;

      crypt_ctx = lsc_crypt_new ();

      secret = lsc_crypt_encrypt (crypt_ctx,
                                  "private_key", key_priv, NULL);
      if (!secret)
        {
          fprintf (stderr, "Failed to encrypt private key.\n");
          g_free (ca_pub);
          g_free (key_pub);
          g_free (key_priv);
          manage_option_cleanup ();
          return -1;
        }
      set_credential_data (new_credential, "secret", secret);
    }
  credential_id = credential_uuid (new_credential);

  ret = create_scanner (name, NULL, host, port, type, NULL, ca_pub,
                        credential_id);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  switch (ret)
    {
      case 0:
        printf ("Scanner created.\n");
        break;
      case 1:
        fprintf (stderr, "Scanner exists already.\n");
        break;
      case 2:
        fprintf (stderr, "Invalid value provided.\n");
        break;
      default:
        fprintf (stderr, "Failed to create scanner.\n");
        break;
    }

  manage_option_cleanup ();

  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)
{
  int ret;

  assert (uuid);

  g_info ("   Deleting scanner.\n");

  if (!strcmp (uuid, SCANNER_UUID_CVE))
    {
      fprintf (stderr, "Default CVE Scanner can't be deleted.\n");
      return 3;
    }

  if (!strcmp (uuid, SCANNER_UUID_DEFAULT))
    {
      fprintf (stderr, "Default OpenVAS Scanner can't be deleted.\n");
      return 3;
    }

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  current_credentials.uuid = "";
  switch ((ret = delete_scanner (uuid, 1)))
    {
      case 0:
        printf ("Scanner deleted.\n");
        break;
      case 1:
        fprintf (stderr, "Scanner in use.\n");
        break;
      case 2:
        fprintf (stderr, "Failed to find scanner.\n");
        break;
      case 3:
        fprintf (stderr, "Scanner is predefined.\n");
        break;
      default:
        fprintf (stderr, "Internal Error.\n");
        break;
    }
  current_credentials.uuid = NULL;

  manage_option_cleanup ();
  return ret;
}

/**
 * @brief Modify the given scanner.
 *
 * @param[in]  log_config       Log configuration.
 * @param[in]  database         Location of manage database.
 * @param[in]  scanner_id       ID of scanner.
 * @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.  NULL to leave it as is.
 *                              "" to use the default.
 * @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)
{
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;
  scanner_t scanner;
  credential_t new_credential;
  gchar *credential_id;
  gchar *name_for_credential;

  g_info ("   Modifying scanner.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  current_credentials.uuid = "";

  if (scanner_id)
    {
      if (find_scanner (scanner_id, &scanner))
        {
          fprintf (stderr, "Error finding scanner.\n");
          return -1;
        }
      if (scanner == 0)
        {
          fprintf (stderr, "Failed to find scanner %s.\n", scanner_id);
          return 1;
        }
    }
  else
    {
      fprintf (stderr, "Scanner UUID required.\n");
      manage_option_cleanup ();
      return 3;
    }

  if (name)
    name_for_credential = sql_quote (name);
  else
    {
      gchar *current_scanner_name = scanner_name (scanner);
      name_for_credential = sql_quote (current_scanner_name);
      g_free (current_scanner_name);
    }

  if (ca_pub_path)
    {
      if (*ca_pub_path == '\0')
        ca_pub = g_strdup ("");
      else if (g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error) == 0)
        {
          fprintf (stderr, "%s.\n", error->message);
          g_error_free (error);
          manage_option_cleanup ();
          return -1;
        }
    }
  else
    ca_pub = NULL;

  if (key_pub_path)
    {
      if (g_file_get_contents (key_pub_path, &key_pub, NULL, &error) == 0)
        {
          fprintf (stderr, "%s.\n", error->message);
          g_error_free (error);
          g_free (ca_pub);
          manage_option_cleanup ();
          return -1;
        }
    }
  else
    key_pub = scanner_key_pub (scanner);

  if (key_priv_path)
    {
      if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error))
        {
          fprintf (stderr, "%s.\n", error->message);
          g_error_free (error);
          g_free (ca_pub);
          g_free (key_pub);
          manage_option_cleanup ();
          return -1;
        }
    }
  else
    key_priv = scanner_key_priv (scanner);

  if (key_priv || key_pub)
    {
      if (sql_int ("SELECT count(*) FROM credentials"
                   " WHERE name = 'Credential for Scanner %s'"
                   "   AND owner IS NULL;",
                   name_for_credential))
        sql ("INSERT INTO credentials"
             " (uuid, name, owner, comment, type,"
             "  creation_time, modification_time)"
             " VALUES"
             " (make_uuid (),"
             "  uniquify ('scanner', 'Credential for Scanner %s',"
             "            NULL, ''),"
             "  NULL, 'Autogenerated', 'cc',"
             "  m_now (), m_now ());",
             name_for_credential);
      else
        sql ("INSERT INTO credentials"
             " (uuid, name, owner, comment, type,"
             "  creation_time, modification_time)"
             " VALUES"
             " (make_uuid (), 'Credential for Scanner %s',"
             "  NULL, 'Autogenerated', 'cc',"
             "  m_now (), m_now ());",
             name_for_credential);

      g_free (name_for_credential);
      new_credential = sql_last_insert_id();
      set_credential_data (new_credential, "certificate", key_pub);

      if (disable_encrypted_credentials)
        {
          set_credential_data (new_credential, "private_key", key_priv);
        }
      else
        {
          lsc_crypt_ctx_t crypt_ctx;
          char *secret;

          crypt_ctx = lsc_crypt_new ();

          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "private_key", key_priv, NULL);
          if (!secret)
            {
              fprintf (stderr, "Failed to encrypt private key.\n");
              g_free (ca_pub);
              g_free (key_pub);
              g_free (key_priv);
              manage_option_cleanup ();
              return -1;
            }
          set_credential_data (new_credential, "secret", secret);
        }
      credential_id = credential_uuid (new_credential);
    }
  else
    credential_id = NULL;

  ret = modify_scanner (scanner_id, name, NULL, host, port, type, ca_pub,
                        credential_id);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  switch (ret)
    {
      case 0:
        printf ("Scanner modified.\n");
        break;
      case 2:
        fprintf (stderr, "Scanner with new name exists already.\n");
        break;
      case 3:
        fprintf (stderr, "Scanner ID required.\n");
        break;
      case 4:
        fprintf (stderr, "Invalid value.\n");
        break;
      case 99:
        fprintf (stderr, "Permission denied.\n");
        break;
      default:
        fprintf (stderr, "Failed to modify scanner.\n");
        break;
    }

  manage_option_cleanup ();

  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)
{
  int ret;
  char *version;

  assert (uuid);

  g_info ("   Verifying scanner.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  current_credentials.uuid = "";
  switch ((ret = verify_scanner (uuid, &version)))
    {
      case 0:
        printf ("Scanner version: %s.\n", version);
        g_free (version);
        break;
      case 1:
        fprintf (stderr, "Failed to find scanner.\n");
        break;
      case 2:
        fprintf (stderr, "Failed to verify scanner.\n");
        break;
      default:
        fprintf (stderr, "Internal Error.\n");
        break;
    }
  current_credentials.uuid = NULL;

  manage_option_cleanup ();
  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[out]  new_scanner    The created scanner.
 * @param[in]   ca_pub         CA Certificate for scanner.
 * @param[in]   credential_id  ID of credential for scanner.
 *
 * @return 0 success, 1 scanner exists already, 2 Invalid value,
 *         3 credential not found, 4 credential should be 'up',
 *         5 credential should be 'cc', 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 *credential_id)
{
  char *quoted_name, *quoted_comment, *quoted_host, *quoted_ca_pub;
  int iport, itype;
  credential_t credential;

  assert (current_credentials.uuid);
  assert (name);

  sql_begin_immediate ();

  if (acl_user_may ("create_scanner") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (!host || !port || !type || !credential_id)
    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;
  /* XXX: Workaround for unix socket case. Should add a host type flag, or
   * remove otp over tcp case entirely. */
  if (openvas_get_host_type (host) == -1 && *host != '/')
    return 2;
  if (resource_with_name_exists (name, "scanner", 0))
    {
      sql_rollback ();
      return 1;
    }

  if (find_credential_with_permission (credential_id, &credential,
                                       "get_credentials"))
    {
      sql_rollback ();
      return -1;
    }

  if (credential == 0)
    {
      sql_rollback ();
      return 3;
    }

  if (itype == SCANNER_TYPE_OMP)
    {
      if (sql_int ("SELECT type != 'up' FROM credentials WHERE id = %llu;",
                   credential))
        {
          sql_rollback ();
          return 4;
        }
    }
  else if (sql_int ("SELECT type != 'cc' FROM credentials WHERE id = %llu;",
                    credential))
    {
      sql_rollback ();
      return 5;
    }

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");
  quoted_host = sql_quote (host ?: "");
  quoted_ca_pub = sql_quote (ca_pub ?: "");
  sql ("INSERT INTO scanners (uuid, name, owner, comment, host, port, type,"
       "                      ca_pub, credential, creation_time,"
       "                      modification_time)"
       " VALUES (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '%s', %d, %d, %s%s%s, %llu, m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment, quoted_host,
       iport, itype,
       ca_pub ? "'" : "",
       ca_pub ? quoted_ca_pub : "NULL",
       ca_pub ? "'" : "",
       credential);

  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);
  return 0;
}

/**
 * @brief Create a scanner from an existing scanner.
 *
 * @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, 98 not allowed to copy cve scanner,
 *         99 permission denied.
 */
int
copy_scanner (const char* name, const char* comment, const char *scanner_id,
              scanner_t* new_scanner)
{
  if (strcmp (scanner_id, SCANNER_UUID_CVE) == 0)
    return 98;

  return copy_resource ("scanner", name, comment, scanner_id,
                        "host, port, type, ca_pub, credential", 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_pub      CA Certificate of scanner, or "" for default, or
 *                          to keep existing value.
 * @param[in]   credential_id  UUID of credential or NULL.
 *
 * @return 0 success, 1 failed to find scanner, 2 scanner with new name exists,
 *         3 scanner_id required, 4 invalid value, 5 credential not found,
 *         6 credential should be 'cc', 7 credential should be 'up',
 *         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 *credential_id)
{
  gchar *quoted_name, *quoted_comment, *quoted_host, *new_port, *new_type;
  scanner_t scanner = 0;
  credential_t credential = 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
    itype = 0;

  if (host && (openvas_get_host_type (host) == -1))
    return 4;

  sql_begin_immediate ();

  if (acl_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;
    }

  if (credential_id)
    {
      if (find_credential_with_permission (credential_id, &credential,
                                           "get_credentials"))
        {
          sql_rollback ();
          return -1;
        }

      if (credential == 0)
        {
          sql_rollback ();
          return 5;
        }

      if (itype == 0)
        itype = sql_int ("SELECT type FROM scanners WHERE id = %llu;", scanner);

      if (itype == SCANNER_TYPE_OMP)
        {
          if (sql_int ("SELECT type != 'up' FROM credentials WHERE id = %llu;",
                       credential))
            {
              sql_rollback ();
              return 7;
            }
        }
      else if (sql_int ("SELECT type != 'cc' FROM credentials WHERE id = %llu;",
                   credential))
        {
          sql_rollback ();
          return 6;
        }
    }

  /* 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)
    {
      if (*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);
        }
      else
        /* Use default CA cert. */
        sql ("UPDATE scanners SET ca_pub = NULL WHERE id = %llu;", scanner);
    }

  if (credential_id)
    {
      sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;",
           credential, scanner);
    }
  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,
 *         3 predefined scanner, 99 permission denied, -1 error.
 */
int
delete_scanner (const char *scanner_id, int ultimate)
{
  scanner_t scanner = 0;

  sql_begin_immediate ();

  if (acl_user_may ("delete_scanner") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (strcmp (scanner_id, SCANNER_UUID_CVE) == 0
      || strcmp (scanner_id, SCANNER_UUID_DEFAULT) == 0)
    return 3;

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

      /* Check if it's in use by a task in the trashcan. */
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE scanner = %llu"
                   " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   scanner))
        {
          sql_rollback ();
          return 1;
        }

      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 (ultimate == 0)
    {
      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE scanner = %llu"
                   " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND (hidden = 0 OR hidden = 1);",
                   scanner))
        {
          sql_rollback ();
          return 1;
        }

      sql ("INSERT INTO scanners_trash"
           " (uuid, owner, name, comment, host, port, type, ca_pub,"
           "  credential, credential_location,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, host, port, type, ca_pub,"
           "        credential, " G_STRINGIFY (LOCATION_TABLE) ","
           "        creation_time, modification_time"
           " FROM scanners WHERE id = %llu;", scanner);

      /* Update the location of the scanner in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET scanner = %llu,"
           "     scanner_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE scanner = %llu"
           " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           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, KEYWORD_TYPE_STRING },                            \
   { "port", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "ca_pub", NULL, KEYWORD_TYPE_STRING },                          \
   {                                                                 \
     "(SELECT name FROM credentials WHERE id = credential)",         \
     "credential",                                                   \
     KEYWORD_TYPE_STRING                                             \
   },                                                                \
   { "credential", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                              \
   { "credential_value (credential, 0, CAST ('certificate' AS TEXT))", \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   { "credential_value (credential, 0, CAST ('private_key' AS TEXT))", \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   { "credential_value (credential, 0, CAST ('secret' AS TEXT))",      \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                \
 }

/**
 * @brief Scanner iterator columns for trash case.
 */
#define SCANNER_ITERATOR_TRASH_COLUMNS                               \
 {                                                                   \
   GET_ITERATOR_COLUMNS (scanners_trash),                            \
   { "host" , NULL, KEYWORD_TYPE_STRING },                           \
   { "port" , NULL, KEYWORD_TYPE_INTEGER },                          \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "ca_pub", NULL, KEYWORD_TYPE_STRING },                          \
   {                                                                    \
     "(SELECT CASE"                                                     \
     " WHEN credential_location = " G_STRINGIFY (LOCATION_TABLE)        \
     " THEN (SELECT name FROM credentials WHERE id = credential)"       \
     " ELSE (SELECT name FROM credentials_trash WHERE id = credential)" \
     " END)",                                                           \
     "credential",                                                      \
     KEYWORD_TYPE_STRING                                                \
   },                                                                   \
   { "credential", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "credential_location", NULL, KEYWORD_TYPE_INTEGER },               \
   { "credential_value (credential, 1, CAST ('certificate' AS TEXT))",  \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { "credential_value (credential, 1, CAST ('private_key' AS TEXT))",  \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { "credential_value (credential, 1, CAST ('secret' AS TEXT))",       \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

/**
 * @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 Credential name from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Credential name, or NULL if iteration is complete. Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_credential_name, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the credential of the scanner from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Credential of the scanner or 0 if iteration is complete.
 */
credential_t
scanner_iterator_credential (iterator_t *iterator)
{
  if (iterator->done)
    return 0;
  else
    return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
}

/**
 * @brief Get the credential location of the scanner from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Location of the credential or NULL if iteration is complete.
 */
int
scanner_iterator_credential_trash (iterator_t *iterator)
{
  if (iterator->done)
    return 0;
  else
    return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

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

/**
 * @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.
 */
const char*
scanner_iterator_key_priv (iterator_t* iterator)
{
  const char *private_key;

  private_key = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8);

  if (private_key == NULL)
    {
      const char *secret;
      if (!iterator->crypt_ctx)
        iterator->crypt_ctx = lsc_crypt_new ();

      secret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
      private_key = lsc_crypt_get_private_key (iterator->crypt_ctx, secret);
    }

  return private_key;
}

/**
 * @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 = acl_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, 3);
}

/**
 * @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"
                    " AND hidden = 0;", 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 !!sql_int ("SELECT count(*) FROM tasks"
                    " WHERE scanner = %llu"
                    " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH),
                    scanner);
}

/**
 * @brief Check whether a scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
scanner_writable (scanner_t scanner)
{
  return 1;
}

/**
 * @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 whether a trashcan scanner is readable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 if readable, else 0.
 */
int
trash_scanner_readable (scanner_t scanner)
{
  char *uuid;
  scanner_t found = 0;

  if (scanner == 0)
    return 0;
  uuid = scanner_uuid (scanner);
  if (find_trash ("scanner", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 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 UUID of the default scanner.
 *
 * @return UUID.
 */
const char *
scanner_uuid_default ()
{
  return SCANNER_UUID_DEFAULT;
}

/**
 * @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)
{
  if (scanner == 0)
    return NULL;

  return sql_string ("SELECT value FROM credentials_data"
                     " WHERE credential = (SELECT credential FROM scanners"
                     "                     WHERE id = %llu)"
                     "   AND type = 'certificate';",
                     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)
{
  gchar *key;

  key = sql_string ("SELECT value FROM credentials_data"
                    " WHERE credential = (SELECT credential FROM scanners"
                    "                     WHERE id = %llu)"
                    "   AND type = 'private_key';",
                    scanner);

  if (key == NULL)
    {
      gchar *secret;
      lsc_crypt_ctx_t crypt_ctx;
      crypt_ctx = lsc_crypt_new ();

      secret = sql_string ("SELECT value FROM credentials_data"
                           " WHERE credential"
                           "         = (SELECT credential FROM scanners"
                           "            WHERE id = %llu)"
                           "   AND type = 'secret';",
                           scanner);

      key = g_strdup (lsc_crypt_get_private_key (crypt_ctx, secret));
      lsc_crypt_release (crypt_ctx);
      g_free (secret);
    }

  return key;
}

/**
 * @brief Return the login associated with a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated login if available, else NULL.
 */
char*
scanner_login (scanner_t scanner)
{
  return sql_string ("SELECT credentials_data.value"
                     " FROM scanners, credentials_data"
                     " WHERE scanners.id = %llu"
                     "   AND credentials_data.credential = scanners.credential"
                     "   AND credentials_data.type = 'username';",
                     scanner);
}

/**
 * @brief Return the password associated with a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated password if available, else NULL.
 */
char*
scanner_password (scanner_t scanner)
{
  gchar *password;

  password = sql_string ("SELECT credentials_data.value"
                         " FROM scanners, credentials_data"
                         " WHERE scanners.id = %llu"
                         "   AND credentials_data.credential"
                         "         = scanners.credential"
                         "   AND credentials_data.type = 'password';",
                         scanner);

  if (password == NULL)
    {
      gchar *secret;
      lsc_crypt_ctx_t crypt_ctx;
      crypt_ctx = lsc_crypt_new ();

      secret = sql_string ("SELECT credentials_data.value"
                           " FROM scanners, credentials_data"
                           " WHERE scanners.id = %llu"
                           "   AND credentials_data.credential"
                           "         = scanners.credential"
                           "   AND credentials_data.type = 'secret';",
                           scanner);

      password = g_strdup (lsc_crypt_get_password (crypt_ctx, secret));
      lsc_crypt_release (crypt_ctx);
      g_free (secret);
    }

  return password;
}

/**
 * @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_ver       Daemon version.
 * @param[out]  p_name      Protocol name.
 * @param[out]  p_ver       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 Connect a UNIX socket.
 *
 * @param[in]  path  Path.
 *
 * @return Socket, or -1 on error.
 */
static int
connect_unix (const gchar *path)
{
  struct sockaddr_un address;
  int sock;

  /* Make socket. */

  sock = socket (AF_UNIX, SOCK_STREAM, 0);
  if (sock == -1)
    return -1;

  /* Connect to server. */

  address.sun_family = AF_UNIX;
  strncpy (address.sun_path, path, sizeof (address.sun_path) - 1);
  if (connect (sock, (struct sockaddr *) &address, sizeof (address)) == -1)
    {
      close (sock);
      return -1;
    }

  return sock;
}

/**
 * @brief Connect to an address.
 *
 * @param[out]  connection  Connection.
 * @param[out]  address     Address.
 * @param[out]  port        Port.
 *
 * @return 0 success, -1 failed to connect.
 */
static int
connection_open (openvas_connection_t *connection,
                 const gchar *address,
                 int port)
{
  if (address == NULL)
    return -1;

  connection->tls = *address != '/';

  if (connection->tls)
    {
      connection->socket = openvas_server_open (&connection->session,
                                            address,
                                            port);
      connection->credentials = NULL;
    }
  else
    connection->socket = connect_unix (address);

  if (connection->socket == -1)
    return -1;

  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,
 *         3 no cert, 99 if permission denied, -1 error.
 */
int
verify_scanner (const char *scanner_id, char **version)
{
  get_data_t get;
  iterator_t scanner;

  if (acl_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_OMP)
    {
      openvas_connection_t connection;
      const char *host;
      int port;

      host = scanner_iterator_host (&scanner);
      port = scanner_iterator_port (&scanner);
      if (host == NULL)
        return -1;

      if (connection_open (&connection, host, port))
        return 2;

      if (omp_ping_c (&connection, 0, version))
        {
          openvas_connection_close (&connection);
          return 2;
        }
      g_debug ("%s: *version: %s", __FUNCTION__, *version);
      openvas_connection_close (&connection);
      return 0;
    }
  else 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)
    {
      const char *host = scanner_iterator_host (&scanner);

      if (host && *host == '/')
        openvas_scanner_set_unix (host);
      else
        {
          if (openvas_scanner_set_address (scanner_iterator_host (&scanner),
                                           scanner_iterator_port (&scanner))
              == -1)
            {
              cleanup_iterator (&scanner);
              return 2;
            }

          if (set_certs (scanner_iterator_ca_pub (&scanner),
                         scanner_iterator_key_pub (&scanner),
                         scanner_iterator_key_priv (&scanner)))
            {
              cleanup_iterator (&scanner);
              return 3;
            }
        }
      cleanup_iterator (&scanner);
      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;
    }
  else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_CVE)
    {
      if (version)
        *version = g_strdup ("OTP/2.0");
      return 0;
    }
  assert (0);
  return -1;
}

/**
 * @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;
  int ret;

  g_info ("   Getting scanners.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

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

  manage_option_cleanup ();

  return 0;
}


/* 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 (acl_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,
 *         2 failed to find schedule, 99 permission denied, -1 error.
 */
int
delete_schedule (const char *schedule_id, int ultimate)
{
  schedule_t schedule = 0;

  sql_begin_immediate ();

  if (acl_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"
                    " AND hidden = 0;", 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 1;
}

/**
 * @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 = 0;

  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 duration of a schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Duration in seconds.
 */
int
schedule_duration (schedule_t schedule)
{
  return sql_int ("SELECT duration FROM schedules WHERE id = %llu;",
                  schedule);
}

/**
 * @brief Return info about a schedule.
 *
 * @param[in]  schedule    Schedule.
 * @param[out] first_time  First time schedule ran.
 * @param[out] next_time   Next time schedule will run.
 * @param[out] period      Period.
 * @param[out] period_months  Period months.
 * @param[out] duration       Duration.
 *
 * @return 0 success, -1 error.
 */
int
schedule_info (schedule_t schedule, time_t *first_time, time_t *next_time,
               int *period, int *period_months, int *duration)
{
  iterator_t schedules;

  init_iterator (&schedules,
                 "SELECT"
                 " first_time,"
                 " next_time (first_time, period,"
                 "            period_months),"
                 " period, period_months, duration"
                 " FROM schedules"
                 " WHERE id = %llu",
                 schedule);
  if (next (&schedules))
    {
      *first_time = iterator_int (&schedules, 0);
      *next_time = iterator_int (&schedules, 1);
      *period = iterator_int (&schedules, 2);
      *period_months = iterator_int (&schedules, 3);
      *duration = iterator_int (&schedules, 4);
      cleanup_iterator (&schedules);
      return 0;
    }
  cleanup_iterator (&schedules);
  return -1;
}

/**
 * @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, KEYWORD_TYPE_INTEGER },                           \
   { "period", NULL, KEYWORD_TYPE_INTEGER },                               \
   { "period_months", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "duration", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "timezone", NULL, KEYWORD_TYPE_STRING },                              \
   { "initial_offset", NULL, KEYWORD_TYPE_INTEGER },                       \
   { "next_time (first_time, period, period_months, timezone)",            \
     "next_run",                                                           \
     KEYWORD_TYPE_INTEGER },                                               \
   { "first_time", "first_run", KEYWORD_TYPE_INTEGER },                    \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @brief Schedule iterator columns for trash case.
 */
#define SCHEDULE_ITERATOR_TRASH_COLUMNS                                    \
 {                                                                         \
   GET_ITERATOR_COLUMNS (schedules_trash),                                 \
   { "first_time", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "period", NULL, KEYWORD_TYPE_INTEGER },                               \
   { "period_months", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "duration", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "timezone", NULL, KEYWORD_TYPE_STRING },                              \
   { "initial_offset", NULL, KEYWORD_TYPE_INTEGER },                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @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 = acl_where_owned_user ("", "users.id", "task", &get, 1,
                                      "any", 0, permissions);
  array_free (permissions);

  permissions = make_array ();
  array_add (permissions, g_strdup ("get_schedules"));
  schedule_clause = acl_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.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 schedule from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The schedule.
 */
schedule_t
task_schedule_iterator_schedule (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (schedule_t) iterator_int64 (iterator, 2);
}

/**
 * @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 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, 10);

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

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

  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 first, period, period_months, duration;
  const char *timezone;

  if (iterator->done) return FALSE;

  first = task_schedule_iterator_first_time (iterator);
  period = task_schedule_iterator_period (iterator);
  period_months = task_schedule_iterator_period_months (iterator);
  duration = task_schedule_iterator_duration (iterator);
  timezone = task_schedule_iterator_timezone (iterator);

  if (duration)
    {
      report_t report;
      task_status_t run_status;

      report = task_running_report (task_schedule_iterator_task (iterator));
      if (report && (report_scheduled (report) == 0))
        return FALSE;

      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 = next_time (first, period, period_months, timezone, -1);
          if ((start + duration) < now)
            return TRUE;
        }
    }

  return FALSE;
}

/**
 * @brief Get if schedule of task in iterator is timed out.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Whether task schedule is timed out.
 */
gboolean
task_schedule_iterator_timed_out (iterator_t* iterator)
{
  time_t schedule_timeout_secs;
  int duration;
  task_status_t run_status;
  time_t start_time, timeout_time;

  if (get_schedule_timeout () < 0)
    return FALSE;

  if (iterator->done) return FALSE;

  start_time = task_schedule_iterator_next_time (iterator);

  if (start_time == 0)
    return FALSE;

  schedule_timeout_secs = get_schedule_timeout () * 60;
  if (schedule_timeout_secs < SCHEDULE_TIMEOUT_MIN_SECS)
    schedule_timeout_secs = SCHEDULE_TIMEOUT_MIN_SECS;

  run_status = task_run_status (task_schedule_iterator_task (iterator));
  duration = task_schedule_iterator_duration (iterator);

  if (duration && (duration < schedule_timeout_secs))
    timeout_time = start_time + duration;
  else
    timeout_time = start_time + schedule_timeout_secs;

  if ((run_status == TASK_STATUS_DONE
       || run_status == TASK_STATUS_INTERNAL_ERROR
       || run_status == TASK_STATUS_NEW
       || run_status == TASK_STATUS_STOPPED)
      && (timeout_time <= time (NULL)))
    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 = acl_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;
  time_t real_first_time, real_period, real_period_months, new_next_time;
  const char *real_timezone;
  iterator_t schedule_iter;

  if (schedule_id == NULL)
    return 4;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (acl_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);

  // Update scheduled next times for tasks
  init_iterator (&schedule_iter,
                 "SELECT first_time, period, period_months, timezone"
                 " FROM schedules"
                 " WHERE id = %llu",
                 schedule);

  next (&schedule_iter);
  real_first_time = (time_t) iterator_int64 (&schedule_iter, 0);
  real_period = (time_t) iterator_int64 (&schedule_iter, 1);
  real_period_months = (time_t) iterator_int64 (&schedule_iter, 2);
  real_timezone = iterator_string (&schedule_iter, 3);

  new_next_time = next_time (real_first_time,
                             real_period,
                             real_period_months,
                             real_timezone,
                             0);

  cleanup_iterator (&schedule_iter);

  sql ("UPDATE tasks SET schedule_next_time = %ld"
       " WHERE schedule = %llu;",
       new_next_time,
       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 (acl_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 " ACL_USER_OWNS () ";",
                     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 (acl_user_may ("create_report_format") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (global && acl_user_can_everything (current_credentials.uuid) == 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, *path;
      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_STATE_DIR,
                                  "openvasmd",
                                  "signatures",
                                  "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);
              g_debug ("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_free (real_old);
                  g_free (old);
                  g_warning ("%s: readlink failed", __FUNCTION__);
                  sql_rollback ();
                  return -1;
                }

              real_old[state.st_size] = '\0';
              g_free (old);
              old = real_old;
              g_debug ("using linked old: %s\n", old);
            }
        }
      g_free (base);

      path = g_build_filename (OPENVAS_STATE_DIR, "openvasmd",
                               "signatures", "report_formats", NULL);

      if (g_mkdir_with_parents (path, 0755 /* "rwxr-xr-x" */))
        {
          g_warning ("%s: failed to create dir %s: %s",
                     __FUNCTION__, path, strerror (errno));
          g_free (old);
          g_free (path);
          sql_rollback ();
          return -1;
        }

      base = g_strdup_printf ("%s.asc", new_uuid);
      new = g_build_filename (path, base, NULL);
      g_free (path);
      g_free (base);
      if (symlink (old, new))
        {
          g_free (old);
          g_free (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. */

  assert (global == 0);
  if (global)
    dir = predefined_report_format_dir (new_uuid ? new_uuid : uuid);
  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: %s",
                 __FUNCTION__, dir, strerror (errno));
      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 predefined, 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;
    }

  if (report_format_predefined (old))
    sql ("UPDATE report_formats SET trust = %i, trust_time = %i"
         " WHERE id = %llu;",
         TRUST_YES,
         time (NULL),
         new);

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

  predefined = report_format_predefined (old);
  if (predefined)
    source_dir = predefined_report_format_dir (source_uuid);
  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.
 * @param[in]  predefined        Predefined flag.
 *
 * @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, 5 error in predefined, 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,
                      const char *predefined)
{
  report_format_t report_format;
  int ret = 0;

  if (report_format_id == NULL)
    return 2;

  if (predefined && strcmp (predefined, "0") && strcmp (predefined, "1"))
    return 5;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

  if (acl_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;
    }

  /* It is only possible to modify predefined report formats from the command
   * line. */
  if (current_credentials.uuid == NULL
      && report_format_predefined (report_format))
    {
      sql_rollback ();
      return 99;
    }

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

  if (predefined)
    resource_set_predefined ("report_format", report_format,
                             strcmp (predefined, "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;
  char *owner_uuid;
  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 (acl_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 = report_format_trash_dir (report_format_string);
      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);

      /* Links to the feed signatures. */
      dir = g_build_filename (OPENVAS_STATE_DIR, "openvasmd", "signatures",
                              "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;
    }

  owner_uuid = report_format_owner_uuid (report_format);
  dir = g_build_filename (OPENVAS_STATE_DIR,
                          "openvasmd",
                          "report_formats",
                          owner_uuid,
                          report_format_id,
                          NULL);
  free (owner_uuid);

  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 = report_format_trash_dir (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 = report_format_trash_dir (report_format_string);
      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.
 */
static 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_predefined (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 (acl_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 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 " ACL_IS_GLOBAL () " 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'"
                    "      OR name = 'scp_report_format'"
                    "      OR name = 'send_report_format'"
                    "      OR name = 'smb_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'"
                    "      OR name = 'scp_report_format'"
                    "      OR name = 'send_report_format'"
                    "      OR name = 'smb_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_predefined (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;
}

/**
 * @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 predefined.
 *
 * @param[in]  report_format  Report format.
 *
 * @return 1 if predefined, else 0.
 */
int
report_format_predefined (report_format_t report_format)
{
  return resource_predefined ("report_format", 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;
      case REPORT_FORMAT_PARAM_TYPE_REPORT_FORMAT_LIST:
        {
          if (g_regex_match_simple
                ("^(?:[[:alnum:]-_]+)?(?:,(?:[[:alnum:]-_])+)*$", value, 0, 0)
              == FALSE)
            return 1;
          else
            return 0;
        }
        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, KEYWORD_TYPE_INTEGER },                                \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                               \
   { "name", NULL, KEYWORD_TYPE_STRING },                               \
   { "''", NULL, KEYWORD_TYPE_STRING },                                 \
   { "iso_time (creation_time)", NULL, KEYWORD_TYPE_STRING },           \
   { "iso_time (modification_time)", NULL, KEYWORD_TYPE_STRING },       \
   { "creation_time", "created", KEYWORD_TYPE_INTEGER },                \
   { "modification_time", "modified", KEYWORD_TYPE_INTEGER },           \
   {                                                                    \
     "(SELECT name FROM users WHERE users.id = report_formats.owner)",  \
     "_owner",                                                          \
     KEYWORD_TYPE_STRING                                                \
   },                                                                   \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "extension", NULL, KEYWORD_TYPE_STRING },                          \
   { "content_type", NULL, KEYWORD_TYPE_STRING },                       \
   { "summary", NULL, KEYWORD_TYPE_STRING },                            \
   { "description", NULL, KEYWORD_TYPE_STRING },                        \
   { "signature", NULL, KEYWORD_TYPE_STRING },                          \
   { "trust", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "trust_time", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "flags & 1", "active", KEYWORD_TYPE_INTEGER },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

/**
 * @brief Report Format iterator columns for trash case.
 */
#define REPORT_FORMAT_ITERATOR_TRASH_COLUMNS                            \
 {                                                                      \
   { "id", NULL, KEYWORD_TYPE_INTEGER },                                \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                               \
   { "name", NULL, KEYWORD_TYPE_STRING },                               \
   { "''", NULL, KEYWORD_TYPE_STRING },                                 \
   { "iso_time (creation_time)", NULL, KEYWORD_TYPE_STRING },           \
   { "iso_time (modification_time)", NULL, KEYWORD_TYPE_STRING },       \
   { "creation_time", "created", KEYWORD_TYPE_INTEGER },                \
   { "modification_time", "modified", KEYWORD_TYPE_INTEGER },           \
   {                                                                    \
     "(SELECT name FROM users"                                          \
     " WHERE users.id = report_formats_trash.owner)",                   \
     "_owner",                                                          \
     KEYWORD_TYPE_STRING                                                \
   },                                                                   \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "extension", NULL, KEYWORD_TYPE_STRING },                          \
   { "content_type", NULL, KEYWORD_TYPE_STRING },                       \
   { "summary", NULL, KEYWORD_TYPE_STRING },                            \
   { "description", NULL, KEYWORD_TYPE_STRING },                        \
   { "signature", NULL, KEYWORD_TYPE_STRING },                          \
   { "trust", NULL, KEYWORD_TYPE_INTEGER },                             \
   { "trust_time", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "flags & 1", "active", KEYWORD_TYPE_INTEGER },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

/**
 * @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 = acl_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);

/**
 * @brief Create or update report format for check_report_format.
 *
 * @param[in]  quoted_uuid    UUID of report format, quoted for SQL.
 * @param[in]  name           Name.
 * @param[in]  summary        Summary.
 * @param[in]  description    Description.
 * @param[in]  extension      Extension.
 * @param[in]  content_type   Content type.
 * @param[out] report_format  Created report format.
 *
 * @return 0 success, -1 error.
 */
static int
check_report_format_create (const gchar *quoted_uuid, const gchar *name,
                            const gchar *summary, const gchar *description,
                            const gchar *extension, const gchar *content_type,
                            report_format_t *report_format)
{
  gchar *quoted_name, *quoted_summary, *quoted_description;
  gchar *quoted_extension, *quoted_content_type;

  quoted_name = sql_quote (name);
  quoted_summary = sql_quote (summary);
  quoted_description = sql_quote (description);
  quoted_extension = sql_quote (extension);
  quoted_content_type = sql_quote (content_type);

  if (sql_int ("SELECT count (*) FROM report_formats WHERE uuid = '%s';",
               quoted_uuid))
    {
      sql ("UPDATE report_formats"
           " SET owner = NULL, name = '%s', summary = '%s', description = '%s',"
           "     extension = '%s', content_type = '%s', signature = '',"
           "     trust = %i, trust_time = %i, flags = %llu"
           " WHERE uuid = '%s';",
           g_strstrip (quoted_name),
           g_strstrip (quoted_summary),
           g_strstrip (quoted_description),
           g_strstrip (quoted_extension),
           g_strstrip (quoted_content_type),
           TRUST_YES,
           time (NULL),
           (long long int) REPORT_FORMAT_FLAG_ACTIVE,
           quoted_uuid);

      sql ("UPDATE report_formats SET modification_time = m_now ()"
           " WHERE id"
           " IN (SELECT report_formats.id"
           "     FROM report_formats, report_formats_check"
           "     WHERE report_formats.uuid = '%s'"
           "     AND report_formats.id = report_formats_check.id"
           "     AND (report_formats.owner != report_formats_check.owner"
           "          OR report_formats.name != report_formats_check.name"
           "          OR report_formats.summary != report_formats_check.summary"
           "          OR report_formats.description"
           "             != report_formats_check.description"
           "          OR report_formats.extension"
           "             != report_formats_check.extension"
           "          OR report_formats.content_type"
           "             != report_formats_check.content_type"
           "          OR report_formats.trust != report_formats_check.trust"
           "          OR report_formats.flags != report_formats_check.flags));",
           quoted_uuid);
    }
  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', NULL, '%s', '%s', '%s', '%s', '', %i, %i, %i,"
         "         m_now (), m_now ());",
         quoted_uuid,
         g_strstrip (quoted_name),
         g_strstrip (quoted_summary),
         g_strstrip (quoted_description),
         g_strstrip (quoted_extension),
         g_strstrip (quoted_content_type),
         TRUST_YES,
         time (NULL),
         (long long int) REPORT_FORMAT_FLAG_ACTIVE);

  g_free (quoted_name);
  g_free (quoted_summary);
  g_free (quoted_description);
  g_free (quoted_extension);
  g_free (quoted_content_type);

  switch (sql_int64 (report_format,
                     "SELECT id FROM report_formats WHERE uuid = '%s';",
                     quoted_uuid))
    {
      case 0:
        break;
      default:       /* Programming error. */
        assert (0);
      case 1:        /* Too few rows in result of query. */
      case -1:
        g_warning ("%s: Report format missing: %s\n",
                   __FUNCTION__, quoted_uuid);
        return -1;
    }

  resource_set_predefined ("report_format", *report_format, 1);

  return 0;
}

/**
 * @brief Add params for check_report_format.
 *
 * @param[in]  quoted_uuid      UUID of report format, quoted.
 * @param[in]  config_path      Config path.
 * @param[in]  entity           Parsed XML.
 * @param[out] update_mod_time  Whether to update modification time.
 *
 * @return 0 success, -1 error.
 */
static int
check_report_format_add_params (const gchar *quoted_uuid, const gchar *config_path,
                                entity_t entity, int *update_mod_time)
{
  entities_t entities;
  entity_t param;

  entities = entity->entities;
  while ((param = first_entity (entities)))
    {
      g_debug ("%s: possible param: %s", __FUNCTION__, entity_name (param));

      if (strcmp (entity_name (param), "param") == 0)
        {
          const char *name, *value, *fallback;
          gchar *quoted_name, *quoted_value, *quoted_fallback, *type;
          const char *min, *max;
          array_t *opts;
          entity_t child;

          opts = NULL;
          min = max = NULL;

          child = entity_child (param, "name");
          if (child == NULL)
            {
              g_warning ("%s: Param missing name in '%s'\n",
                         __FUNCTION__, config_path);
              return -1;
            }
          name = entity_text (child);

          child = entity_child (param, "default");
          if (child == NULL)
            {
              g_warning ("%s: Param missing default in '%s'\n",
                         __FUNCTION__, config_path);
              return -1;
            }
          fallback = entity_text (child);

          child = entity_child (param, "type");
          if (child == NULL)
            {
              g_warning ("%s: Param missing type in '%s'\n",
                         __FUNCTION__, config_path);
              return -1;
            }
          type = g_strstrip (g_strdup (entity_text (child)));
          if (report_format_param_type_from_name (type)
              == REPORT_FORMAT_PARAM_TYPE_ERROR)
            {
              g_warning ("%s: Error in param type in '%s'\n",
                         __FUNCTION__, config_path);
              return -1;
            }

          if (strcmp (type, "report_format_list"))
            {
              entity_t bound;

              bound = entity_child (child, "min");
              if (bound && strlen (entity_text (bound)))
                {
                  long long int number;
                  char *end;

                  min = entity_text (bound);
                  number = strtoll (min, &end, 0);
                  if (*end != '\0'
                      || number == LLONG_MAX
                      || number == LLONG_MIN)
                    {
                      g_warning ("%s: Failed to parse min in '%s'\n",
                                 __FUNCTION__, config_path);
                      g_free (type);
                      return -1;
                    }
                }

              bound = entity_child (child, "max");
              if (bound && strlen (entity_text (bound)))
                {
                  long long int number;
                  char *end;

                  max = entity_text (bound);
                  number = strtoll (max, &end, 0);
                  if (*end != '\0'
                      || number == LLONG_MAX
                      || number == LLONG_MIN)
                    {
                      g_warning ("%s: Failed to parse max in '%s'\n",
                                 __FUNCTION__, config_path);
                      g_free (type);
                      return -1;
                    }
                }

              if (strcmp (type, "selection") == 0)
                {
                  entity_t options, option;
                  entities_t children;

                  options = entity_child (child, "options");
                  if (options == NULL)
                    {
                      g_warning ("%s: Selection missing options in '%s'\n",
                                 __FUNCTION__, config_path);
                      g_free (type);
                      return -1;
                    }

                  children = options->entities;
                  opts = make_array ();
                  while ((option = first_entity (children)))
                    {
                      array_add (opts, entity_text (option));
                      children = next_entities (children);
                    }
                }

              child = entity_child (param, "value");
              if (child == NULL)
                {
                  g_warning ("%s: Param missing value in '%s'\n",
                             __FUNCTION__, config_path);
                  g_free (type);
                  return -1;
                }
              value = entity_text (child);

            }
          else
            {
              entity_t report_format;

              child = entity_child (param, "value");
              if (child == NULL)
                {
                  g_warning ("%s: Param missing value in '%s'\n",
                             __FUNCTION__, config_path);
                  g_free (type);
                  return -1;
                }

              report_format = entity_child (child, "report_format");
              if (report_format == NULL)
                {
                  g_warning ("%s: Param missing report format in '%s'\n",
                             __FUNCTION__, config_path);
                  g_free (type);
                  return -1;
                }

              value = entity_attribute (report_format, "id");
              if (value == NULL)
                {
                  g_warning ("%s: Report format missing id in '%s'\n",
                             __FUNCTION__, config_path);
                  g_free (type);
                  return -1;
                }
            }

          /* Add or update the param. */

          quoted_name = g_strstrip (sql_quote (name));
          quoted_value = g_strstrip (sql_quote (value));
          quoted_fallback = g_strstrip (sql_quote (fallback));

          g_debug ("%s: param: %s", __FUNCTION__, name);

          if (sql_int ("SELECT count (*) FROM report_format_params"
                       " WHERE name = '%s'"
                       " AND report_format = (SELECT id FROM report_formats"
                       "                      WHERE uuid = '%s');",
                       quoted_name,
                       quoted_uuid))
            {
              g_debug ("%s: param: %s: updating", __FUNCTION__, name);

              sql ("UPDATE report_format_params"
                   " SET type = %u, value = '%s', type_min = %s,"
                   "     type_max = %s, type_regex = '', fallback = '%s'"
                   " WHERE name = '%s'"
                   " AND report_format = (SELECT id FROM report_formats"
                   "                      WHERE uuid = '%s');",
                   report_format_param_type_from_name (type),
                   quoted_value,
                   min ? min : "NULL",
                   max ? max : "NULL",
                   quoted_fallback,
                   quoted_name,
                   quoted_uuid);

               /* If any value changed, update the modification time. */

               if (sql_int
                    ("SELECT"
                     " EXISTS"
                     "  (SELECT *"
                     "   FROM report_format_params,"
                     "        report_format_params_check"
                     "   WHERE report_format_params.name = '%s'"
                     "   AND report_format_params_check.name = '%s'"
                     "   AND report_format_params.report_format"
                     "       = report_format_params_check.report_format"
                     "   AND (report_format_params.type"
                     "        != report_format_params_check.type"
                     "        OR report_format_params.value"
                     "           != report_format_params_check.value"
                     "        OR report_format_params.type_min"
                     "           != report_format_params_check.type_min"
                     "        OR report_format_params.type_max"
                     "           != report_format_params_check.type_max"
                     "        OR report_format_params.fallback"
                     "           != report_format_params_check.fallback));",
                     quoted_name,
                     quoted_name))
                 *update_mod_time = 1;

              /* Delete existing param options.
               *
               * Predefined report formats can't be modified so the options
               * don't really matter, so don't worry about them for updating
               * the modification time. */

              sql ("DELETE FROM report_format_param_options"
                   " WHERE report_format_param"
                   "       IN (SELECT id FROM report_format_params"
                   "           WHERE name = '%s'"
                   "           AND report_format = (SELECT id"
                   "                                FROM report_formats"
                   "                                WHERE uuid = '%s'));",
                   quoted_name,
                   quoted_uuid);
            }
          else
            {
              g_debug ("%s: param: %s: creating", __FUNCTION__, name);

              sql ("INSERT INTO report_format_params"
                   " (report_format, name, type, value, type_min, type_max,"
                   "  type_regex, fallback)"
                   " VALUES"
                   " ((SELECT id FROM report_formats WHERE uuid = '%s'),"
                   "  '%s', %u, '%s', %s, %s, '', '%s');",
                   quoted_uuid,
                   quoted_name,
                   report_format_param_type_from_name (type),
                   quoted_value,
                   min ? min : "NULL",
                   max ? max : "NULL",
                   quoted_fallback);
              *update_mod_time = 1;
            }

          g_free (type);

          /* Keep this param. */

          sql ("DELETE FROM report_format_params_check"
               " WHERE report_format = (SELECT id FROM report_formats"
               "                        WHERE uuid = '%s')"
               " AND name = '%s';",
               quoted_uuid,
               quoted_name);

          /* Add any options. */

          if (opts)
            {
              int index;

              index = 0;
              while (opts && (index < opts->len))
                {
                  gchar *quoted_option;
                  quoted_option = sql_quote (g_ptr_array_index (opts, index++));
                  sql ("INSERT INTO report_format_param_options"
                       " (report_format_param, value)"
                       " VALUES ((SELECT id FROM report_format_params"
                       "          WHERE name = '%s'"
                       "          AND report_format = (SELECT id"
                       "                               FROM report_formats"
                       "                               WHERE uuid = '%s')),"
                       "         '%s');",
                       quoted_name,
                       quoted_uuid,
                       quoted_option);
                  g_free (quoted_option);
                }

              /* array_free would try free the elements too. */
              g_ptr_array_free (opts, TRUE);
            }

          g_free (quoted_name);
          g_free (quoted_value);
          g_free (quoted_fallback);
        }
      entities = next_entities (entities);
    }

  return 0;
}

/**
 * @brief Setup a predefined report format from disk.
 *
 * @param[in]  entity        XML.
 * @param[in]  config_path   Config path.
 * @param[in]  name          Name.
 * @param[in]  summary       Summary.
 * @param[in]  description   Description.
 * @param[in]  extension     Extension.
 * @param[in]  content_type  Content type.
 *
 * @return 0 success, -1 error.
 */
static int
check_report_format_parse (entity_t entity, const char *config_path,
                           const char **name, const char **summary,
                           const char **description, const char **extension,
                           const char **content_type)
{
  entity_t child;

  child = entity_child (entity, "name");
  if (child == NULL)
    {
      g_warning ("%s: Missing name in '%s'\n", __FUNCTION__, config_path);
      return -1;
    }
  *name = entity_text (child);

  child = entity_child (entity, "summary");
  if (child == NULL)
    {
      g_warning ("%s: Missing summary in '%s'\n", __FUNCTION__, config_path);
      return -1;
    }
  *summary = entity_text (child);

  child = entity_child (entity, "description");
  if (child == NULL)
    {
      g_warning ("%s: Missing description in '%s'\n",
                 __FUNCTION__, config_path);
      return -1;
    }
  *description = entity_text (child);

  child = entity_child (entity, "extension");
  if (child == NULL)
    {
      g_warning ("%s: Missing extension in '%s'\n", __FUNCTION__, config_path);
      return -1;
    }
  *extension = entity_text (child);

  child = entity_child (entity, "content_type");
  if (child == NULL)
    {
      g_warning ("%s: Missing content_type in '%s'\n",
                 __FUNCTION__, config_path);
      return -1;
    }
  *content_type = entity_text (child);

  return 0;
}

/**
 * @brief Setup a predefined report format from disk.
 *
 * @param[in]  uuid  UUID of report format.
 *
 * @return 0 success, -1 error.
 */
static int
check_report_format (const gchar *uuid)
{
  GError *error;
  gchar *path, *config_path, *xml, *quoted_uuid;
  gsize xml_len;
  const char *name, *summary, *description, *extension, *content_type;
  entity_t entity;
  int update_mod_time;
  report_format_t report_format;

  g_debug ("%s: uuid: %s", __FUNCTION__, uuid);

  update_mod_time = 0;
  path = predefined_report_format_dir (uuid);
  g_debug ("%s: path: %s", __FUNCTION__, path);
  config_path = g_build_filename (path, "report_format.xml", NULL);
  g_free (path);

  /* Read the file in. */

  error = NULL;
  g_file_get_contents (config_path, &xml, &xml_len, &error);
  if (error)
    {
      g_warning ("%s: Failed to read '%s': %s\n",
                  __FUNCTION__,
                 config_path,
                 error->message);
      g_error_free (error);
      g_free (config_path);
      return -1;
    }

  /* Parse it as XML. */

  if (parse_entity (xml, &entity))
    {
      g_warning ("%s: Failed to parse '%s'\n", __FUNCTION__, config_path);
      g_free (config_path);
      return -1;
    }

  /* Get the report format properties from the XML. */

  if (check_report_format_parse (entity, config_path, &name, &summary,
                                 &description, &extension, &content_type))
    {
      g_free (config_path);
      free_entity (entity);
      return -1;
    }

  quoted_uuid = sql_quote (uuid);

  /* Create or update the report format. */

  if (check_report_format_create (quoted_uuid, name, summary, description,
                                  extension, content_type, &report_format))
    goto fail;

  /* Add or update the parameters from the parsed XML. */

  if (check_report_format_add_params (quoted_uuid, config_path, entity,
                                      &update_mod_time))
    goto fail;

  free_entity (entity);
  g_free (config_path);

  /* Remove any params that were not defined by the XML. */

  if (sql_int ("SELECT count (*)"
               " FROM report_format_params_check"
               " WHERE report_format = (SELECT id FROM report_formats"
               "                        WHERE uuid = '%s')",
               quoted_uuid))
    {
      sql ("DELETE FROM report_format_param_options"
           " WHERE report_format_param"
           "       IN (SELECT id FROM report_format_params_check"
           "           WHERE report_format = (SELECT id FROM report_formats"
           "                                  WHERE uuid = '%s'));",
           quoted_uuid);
      sql ("DELETE FROM report_format_params"
           " WHERE id IN (SELECT id FROM report_format_params_check"
           "              WHERE report_format = (SELECT id FROM report_formats"
           "                                     WHERE uuid = '%s'));",
           quoted_uuid);
      update_mod_time = 1;
    }

  /* Update modification time if report format changed. */

  if (update_mod_time)
    sql ("UPDATE report_formats SET modification_time = m_now ()"
         " WHERE uuid = '%s';",
         quoted_uuid);

  /* Keep this report format. */

  sql ("DELETE FROM report_formats_check WHERE uuid = '%s';",
       quoted_uuid);

  g_free (quoted_uuid);
  return 0;

 fail:
  g_free (quoted_uuid);
  g_free (config_path);
  free_entity (entity);
  return -1;
}


/* OMP slave scanners. */

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

  sql_begin_immediate ();
  hosts = (*report)->entities;
  while ((host_start = first_entity (hosts)))
    {
      if (strcmp (entity_name (host_start), "host_start") == 0)
        {
          entity_t host;
          char *uuid;

          host = entity_child (host_start, "host");
          if (host == NULL)
            goto rollback_fail;

          uuid = report_uuid (current_report);
          host_notice (entity_text (host), "ip", entity_text (host),
                       "Report Host", uuid, 1, 1);
          free (uuid);

          set_scan_host_start_time (current_report,
                                    entity_text (host),
                                    entity_text (host_start));
        }
      hosts = next_entities (hosts);
    }
  sql_commit ();

  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 (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 = user_uuid_any_method (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[in]   special_full     Whether to give group super on itself (full
 *                               sharing between members).
 * @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,
              int special_full, group_t* group)
{
  int ret;
  gchar *quoted_group_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (group_name);
  assert (group);

  sql_begin_immediate ();

  if (acl_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
    {
      if (special_full)
        {
          char *group_id;

          group_id = group_uuid (*group);
          ret = create_permission_internal ("Super", NULL, "group", group_id,
                                            "group", group_id, NULL);
          g_free (group_id);
          if (ret)
            {
              sql_rollback ();
              return ret;
            }
        }
      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 (acl_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, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Group iterator columns for trash case.
 */
#define GROUP_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                            \
   GET_ITERATOR_COLUMNS (groups_trash),                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @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 (acl_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_arg    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 (strcasecmp (*resource_type, "asset") == 0)
        {
          g_free (*resource_type);
          *resource_type = g_strdup ("host");
          if (find_resource (*resource_type, resource_id_arg, resource))
            {
              g_free (*name);
              g_free (*resource_type);
              return -1;
            }

          if (*resource == 0)
            {
              g_free (*resource_type);
              *resource_type = g_strdup ("os");
              if (find_resource (*resource_type, resource_id_arg, resource))
                {
                  g_free (*name);
                  g_free (*resource_type);
                  return -1;
                }
            }
        }
      else 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 ((acl_user_is_owner (*resource_type, *resource_id) == 0)
          && (acl_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)
      && (acl_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 SQL clause to select the subject users.
 *
 * @param[in]  subject_type  Subject type.
 * @param[in]  subject       The subject.
 *
 * @return Newly allocated string containing the SQL clause.
 */
static gchar*
subject_where_clause (const char* subject_type, resource_t subject)
{
  gchar *subject_where = NULL;
  if (subject && subject_type)
    {
      if (strcmp (subject_type, "user") == 0)
        {
          subject_where
            = g_strdup_printf ("id = %llu", subject);
        }
      else if (strcmp (subject_type, "group") == 0)
        {
          subject_where
            = g_strdup_printf ("id IN (SELECT \"user\" FROM group_users"
                               "        WHERE \"group\" = %llu)",
                               subject);
        }
      else if (strcmp (subject_type, "role") == 0)
        {
          subject_where
            = g_strdup_printf ("id IN (SELECT \"user\" FROM role_users"
                               "        WHERE \"role\" = %llu)",
                               subject);
        }
      else
        {
          subject_where = strdup ("t()");
          g_warning ("%s: unknown subject_type %s",
                     __FUNCTION__, subject_type);
        }
    }
  return subject_where;
}

/**
 * @brief Create a permission.
 *
 * Caller must organise the transaction.
 *
 * @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.
 */
static int
create_permission_internal (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;
  GHashTable *reports = NULL;
  int clear_original = 0;
  gchar *subject_where;

  assert (current_credentials.uuid);

  if (acl_user_may ("create_permission") == 0)
    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)
    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);

  subject_where = subject_where_clause (subject_type, subject);

  if (permission)
    *permission = sql_last_insert_id ();

  /* Update Reports cache */
  if (resource_type && resource_id && strcmp (resource_type, "override") == 0)
    {
      reports = reports_for_override (resource);
    }
  else if (strcasecmp (quoted_name, "super") == 0)
    {
      reports = reports_hashtable ();
      clear_original = 1;
    }

  if (reports && g_hash_table_size (reports))
    {
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      int auto_cache_rebuild;

      reports_ptr = NULL;
      g_hash_table_iter_init (&reports_iter, reports);
      auto_cache_rebuild = setting_auto_cache_rebuild_int ();
      while (g_hash_table_iter_next (&reports_iter,
                                    ((gpointer*)&reports_ptr), NULL))
        {
          if (auto_cache_rebuild)
            report_cache_counts (*reports_ptr, clear_original, 1,
                                 subject_where);
          else
            report_clear_count_cache (*reports_ptr, clear_original, 1,
                                      subject_where);
        }
    }

  if (reports)
    g_hash_table_destroy (reports);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (resource_type);
  g_free (subject_where);

  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;

  sql_begin_immediate ();

  ret = create_permission_internal (name_arg, comment, resource_type_arg,
                                    resource_id_arg, subject_type, subject_id,
                                    permission);
  if (ret)
    sql_rollback ();
  else
    sql_commit ();

  return ret;
}

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

  /* Prevent copying of command level permissions for predefined roles. */
  subject_type = permission_subject_type (permission);
  subject = permission_subject (permission);
  if (permission_resource (permission) == 0
      && 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 Test whether a permission is the special Admin permission.
 *
 * @param[in]  permission_id  UUID of permission.
 *
 * @return 1 permission is Admin, else 0.
 */
int
permission_is_admin (const char *permission_id)
{
  if (permission_id)
    return strcmp (permission_id, PERMISSION_UUID_ADMIN_EVERYTHING);
  return 0;
}

/**
 * @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", "orphan", NULL }

/**
 * @brief Permission iterator columns.
 */
#define PERMISSION_ITERATOR_COLUMNS                                          \
 {                                                                           \
   GET_ITERATOR_COLUMNS (permissions),                                       \
   { "resource_type", "type", KEYWORD_TYPE_STRING },                         \
   { "resource_uuid", NULL, KEYWORD_TYPE_STRING },                           \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN resource_type = '' OR resource_type IS NULL"                     \
     " THEN ''"                                                              \
     " ELSE resource_name (resource_type, resource_uuid, resource_location)" \
     " END)",                                                                \
     "_resource",                                                            \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { "resource_location = " G_STRINGIFY (LOCATION_TRASH),                    \
     NULL,                                                                   \
     KEYWORD_TYPE_INTEGER },                                                 \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN resource = -1"                                                   \
     " THEN 1"                                                               \
     " ELSE 0"                                                               \
     " END)",                                                                \
     "orphan",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   { "subject_type", NULL, KEYWORD_TYPE_STRING },                            \
   {                                                                         \
     "(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",                                                         \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "(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",                                                             \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { "subject_location = " G_STRINGIFY (LOCATION_TRASH),                     \
     NULL,                                                                   \
     KEYWORD_TYPE_INTEGER },                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Permission iterator columns for trash case.
 */
#define PERMISSION_ITERATOR_TRASH_COLUMNS                                    \
 {                                                                           \
   GET_ITERATOR_COLUMNS (permissions_trash),                                 \
   { "resource_type", "type", KEYWORD_TYPE_STRING },                         \
   { "resource_uuid", NULL, KEYWORD_TYPE_STRING },                           \
   {                                                                         \
     "(CASE"                                                                 \
     " WHEN resource_type = '' OR resource_type IS NULL"                     \
     " THEN ''"                                                              \
     " ELSE resource_name (resource_type, resource_uuid, resource_location)" \
     " END)",                                                                \
     "_resource",                                                            \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { "resource_location = " G_STRINGIFY (LOCATION_TRASH),                    \
     NULL,                                                                   \
     KEYWORD_TYPE_INTEGER },                                                 \
   { "resource = -1", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "subject_type", NULL, KEYWORD_TYPE_STRING },                            \
   {                                                                         \
     "(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",                                                         \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "(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",                                                             \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { "subject_location = " G_STRINGIFY (LOCATION_TRASH),                     \
     NULL,                                                                   \
     KEYWORD_TYPE_INTEGER },                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @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 readable status of a resource from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if readable, otherwise 0.
 */
int
permission_iterator_resource_readable (iterator_t* iterator)
{
  resource_t found;
  const char *type, *uuid;
  gchar *permission;

  if (iterator->done) return 0;

  type = permission_iterator_resource_type (iterator);
  uuid = permission_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 if ((strcmp (type, "host") == 0)
           || (strcmp (type, "os") == 0))
    permission = g_strdup ("get_assets");
  else
    permission = g_strdup_printf ("get_%ss", type);

  found = 0;
  find_resource_with_permission (type,
                                 uuid,
                                 &found,
                                 permission,
                                 permission_iterator_resource_in_trash
                                  (iterator));
  g_free (permission);
  return found > 0;
}

/**
 * @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 Get the readable status of a subject from a permission iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if readable, otherwise 0.
 */
int
permission_iterator_subject_readable (iterator_t* iterator)
{
  resource_t found;
  const char *type, *uuid;
  gchar *permission;

  if (iterator->done) return 0;

  type = permission_iterator_subject_type (iterator);
  uuid = permission_iterator_subject_uuid (iterator);

  if (type == NULL || uuid == NULL)
    return 0;

  if ((strcmp (type, "user") == 0)
      || (strcmp (type, "role") == 0)
      || (strcmp (type, "group") == 0))
    permission = g_strdup_printf ("get_%ss", type);
  else
    return 0;

  found = 0;
  find_resource_with_permission (type,
                                 uuid,
                                 &found,
                                 permission,
                                 permission_iterator_subject_in_trash
                                  (iterator));
  g_free (permission);
  return found > 0;
}

/**
 * @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 (acl_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 *name, *subject_type, *resource_type;
  resource_t subject, resource;
  GHashTable *reports = NULL;
  int clear_original = 0;
  gchar *subject_where;

  if (strcasecmp (permission_id, PERMISSION_UUID_ADMIN_EVERYTHING) == 0)
    return 3;

  sql_begin_immediate ();

  if (acl_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;
    }

  /* Prevent deletion of command level permissions for predefined roles. */
  subject_type = permission_subject_type (permission);
  subject = permission_subject (permission);
  resource = permission_resource (permission);
  if (resource == 0
      && subject_type
      && strcmp (subject_type, "role") == 0
      && subject
      && role_is_predefined (subject))
    {
      free (subject_type);
      sql_rollback ();
      return 99;
    }
  subject_where = subject_where_clause (subject_type, subject);
  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);

  name = permission_name (permission);
  resource_type = permission_resource_type (permission);

  sql ("DELETE FROM permissions WHERE id = %llu;", permission);

  /* Update Reports cache */
  if (resource_type && resource && strcmp (resource_type, "override") == 0)
    {
      reports = reports_for_override (resource);
    }
  else if (strcasecmp (name, "super") == 0)
    {
      reports = reports_hashtable ();
      clear_original = 1;
    }
  free (name);
  free (resource_type);

  if (reports && g_hash_table_size (reports))
    {
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      int auto_cache_rebuild;

      reports_ptr = NULL;
      g_hash_table_iter_init (&reports_iter, reports);
      auto_cache_rebuild = setting_auto_cache_rebuild_int ();
      while (g_hash_table_iter_next (&reports_iter,
                                    ((gpointer*)&reports_ptr), NULL))
        {
          if (auto_cache_rebuild)
            report_cache_counts (*reports_ptr, clear_original, 1,
                                 subject_where);
          else
            report_clear_count_cache (*reports_ptr, clear_original, 1,
                                      subject_where);
        }
    }

  g_free (subject_where);

  if (reports)
    g_hash_table_destroy (reports);

  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_arg  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;
  char *old_name, *old_resource_type;
  resource_t old_resource;
  GHashTable *reports = NULL;
  int clear_original = 0;
  gchar *subject_where_old, *subject_where_new, *subject_where;

  if (permission_id == NULL)
    return 4;

  sql_begin_immediate ();

  if (acl_user_may ("modify_permission") == 0)
    {
      sql_rollback ();
      return 99;
    }

  /* Find the permission. */

  permission = 0;
  if (find_permission (permission_id, &permission))
    {
      sql_rollback ();
      return -1;
    }

  if (permission == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* Refuse to modify command-level permissions on predefined roles. */

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

  /* Get old subject clause */
  subject_where_old = subject_where_clause (existing_subject_type, subject);

  /* Set the comment first, to make things easier. */

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

  /* Check the arguments. */

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

  subject_where_new = subject_where_clause (new_subject_type
                                              ? new_subject_type
                                              : subject_type,
                                            subject);

  if (strcmp (subject_where_new, subject_where_old))
    {
      subject_where = g_strdup_printf ("(%s) OR (%s)",
                                       subject_where_new, subject_where_old);
      g_free (subject_where_new);
      g_free (subject_where_old);
    }
  else
    {
      subject_where = subject_where_old;
      g_free (subject_where_new);
    }

  /* Get old values and check if caches are affected by previous resource. */

  old_name = permission_name (permission);
  old_resource_type = permission_resource_type (permission);
  old_resource = resource;

  if (old_resource
      && strcmp (old_resource_type, "override") == 0)
    {
      reports = reports_for_override (resource);
    }
  else if (strcasecmp (old_name, "super"))
    {
      reports = reports_hashtable ();
      clear_original = 1;
    }
  else
    {
      reports = new_resources_hashtable ();
    }

  /* Modify the permission. */

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

  /* Check if caches are affected by the permission and update reports cache */

  if (resource_type
      && resource
      && (resource != old_resource
          || strcmp (old_resource_type, "override"))
      && strcmp (resource_type, "override") == 0)
    {
      reports_add_for_override (reports, resource);
    }
  else if (strcasecmp (quoted_name, "super") == 0
           && strcasecmp (old_name, quoted_name))
    {
      reports_add_all (reports);
      clear_original = 1;
    }

  if (reports)
    {
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      int auto_cache_rebuild;

      g_hash_table_iter_init (&reports_iter, reports);
      reports_ptr = NULL;
      auto_cache_rebuild = setting_auto_cache_rebuild_int ();
      while (g_hash_table_iter_next (&reports_iter,
                                    ((gpointer*)&reports_ptr), NULL))
        {
          if (auto_cache_rebuild)
            report_cache_counts (*reports_ptr, clear_original, 1,
                                 subject_where);
          else
            report_clear_count_cache (*reports_ptr, clear_original, 1,
                                      subject_where);
        }
      g_hash_table_destroy (reports);
      reports = NULL;
    }

  /* Cleanup. */

  g_free (quoted_name);
  free (new_resource_type);
  free (new_resource_id);
  free (existing_subject_type);
  free (new_subject_id);
  free (old_name);
  free (old_resource_type);
  g_free (subject_where);

  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 (acl_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 (acl_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 " ACL_USER_OWNS () ";",
                   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 " ACL_USER_OWNS () ";",
                   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 (acl_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 (acl_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 (acl_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 (acl_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_list = %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_list = %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_list = %llu,"
           "     port_list_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE port_list = %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 Create a port range.
 *
 * @param[in]  port_list   Port list to insert into.
 * @param[in]  type        Protocol: PORT_PROTOCOL_UDP or PORT_PROTOCOL_TCP.
 * @param[in]  start       Start of range.
 * @param[in]  end         End of range.
 */
void
insert_port_range (port_list_t port_list, port_protocol_t type, int start,
                   int end)
{
  sql ("INSERT INTO port_ranges"
       " (uuid, port_list, type, start, \"end\", comment, exclude)"
       " VALUES"
       " (make_uuid (), %llu, %i, '%i', '%i', '', 0);",
       port_list,
       type,
       start,
       end);
}

/**
 * @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 (acl_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",                                                      \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   {                                                               \
     /* 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",                                                        \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   {                                                               \
     /* 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",                                                        \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                            \
 }

/**
 * @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",                                                      \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   {                                                               \
     /* 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",                                                        \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   {                                                               \
     /* 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",                                                        \
     KEYWORD_TYPE_INTEGER                                          \
   },                                                              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                            \
 }

/**
 * @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_list = %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_list = %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)
    {
      char *uuid;

      uuid = port_list_uuid (port_list);
      assert (uuid);
      if (acl_user_has_access_uuid ("port_list", uuid, "get_port_lists", trash))
        init_iterator (iterator,
                       "SELECT uuid, comment, start, \"end\", type, exclude"
                       " FROM port_ranges%s"
                       " WHERE port_list = %llu"
                       " ORDER BY %s %s;",
                       trash ? "_trash" : "",
                       port_list,
                       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"
                       " WHERE 1 = 0");
      free (uuid);
    }
  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 (" ACL_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 = acl_where_owned ("target", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT uuid, name, %s FROM targets"
                 " WHERE port_list = %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 (acl_user_may ("create_role") == 0)
    return 99;

  if (acl_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 (acl_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 (acl_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 %llu, \"user\""
           " FROM role_users WHERE \"role\" = %llu;",
           trash_role,
           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 (acl_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, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Role iterator columns for trash case.
 */
#define ROLE_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                           \
   GET_ITERATOR_COLUMNS (roles_trash),                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

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

      /* Check if it's in use by the condition of an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alert_condition_data_trash"
                   " WHERE name = 'filter_id'"
                   " AND data = (SELECT uuid FROM filters_trash"
                   "             WHERE id = %llu)"
                   " AND (SELECT condition = %i OR condition = %i"
                   "      FROM alerts_trash WHERE id = alert);",
                   filter,
                   ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                   ALERT_CONDITION_FILTER_COUNT_CHANGED))
        {
          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;
    }

  /* Check if it's in use by an alert. */
  if (sql_int ("SELECT count(*) FROM alerts"
               " WHERE filter = %llu;",
               filter))
    {
      sql_rollback ();
      return 1;
    }

  /* Check if it's in use by the condition of an alert. */
  if (sql_int ("SELECT count(*) FROM alert_condition_data"
               " WHERE name = 'filter_id'"
               " AND data = (SELECT uuid FROM filters"
               "             WHERE id = %llu)"
               " AND (SELECT condition = %i OR condition = %i"
               "      FROM alerts WHERE id = alert);",
               filter,
               ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
               ALERT_CONDITION_FILTER_COUNT_CHANGED))
    {
      sql_rollback ();
      return 1;
    }

  if (ultimate)
    {
      /* Check if it's in use by the condition of an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alert_condition_data_trash"
                   " WHERE name = 'filter_id'"
                   " AND data = (SELECT uuid FROM filters"
                   "             WHERE id = %llu)"
                   " AND (SELECT condition = %i OR condition = %i"
                   "      FROM alerts_trash WHERE id = alert);",
                   filter,
                   ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                   ALERT_CONDITION_FILTER_COUNT_CHANGED))
        {
          sql_rollback ();
          return 1;
        }
    }

  quoted_filter_id = sql_quote (filter_id);
  sql ("DELETE FROM settings WHERE name %s '%% Filter' AND value = '%s';",
       sql_ilike_op (),
       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"
                    "   OR (EXISTS (SELECT * FROM alert_condition_data"
                    "             WHERE name = 'filter_id'"
                    "             AND data = (SELECT uuid FROM filters"
                    "                          WHERE id = %llu)"
                    "             AND alert = alerts.id)"
                    "       AND (condition = %i OR condition = %i))",
                    filter,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @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) ")"
                    "   OR (EXISTS (SELECT *"
                    "               FROM alert_condition_data_trash"
                    "               WHERE name = 'filter_id'"
                    "                 AND data = (SELECT uuid"
                    "                             FROM filters_trash"
                    "                             WHERE id = %llu)"
                    "                 AND alert = alerts_trash.id)"
                    "       AND (condition = %i OR condition = %i))",
                    filter,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @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, KEYWORD_TYPE_STRING },                    \
   { "term", NULL, KEYWORD_TYPE_STRING },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                       \
 }

/**
 * @brief Filter iterator columns for trash case.
 */
#define FILTER_ITERATOR_TRASH_COLUMNS                         \
 {                                                            \
   GET_ITERATOR_COLUMNS (filters_trash),                      \
   { "type" , NULL, KEYWORD_TYPE_STRING },                    \
   { "term", NULL, KEYWORD_TYPE_STRING },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                       \
 }

/**
 * @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 = acl_where_owned ("alert", &get, 1, "any", 0, permissions);
  array_free (permissions);

  init_iterator (iterator,
                 "SELECT name, uuid, %s FROM alerts"
                 " WHERE filter = %llu"
                 " OR (EXISTS (SELECT * FROM alert_condition_data"
                 "             WHERE name = 'filter_id'"
                 "             AND data = (SELECT uuid FROM filters"
                 "                         WHERE id = %llu)"
                 "             AND alert = alerts.id)"
                 "     AND (condition = %i OR condition = %i))"
                 " ORDER BY name ASC;",
                 available,
                 filter,
                 filter,
                 ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                 ALERT_CONDITION_FILTER_COUNT_CHANGED);

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

      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 (acl_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 " ACL_USER_OWNS () ";",
                   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 " ACL_USER_OWNS () ";",
                   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, scanner, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, nvt_selector, comment, family_count,"
           "        nvt_count, families_growing, nvts_growing,"
           "        type, scanner, 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, hr_name)"
           " SELECT %llu, type, name, value, default_value, hr_name"
           " 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 " ACL_USER_OWNS () ";",
                   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 " ACL_USER_OWNS () ";",
                   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 " ACL_USER_OWNS () ";",
                   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;
    }

  /* Credential. */

  if (find_trash ("credential", id, &resource))
    {
      sql_rollback ();
      return -1;
    }

  if (resource)
    {
      credential_t credential;

      if (sql_int ("SELECT count(*) FROM credentials"
                   " WHERE name ="
                   " (SELECT name FROM credentials_trash WHERE id = %llu)"
                   " AND " ACL_USER_OWNS () ";",
                   resource,
                   current_credentials.uuid))
        {
          sql_rollback ();
          return 3;
        }

      sql ("INSERT INTO credentials"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, type)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, type"
           " FROM credentials_trash WHERE id = %llu;",
           resource);

      credential = sql_last_insert_id ();

      sql ("INSERT INTO credentials_data"
           " (credential, type, value)"
           " SELECT %llu, type, value"
           " FROM credentials_trash_data"
           " WHERE credential = %llu",
           credential,
           resource);

      /* Update the credentials in any trashcan targets. */
      sql ("UPDATE targets_trash_login_data"
           " SET credential_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);
      sql ("UPDATE scanners_trash"
           " SET credential_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);

      permissions_set_locations ("credential", resource, credential,
                                 LOCATION_TABLE);
      tags_set_locations ("credential", resource, credential,
                          LOCATION_TABLE);

      sql ("DELETE FROM credentials_trash_data WHERE credential = %llu;",
           resource);
      sql ("DELETE FROM 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)
    {
      override_t override;
      GHashTable *reports;
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      gchar *users_where;
      int auto_cache_rebuild;

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

      override = sql_last_insert_id ();

      permissions_set_locations ("override", resource,
                                 override,
                                 LOCATION_TABLE);
      tags_set_locations ("override", resource,
                          override,
                          LOCATION_TABLE);
      users_where = acl_users_with_access_where ("override", id, NULL,
                                                 "id");

      reports = reports_for_override (override);
      g_hash_table_iter_init (&reports_iter, reports);
      reports_ptr = NULL;
      auto_cache_rebuild = setting_auto_cache_rebuild_int ();
      while (g_hash_table_iter_next (&reports_iter,
                                    ((gpointer*)&reports_ptr), NULL))
        {
          if (auto_cache_rebuild)
            report_cache_counts (*reports_ptr, 0, 1, users_where);
          else
            report_clear_count_cache (*reports_ptr, 0, 1, users_where);
        }
      g_hash_table_destroy (reports);
      g_free (users_where);

      sql ("DELETE FROM overrides_trash WHERE id = %llu;", resource);

      sql_commit ();
      return 0;
    }

  /* Permission. */

  if (find_trash ("permission", id, &resource))
    {
      sql_rollback ();
      return -1;
    }

  if (resource)
    {
      permission_t permission;
      char *name, *resource_type;
      resource_t perm_resource;
      GHashTable *reports = NULL;
      int clear_original = 0;
      char *subject_type;
      resource_t subject;
      gchar *subject_where;

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

      permission = sql_last_insert_id ();
      name = permission_name (permission);
      resource_type = permission_resource_type (permission);
      perm_resource = permission_resource (permission);

      subject_type = permission_subject_type (permission);
      subject = permission_subject (permission);
      subject_where = subject_where_clause (subject_type, subject);
      free (subject_type);

      tags_set_locations ("permission", resource,
                          sql_last_insert_id (),
                          LOCATION_TABLE);

      /* Update Reports cache */
      if (resource_type && perm_resource
          && strcmp (resource_type, "override") == 0)
        {
          reports = reports_for_override (perm_resource);
        }
      else if (strcasecmp (name, "super") == 0)
        {
          reports = reports_hashtable ();
          clear_original = 1;
        }

      if (reports && g_hash_table_size (reports))
        {
          GHashTableIter reports_iter;
          report_t *reports_ptr;
          int auto_cache_rebuild;

          reports_ptr = NULL;
          g_hash_table_iter_init (&reports_iter, reports);
          auto_cache_rebuild = setting_auto_cache_rebuild_int ();
          while (g_hash_table_iter_next (&reports_iter,
                                        ((gpointer*)&reports_ptr), NULL))
            {
              if (auto_cache_rebuild)
                report_cache_counts (*reports_ptr, clear_original, 1,
                                     subject_where);
              else
                report_clear_count_cache (*reports_ptr, clear_original, 1,
                                          subject_where);
            }
        }
      g_free (subject_where);

      if (reports)
        g_hash_table_destroy (reports);

      free (name);
      free (resource_type);

      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 " ACL_USER_OWNS () ";",
                   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_list = %llu,"
           "     port_list_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE port_list = %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;
      char *trash_uuid, *owner_uuid;

      if (sql_int ("SELECT count(*) FROM report_formats"
                   " WHERE name ="
                   " (SELECT name FROM report_formats_trash WHERE id = %llu)"
                   " AND " ACL_USER_OWNS () ";",
                   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);

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

      owner_uuid = report_format_owner_uuid (report_format);
      dir = g_build_filename (OPENVAS_STATE_DIR,
                              "openvasmd",
                              "report_formats",
                              owner_uuid,
                              trash_uuid,
                              NULL);
      free (trash_uuid);
      free (owner_uuid);

      resource_string = g_strdup_printf ("%llu", resource);
      trash_dir = report_format_trash_dir (resource_string);
      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 " ACL_USER_OWNS () ";",
                   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 %llu, \"user\""
           " FROM role_users_trash WHERE role = %llu;",
           role,
           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 " ACL_USER_OWNS () ";",
                   resource, current_credentials.uuid))
        {
          sql_rollback ();
          return 3;
        }

      if (sql_int ("SELECT credential_location = " G_STRINGIFY (LOCATION_TRASH)
                   " FROM scanners_trash WHERE id = %llu;",
                   resource))
        {
          sql_rollback ();
          return 1;
        }

      sql ("INSERT INTO scanners"
           " (uuid, owner, name, comment, host, port, type, ca_pub,"
           "  credential, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, host, port, type, ca_pub,"
           "        credential, creation_time, modification_time"
           " FROM scanners_trash WHERE id = %llu;", resource);

      /* Update the scanner in any trashcan tasks. */
      sql ("UPDATE tasks"
           " SET scanner = %llu,"
           "     scanner_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE scanner = %llu"
           " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH),
           sql_last_insert_id (), 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 " ACL_USER_OWNS () ";",
                   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;
    }

  /* 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)
    {
      target_t restored_target;

      if (sql_int ("SELECT count(*) FROM targets"
                   " WHERE name ="
                   " (SELECT name FROM targets_trash WHERE id = %llu)"
                   " AND " ACL_USER_OWNS () ";",
                   resource,
                   current_credentials.uuid))
        {
          sql_rollback ();
          return 3;
        }

      /* Check if it uses a credential or port list in the trashcan. */
      if (sql_int ("SELECT port_list_location = " G_STRINGIFY (LOCATION_TRASH)
                   " OR EXISTS ("
                   "  SELECT *"
                   "  FROM targets_trash_login_data WHERE target = %llu"
                   "  AND credential_location = " G_STRINGIFY (LOCATION_TRASH)
                   " )"
                   " FROM targets_trash WHERE id = %llu;",
                   resource, resource))
        {
          sql_rollback ();
          return 1;
        }

      sql ("INSERT INTO targets"
           " (uuid, owner, name, hosts, exclude_hosts, comment,"
           "  port_list, reverse_lookup_only, reverse_lookup_unify,"
           "  alive_test, creation_time, modification_time)"
           " SELECT uuid, owner, name, hosts, exclude_hosts, comment,"
           "        port_list, reverse_lookup_only, reverse_lookup_unify,"
           "        alive_test, creation_time, modification_time"
           " FROM targets_trash WHERE id = %llu;",
           resource);

      restored_target = sql_last_insert_id ();

      /* Copy login data */
      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           "  SELECT %llu, type, credential, port"
           "   FROM targets_trash_login_data WHERE target = %llu;",
           restored_target, resource);

      sql ("DELETE FROM targets_trash_login_data"
           " WHERE target = %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),
           restored_target,
           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 (scanner_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;
}

#define WHERE_OWNER                                          \
 " WHERE owner = (SELECT id FROM users WHERE uuid = '%s')",  \
 current_credentials.uuid

/**
 * @brief Empty the trashcan.
 *
 * @return 0 success, 99 permission denied, -1 error.
 */
int
manage_empty_trashcan ()
{
  iterator_t rows;
  GArray *report_formats;
  int index, length;

  sql_begin_immediate ();

  if (acl_user_may ("empty_trashcan") == 0)
    {
      sql_rollback ();
      return 99;
    }

  sql ("DELETE FROM agents_trash" WHERE_OWNER);
  sql ("DELETE FROM nvt_selectors WHERE name IN"
       " (SELECT nvt_selector FROM configs_trash"
       "  WHERE owner = (SELECT id FROM users"
       "                 WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM config_preferences_trash"
       " WHERE config IN (SELECT id FROM configs_trash"
       "                  WHERE owner = (SELECT id FROM users"
       "                                 WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM configs_trash" WHERE_OWNER);
  sql ("DELETE FROM permissions"
       " WHERE subject_type = 'group'"
       " AND subject IN (SELECT id from groups_trash"
       "                 WHERE owner = (SELECT id FROM users"
       "                                WHERE uuid = '%s'))"
       " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
       current_credentials.uuid);
  sql ("DELETE FROM group_users_trash"
       " WHERE \"group\" IN (SELECT id from groups_trash"
       "                     WHERE owner = (SELECT id FROM users"
       "                                    WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM groups_trash" WHERE_OWNER);
  sql ("DELETE FROM alert_condition_data_trash"
       " WHERE alert IN (SELECT id from alerts_trash"
       "                 WHERE owner = (SELECT id FROM users"
       "                                WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM alert_event_data_trash"
       " WHERE alert IN (SELECT id from alerts_trash"
       "                 WHERE owner = (SELECT id FROM users"
       "                                WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM alert_method_data_trash"
       " WHERE alert IN (SELECT id from alerts_trash"
       "                 WHERE owner = (SELECT id FROM users"
       "                                WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM alerts_trash" WHERE_OWNER);
  sql ("DELETE FROM credentials_trash_data"
       " WHERE credential IN (SELECT id from credentials_trash"
       "                      WHERE owner = (SELECT id FROM users"
       "                                     WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM credentials_trash" WHERE_OWNER);
  sql ("DELETE FROM filters_trash" WHERE_OWNER);
  sql ("DELETE FROM notes_trash" WHERE_OWNER);
  sql ("DELETE FROM overrides_trash" WHERE_OWNER);
  sql ("DELETE FROM permissions_trash" WHERE_OWNER);
  sql ("DELETE FROM port_ranges_trash"
       " WHERE port_list IN (SELECT id from port_lists_trash"
       "                     WHERE owner = (SELECT id FROM users"
       "                                    WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM port_lists_trash" WHERE_OWNER);
  sql ("DELETE FROM permissions"
       " WHERE subject_type = 'role'"
       " AND subject IN (SELECT id from roles_trash"
       "                 WHERE owner = (SELECT id FROM users"
       "                                WHERE uuid = '%s'))"
       " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
       current_credentials.uuid);
  sql ("DELETE FROM role_users_trash"
       " WHERE role IN (SELECT id from roles_trash"
       "                WHERE owner = (SELECT id FROM users"
       "                               WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM roles_trash" WHERE_OWNER);
  sql ("DELETE FROM scanners_trash" WHERE_OWNER);
  sql ("DELETE FROM schedules_trash" WHERE_OWNER);
  sql ("DELETE FROM tags_trash" WHERE_OWNER);
  sql ("DELETE FROM targets_trash_login_data"
       " WHERE target IN (SELECT id from targets_trash"
       "                  WHERE owner = (SELECT id FROM users"
       "                                 WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM targets_trash" WHERE_OWNER);
  if (delete_trash_tasks ())
    {
      sql_rollback ();
      return -1;
    }

  sql ("UPDATE permissions"
       " SET resource = -1"
       " WHERE resource > 0"
       " AND resource_location = " G_STRINGIFY (LOCATION_TRASH)
       " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
       current_credentials.uuid);
  sql ("UPDATE tags"
       " SET resource = 0, resource_location = " G_STRINGIFY (LOCATION_TABLE)
       " WHERE resource_location = " G_STRINGIFY (LOCATION_TRASH)
       " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
       current_credentials.uuid);

  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 = (SELECT id FROM users"
       "                                    WHERE uuid = '%s')));",
       current_credentials.uuid);
  sql ("DELETE FROM report_format_params_trash"
       " WHERE report_format IN (SELECT id from report_formats_trash"
       "                         WHERE owner = (SELECT id FROM users"
       "                                        WHERE uuid = '%s'));",
       current_credentials.uuid);

  init_iterator (&rows, "SELECT id FROM report_formats_trash" WHERE_OWNER);
  report_formats = g_array_new (FALSE, FALSE, sizeof (report_format_t));
  length = 0;
  while (next (&rows))
    {
      report_format_t id;
      id = iterator_int64 (&rows, 0);
      g_array_append_val (report_formats, id);
      length++;
    }
  cleanup_iterator (&rows);

  sql ("DELETE FROM report_formats_trash" WHERE_OWNER);

  /* Remove the report formats dirs last, in case any SQL rolls back. */

  for (index = 0; index < length; index++)
    {
      gchar *dir, *name;

      name = g_strdup_printf ("%llu",
                              g_array_index (report_formats,
                                             report_format_t,
                                             index));
      dir = report_format_trash_dir (name);
      g_free (name);

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

      g_free (dir);
    }

  g_array_free (report_formats, TRUE);

  sql_commit ();
  return 0;
}


/* Assets. */

/**
 * \page asset_rules Ruleset for updating assets from scan detections
 *
 * During a scan various assets are identfied. The findings are by default
 * used to update the asset database. Since assets may already be present in
 * the database or even be present with contradictive properties, a ruleset
 * defines how the asset database is updated upon findings.
 *
 * Hosts
 * -----
 *
 * When a host is detected, and there is at least one asset host that has the
 * same name and owner as the detected host, and whose identifiers all have
 * the same values as the identifiers of the detected host, then the most
 * recent such asset host is used. Otherwise a new asset host is created.
 * Either way the identifiers are added to the asset host. It does not matter
 * if the asset host has fewer identifiers than detected, as long as the
 * existing identifiers match.
 *
 * At the beginning of a scan, when a host is first detected, the decision
 * about which asset host to use is made by \ref host_notice.  At the end
 * of the scan, if the host has identifiers, then this decision is revised
 * by \ref hosts_set_identifiers to take the identifiers into account.
 *
 * Host identifiers can be ip, hostname, MAC, DNS-via-TargetDefinition, OS or
 * ssh-key.
 *
 * This documentation includes some pseudo-code and tabular definition.
 * Eventually one of them will repalce the other.
 *
 * Name    : The assigned name (usually the IP)
 * IP      : The detected IP
 * Hostname: The detected Hostname
 * OS:     : The detected OS
 *
 * If IP And Not Hostname:
 *   If Not Assets.Host(id=Name) And Not Assets.Host(attrib=IP, IP):
 *     Assets.Host.New(id=Name, ip=IP)
 *   If Assets.Host(id=Name) == 1:
 *     Assets.Host.Add(id=Name, ip=IP)
 *
 * This pseudo-code is equivalent to the first two rows of:
 *
 * Detection                    | Asset State                                                                 |     Asset Update
 * ---------------------------- | --------------------------------------------------------------------------- | -----------------------------
 * IP address X.                | No host with Name=X or any ip=X.                                            | Create host with Name=X and ip=X.
 * IP address X.                | Host A with Name=X.                                                         | Add ip=X to host A.
 * IP address X.                | (Host A with Name=X and ip=X) and (Host B with Name=X and ip=X).            | Add ip=X to host (Newest(A,B)).
 * IP addess X with Hostname Y. | Host A with Name=X and ip=X.                                                | Add ip=X and hostname=Y to host A.
 * IP addess X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y.                                 | Add ip=X and hostname=Y to host A.
 * IP addess X with Hostname Y. | Host A with Name=X and ip=X and hostname<>Y.                                | Create host with Name=X, ip=X and hostname=Y.
 * IP addess X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y and host B with Name=X and ip=X. | Add ip=X and hostname=Y to host (Newst(A,B)).
 *
 * Follow up action: If a MAC, DNS-via-TargetDefinition, OS or ssh-key was
 * detected, then the respective identifiers are added to the asset host
 * selected during asset update.
 *
 * Operating Systems
 * -----------------
 *
 * If OS:
 *   If Not Assets.OS(id=OS):
 *     Assets.OS.New(id=OS)
 *
 * This pseudo-code is equivalent to:
 *
 * Detection | Asset State        | Asset Update
 * --------- | ------------------ | ------------------------
 * OS X.     | No OS with Name=X. | Create OS with Name=X.
 * OS X.     | OS with Name=X.    | No action.
 */

/**
 * @brief Return the UUID of the asset associated with a result host.
 *
 * @param[in]  host    Host value from result.
 * @param[in]  result  Result.
 *
 * @return Asset UUID.
 */
char *
result_host_asset_id (const char *host, result_t result)
{
  gchar *quoted_host;
  char *asset_id;

  quoted_host = sql_quote (host);
  asset_id = sql_string ("SELECT uuid FROM hosts"
                         " WHERE id = (SELECT host FROM host_identifiers"
                         "             WHERE source_type = 'Report Host'"
                         "             AND name = 'ip'"
                         "             AND source_id"
                         "                 = (SELECT uuid"
                         "                    FROM reports"
                         "                    WHERE id = (SELECT report"
                         "                                FROM results"
                         "                                WHERE id = %llu))"
                         "             AND value = '%s'"
                         "             LIMIT 1);",
                         result,
                         quoted_host);
  g_free (quoted_host);
  return asset_id;
}

/**
 * @brief Return the UUID of a host.
 *
 * @param[in]  host  Host.
 *
 * @return Host UUID.
 */
char*
host_uuid (resource_t host)
{
  return sql_string ("SELECT uuid FROM hosts WHERE id = %llu;",
                     host);
}

/**
 * @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 Report host.
 */
report_host_t
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);
  return sql_last_insert_id ();
}

/**
 * @brief Tests if a report host is marked as dead.
 *
 * @param[in]  report_host  Report host.
 *
 * @return 1 if the host is marked as dead, 0 otherwise.
 */
int
report_host_dead (report_host_t report_host)
{
  return sql_int ("SELECT count(*) != 0 FROM report_host_details"
                  " WHERE report_host = %llu"
                  "   AND name = 'Host dead'"
                  "   AND value != '0';",
                  report_host);
}

/**
 * @brief Counts.
 *
 * @param[in]  report_host  Report host.
 *
 * @return 1 if the host is marked as dead, 0 otherwise.
 */
int
report_host_result_count (report_host_t report_host)
{
  return sql_int ("SELECT count(*) FROM report_hosts, results"
                  " WHERE report_hosts.id = %llu"
                  "   AND results.report = report_hosts.report"
                  "   AND report_hosts.host = results.host;",
                  report_host);
}

/**
 * @brief Set end time of a report host.
 *
 * @param[in]  report_host  Report host.
 * @param[in]  end_time     End time.
 */
void
report_host_set_end_time (report_host_t report_host, time_t end_time)
{
  sql ("UPDATE report_hosts SET end_time = %lld WHERE id = %llu;",
       end_time, report_host);
}

/**
 * @brief Host identifiers for the current scan.
 */
array_t *identifiers = NULL;

/**
 * @brief Unique hosts listed in host_identifiers.
 */
array_t *identifier_hosts = NULL;

/**
 * @brief Host identifier type.
 */
typedef struct
{
  gchar *ip;                ///< IP of host.
  gchar *name;              ///< Name of identifier, like "hostname".
  gchar *value;             ///< Value of identifier.
  gchar *source_type;       ///< Type of identifier source, like "Report Host".
  gchar *source_id;         ///< ID of source.
  gchar *source_data;       ///< Extra data for source.
} identifier_t;

/**
 * @brief Free an identifier.
 */
void
identifier_free (identifier_t *identifier)
{
  if (identifier)
    {
      g_free (identifier->ip);
      g_free (identifier->name);
      g_free (identifier->value);
      g_free (identifier->source_type);
      g_free (identifier->source_id);
      g_free (identifier->source_data);
    }
}

/**
 * @brief Setup hosts and their identifiers after a scan, from host details.
 *
 * At the end of a scan this revises the decision about which asset host to use
 * for each host that has identifiers.  The rules for this decision are described
 * in \ref asset_rules.  (The initial decision is made by \ref host_notice.)
 */
void
hosts_set_identifiers ()
{
  if (identifier_hosts)
    {
      int host_index, index;
      gchar *ip;

      array_terminate (identifiers);
      array_terminate (identifier_hosts);

      host_index = 0;
      while ((ip = (gchar*) g_ptr_array_index (identifier_hosts, host_index)))
        {
          host_t host, host_new;
          gchar *quoted_host_name;
          identifier_t *identifier;
          GString *select;

          quoted_host_name = sql_quote (ip);

          select = g_string_new ("");

          /* Select the most recent host whose identifiers all match the given
           * identifiers, even if the host has fewer identifiers than given. */

          g_string_append_printf (select,
                                  "SELECT id FROM hosts"
                                  " WHERE name = '%s'"
                                  " AND owner = (SELECT id FROM users"
                                  "              WHERE uuid = '%s')",
                                  quoted_host_name,
                                  current_credentials.uuid);

          index = 0;
          while ((identifier = (identifier_t*) g_ptr_array_index (identifiers, index)))
            {
              gchar *quoted_identifier_name, *quoted_identifier_value;

              if (strcmp (identifier->ip, ip))
                {
                  index++;
                  continue;
                }

              quoted_identifier_name = sql_quote (identifier->name);
              quoted_identifier_value = sql_quote (identifier->value);

              g_string_append_printf (select,
                                      " AND (EXISTS (SELECT * FROM host_identifiers"
                                      "              WHERE host = hosts.id"
                                      "              AND owner = (SELECT id FROM users"
                                      "                           WHERE uuid = '%s')"
                                      "              AND name = '%s'"
                                      "              AND value = '%s')"
                                      "      OR NOT EXISTS (SELECT * FROM host_identifiers"
                                      "                     WHERE host = hosts.id"
                                      "                     AND owner = (SELECT id FROM users"
                                      "                                  WHERE uuid = '%s')"
                                      "                     AND name = '%s'))",
                                      current_credentials.uuid,
                                      quoted_identifier_name,
                                      quoted_identifier_value,
                                      current_credentials.uuid,
                                      quoted_identifier_name);

              g_free (quoted_identifier_name);
              g_free (quoted_identifier_value);

              index++;
            }

          g_string_append_printf (select,
                                  " ORDER BY creation_time DESC LIMIT 1;");

          switch (sql_int64 (&host, select->str))
            {
              case 0:
                break;
              case 1:        /* Too few rows in result of query. */
                host = 0;
                break;
              default:       /* Programming error. */
                assert (0);
              case -1:
                host = 0;
                break;
            }

          g_string_free (select, TRUE);

          if (host == 0)
            {
              /* Add the host. */

              sql ("INSERT into hosts"
                   " (uuid, owner, name, comment, creation_time, modification_time)"
                   " VALUES"
                   " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', '',"
                   "  m_now (), m_now ());",
                   current_credentials.uuid,
                   quoted_host_name);

              host_new = host = sql_last_insert_id ();
            }
          else
            {
              /* Use the existing host. */

              host_new = 0;
            }

          g_free (quoted_host_name);

          /* Add the host identifiers. */

          index = 0;
          while ((identifier = (identifier_t*) g_ptr_array_index (identifiers,
                                                                  index)))
            {
              gchar *quoted_identifier_name, *quoted_identifier_value;
              gchar *quoted_source_id, *quoted_source_type, *quoted_source_data;

              if (strcmp (identifier->ip, ip))
                {
                  index++;
                  continue;
                }

              quoted_identifier_name = sql_quote (identifier->name);
              quoted_identifier_value = sql_quote (identifier->value);
              quoted_source_id = sql_quote (identifier->source_id);
              quoted_source_data = sql_quote (identifier->source_data);
              quoted_source_type = sql_quote (identifier->source_type);

              if (strcmp (identifier->name, "OS") == 0)
                {
                  resource_t os;

                  switch (sql_int64 (&os,
                                     "SELECT id FROM oss"
                                     " WHERE name = '%s'"
                                     " AND owner = (SELECT id FROM users"
                                     "              WHERE uuid = '%s');",
                                     quoted_identifier_value,
                                     current_credentials.uuid))
                    {
                      case 0:
                        break;
                      default:       /* Programming error. */
                        assert (0);
                      case -1:
                      case 1:        /* Too few rows in result of query. */
                        sql ("INSERT into oss"
                             " (uuid, owner, name, comment, creation_time,"
                             "  modification_time)"
                             " VALUES"
                             " (make_uuid (),"
                             "  (SELECT id FROM users WHERE uuid = '%s'),"
                             "  '%s', '', m_now (), m_now ());",
                             current_credentials.uuid,
                             quoted_identifier_value);
                        os = sql_last_insert_id ();
                        break;
                    }

                  sql ("INSERT into host_oss"
                       " (uuid, host, owner, name, comment, os, source_type,"
                       "  source_id, source_data, creation_time, modification_time)"
                       " VALUES"
                       " (make_uuid (), %llu,"
                       "  (SELECT id FROM users WHERE uuid = '%s'),"
                       "  '%s', '', %llu, '%s', '%s', '%s', m_now (), m_now ());",
                       host,
                       current_credentials.uuid,
                       quoted_identifier_name,
                       os,
                       quoted_source_type,
                       quoted_source_id,
                       quoted_source_data);

                  if (host_new == 0)
                    {
                      sql ("UPDATE hosts"
                           " SET modification_time = (SELECT modification_time"
                           "                          FROM host_oss"
                           "                          WHERE id = %llu)"
                           " WHERE id = %llu;",
                           sql_last_insert_id (),
                           host);

                      sql ("UPDATE oss"
                           " SET modification_time = (SELECT modification_time"
                           "                          FROM host_oss"
                           "                          WHERE id = %llu)"
                           " WHERE id = %llu;",
                           sql_last_insert_id (),
                           os);
                    }
                }
              else
                {
                  sql ("INSERT into host_identifiers"
                       " (uuid, host, owner, name, comment, value, source_type,"
                       "  source_id, source_data, creation_time, modification_time)"
                       " VALUES"
                       " (make_uuid (), %llu,"
                       "  (SELECT id FROM users WHERE uuid = '%s'),"
                       "  '%s', '', '%s', '%s', '%s', '%s', m_now (), m_now ());",
                       host,
                       current_credentials.uuid,
                       quoted_identifier_name,
                       quoted_identifier_value,
                       quoted_source_type,
                       quoted_source_id,
                       quoted_source_data);

                  if (host_new == 0)
                    sql ("UPDATE hosts"
                         " SET modification_time = (SELECT modification_time"
                         "                          FROM host_identifiers"
                         "                          WHERE id = %llu)"
                         " WHERE id = %llu;",
                         sql_last_insert_id (),
                         host);
                }

              if (host_new)
                /* Make sure all existing identifiers from this report refer to
                 * this host.  Currently these will only be the Report Host
                 * identifiers added for OTP HOST_START in otp.c. */
                sql ("UPDATE host_identifiers SET host = %llu"
                     " WHERE source_id = '%s';",
                     host_new,
                     quoted_source_id);

              g_free (quoted_source_type);
              g_free (quoted_source_id);
              g_free (quoted_source_data);
              g_free (quoted_identifier_name);
              g_free (quoted_identifier_value);

              index++;
            }
          host_index++;
        }

      index = 0;
      while (identifiers && (index < identifiers->len))
        identifier_free (g_ptr_array_index (identifiers, index++));
      array_free (identifiers);
      identifiers = NULL;

      array_free (identifier_hosts);
      identifier_hosts = NULL;
    }
}

/**
 * @brief Set the maximum severity of each host in a scan.
 *
 * @param[in]  report         The report associated with the scan.
 * @param[in]  overrides_arg  Whether override should be applied.
 * @param[in]  min_qod_arg    Min QOD to use.
 */
void
hosts_set_max_severity (report_t report, int *overrides_arg, int *min_qod_arg)
{
  gchar *new_severity_sql;
  int overrides, min_qod;

  if (overrides_arg)
    overrides = *overrides_arg;
  else
    {
      task_t task;
      /* Get "Assets Apply Overrides" task preference. */
      overrides = 1;
      if (report_task (report, &task) == 0)
        {
          char *value;
          value = task_preference_value (task, "assets_apply_overrides");
          if (value && (strcmp (value, "yes") == 0))
            overrides = 1;
          else
            overrides = 0;
          free (value);
        }
    }

  if (min_qod_arg)
    min_qod = *min_qod_arg;
  else
    {
      task_t task;
      /* Get "Assets Min QOD". */
      min_qod = MIN_QOD_DEFAULT;
      if (report_task (report, &task) == 0)
        {
          char *value;
          value = task_preference_value (task, "assets_min_qod");
          if (value)
            min_qod = atoi (value);
          free (value);
        }
    }

  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,
                       overrides,
                       setting_dynamic_severity_int ());

  sql ("INSERT INTO host_max_severities"
       " (host, severity, source_type, source_id, creation_time)"
       " SELECT asset_host,"
       "        coalesce ((SELECT max (%s) FROM results"
       "                   WHERE report = %llu"
       "                   AND qod >= %i"
       "                   AND host = (SELECT name FROM hosts"
       "                               WHERE id = asset_host)),"
       "                  0.0),"
       "        'Report',"
       "        (SELECT uuid FROM reports WHERE id = %llu),"
       "        m_now ()"
       " FROM (SELECT host AS asset_host"
       "       FROM host_identifiers"
       "       WHERE source_id = (SELECT uuid FROM reports WHERE id = %llu))"
       "      AS subquery;",
       new_severity_sql,
       report,
       min_qod,
       report,
       report);

  g_free (new_severity_sql);
}

/**
 * @brief Store certain host details in the assets after a scan.
 *
 * @param[in]  report  The report associated with the scan.
 */
void
hosts_set_details (report_t report)
{
  sql ("INSERT INTO host_details"
       " (detail_source_type, detail_source_name, detail_source_description,"
       "  name, value, source_type, source_id, host)"
       " SELECT source_type,"
       "        source_name,"
       "        source_description,"
       "        name,"
       "        value,"
       "        'Report',"
       "        (SELECT uuid FROM reports WHERE id = %llu),"
       /*       Assume that every report host detail has a corresponding host
        *       in the assets. */
       "        (SELECT host"
       "         FROM host_identifiers"
       "         WHERE source_id = (SELECT uuid FROM reports"
       "                            WHERE id = %llu)"
       "         AND (SELECT name FROM hosts WHERE id = host)"
       "             = (SELECT host FROM report_hosts"
       "                WHERE id = report_host_details.report_host)"
       "         LIMIT 1)"
       " FROM report_host_details"
       " WHERE (SELECT report FROM report_hosts"
       "        WHERE id = report_host)"
       "       = %llu"
       /* Only if the task is included in the assets. */
       " AND (SELECT value = 'yes' FROM task_preferences"
       "      WHERE task = (SELECT task FROM reports WHERE id = %llu)"
       "      AND name = 'in_assets')"
       " AND (name IN ('best_os_cpe', 'best_os_txt', 'traceroute'));",
       report,
       report,
       report,
       report);
}

/**
 * @brief Get XML of a detailed host route.
 *
 * @param[in]  host  The host.
 */
gchar*
host_routes_xml (host_t host)
{
  iterator_t routes;
  GString* buffer;

  gchar *owned_clause;

  owned_clause = acl_where_owned_for_get ("host", NULL);

  buffer = g_string_new ("<routes>");
  init_iterator (&routes,
                 "SELECT outer_details.value,"
                 "       outer_details.source_type,"
                 "       outer_details.source_id,"
                 "       outer_identifiers.modification_time"
                 "  FROM host_details AS outer_details"
                 "  JOIN host_identifiers AS outer_identifiers"
                 "    ON outer_identifiers.host = outer_details.host"
                 " WHERE outer_details.host = %llu"
                 "   AND outer_details.name = 'traceroute'"
                 "   AND outer_details.source_id = outer_identifiers.source_id"
                 "   AND outer_identifiers.name='ip'"
                 "   AND outer_identifiers.modification_time"
                 "         = (SELECT max (modification_time)"
                 "              FROM host_identifiers"
                 "             WHERE host_identifiers.host = %llu"
                 "               AND host_identifiers.source_id IN"
                 "                   (SELECT source_id FROM host_details"
                 "                     WHERE host = %llu"
                 "                       AND value = outer_details.value)"
                 "              AND host_identifiers.name='ip')"
                 " ORDER BY outer_identifiers.modification_time DESC;",
                 host, host, host);

  while (next (&routes))
    {
      const char *traceroute;
      const char *source_id;
      time_t modified;
      gchar **hop_ips, **hop_ip;
      int distance;

      g_string_append (buffer, "<route>");

      traceroute = iterator_string (&routes, 0);
      source_id = iterator_string (&routes, 2);
      modified = iterator_int64 (&routes, 3);

      hop_ips = g_strsplit (traceroute, ",", 0);
      hop_ip = hop_ips;

      distance = 0;

      while (*hop_ip != NULL) {
        iterator_t best_host_iterator;
        const char *best_host_id;
        int same_source;

        init_iterator (&best_host_iterator,
                       "SELECT hosts.uuid,"
                       "       (source_id='%s')"
                       "         AS same_source"
                       "  FROM hosts, host_identifiers"
                       " WHERE hosts.id = host_identifiers.host"
                       "   AND host_identifiers.name = 'ip'"
                       "   AND host_identifiers.value='%s'"
                       "   AND %s"
                       " ORDER BY same_source DESC,"
                       "          abs(host_identifiers.modification_time"
                       "              - %llu) ASC"
                       " LIMIT 1;",
                       source_id,
                       *hop_ip,
                       owned_clause,
                       modified);

        if (next (&best_host_iterator))
          {
            best_host_id = iterator_string (&best_host_iterator, 0);
            same_source = iterator_int (&best_host_iterator, 1);
          }
        else
          {
            best_host_id = NULL;
            same_source = 0;
          }

        g_string_append_printf (buffer,
                                "<host id=\"%s\""
                                " distance=\"%d\""
                                " same_source=\"%d\">"
                                "<ip>%s</ip>"
                                "</host>",
                                best_host_id ? best_host_id : "",
                                distance,
                                same_source,
                                *hop_ip);

        cleanup_iterator (&best_host_iterator);

        distance++;
        hop_ip++;
      }

      g_string_append (buffer, "</route>");
      g_strfreev(hop_ips);
    }

  cleanup_iterator (&routes);

  g_string_append (buffer, "</routes>");

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Add host details to a report host.
 *
 * @param[in]  report  UUID of resource.
 * @param[in]  ip      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 *ip, entity_t entity)
{
  int in_assets;
  entities_t details;
  entity_t detail;
  char *uuid;

  in_assets = sql_int ("SELECT not(value = 'no') FROM task_preferences"
                       " WHERE task = (SELECT task FROM reports"
                       "                WHERE id = %llu)"
                       " AND name = 'in_assets';",
                       report);

  details = entity->entities;
  if (identifiers == NULL)
    identifiers = make_array ();
  if (identifier_hosts == NULL)
    identifier_hosts = make_array ();
  uuid = report_uuid (report);
  while ((detail = first_entity (details)))
    {
      if (strcmp (entity_name (detail), "detail") == 0)
        {
          entity_t source, source_type, source_name, source_desc, name, value;

          /* Parse host detail and add to report */
          source = entity_child (detail, "source");
          if (source == NULL)
            goto error;
          source_type = entity_child (source, "type");
          if (source_type == NULL)
            goto error;
          source_name = entity_child (source, "name");
          if (source_name == NULL)
            goto error;
          source_desc = entity_child (source, "description");
          if (source_desc == NULL)
            goto error;
          name = entity_child (detail, "name");
          if (name == NULL)
            goto error;
          value = entity_child (detail, "value");
          if (value == NULL)
            goto error;
          insert_report_host_detail
           (report, ip, entity_text (source_type), entity_text (source_name),
            entity_text (source_desc), entity_text (name), entity_text (value));

          /* Only add to assets if "Add to Assets" is set on the task. */
          if (in_assets)
            {
              if (strcmp (entity_text (name), "hostname") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("hostname");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "MAC") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("MAC");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "DNS-via-TargetDefinition") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("DNS-via-TargetDefinition");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "OS") == 0
                  && g_str_has_prefix (entity_text (value), "cpe:"))
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("OS");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "ssh-key") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("ssh-key");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
            }
        }
      details = next_entities (details);
    }
  free (uuid);

  return 0;

 error:
  free (uuid);
  return -1;
}

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

/**
 * @brief Initialise a host identifier iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  host        Host.
 * @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_host_identifier_iterator (iterator_t* iterator, host_t host,
                               int ascending, const char* sort_field)
{
  assert (current_credentials.uuid);

  if (host)
    init_iterator (iterator,
                   "SELECT id, uuid, name, comment, iso_time (creation_time),"
                   "       iso_time (modification_time), creation_time,"
                   "       modification_time, owner, owner, value,"
                   "       source_type, source_id, source_data,"
                   "       (CASE WHEN source_type LIKE 'Report%%'"
                   "        THEN NOT EXISTS (SELECT * FROM reports"
                   "                         WHERE uuid = source_id)"
                   "        ELSE CAST (0 AS boolean)"
                   "        END),"
                   "       '', ''"
                   " FROM host_identifiers"
                   " WHERE host = %llu"
                   " UNION"
                   " SELECT id, uuid, name, comment, iso_time (creation_time),"
                   "        iso_time (modification_time), creation_time,"
                   "        modification_time, owner, owner,"
                   "        (SELECT name FROM oss WHERE id = os),"
                   "        source_type, source_id, source_data,"
                   "        (CASE WHEN source_type LIKE 'Report%%'"
                   "         THEN NOT EXISTS (SELECT * FROM reports"
                   "                          WHERE uuid = source_id)"
                   "         ELSE CAST (0 AS boolean)"
                   "         END),"
                   "        (SELECT uuid FROM oss WHERE id = os),"
                   "        cpe_title ((SELECT name FROM oss WHERE id = os))"
                   " FROM host_oss"
                   " WHERE host = %llu"
                   " ORDER BY %s %s;",
                   host,
                   host,
                   sort_field ? sort_field : "creation_time",
                   ascending ? "ASC" : "DESC");
  else
    init_iterator (iterator,
                   "SELECT id, uuid, name, comment, iso_time (creation_time),"
                   "       iso_time (modification_time), creation_time,"
                   "       modification_time, owner, owner, value,"
                   "       source_type, source_id, source_data, 0, '', ''"
                   " FROM host_identifiers"
                   " ORDER BY %s %s;",
                   sort_field ? sort_field : "creation_time",
                   ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the value from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the host identifier, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_value, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the source type from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source type of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_type,
            GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the source from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_id, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the source data from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source data of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_data,
            GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the source orphan state from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source orphan state of the host identifier, or 0 if iteration is
 *         complete. Freed by cleanup_iterator.
 */
int
host_identifier_iterator_source_orphan (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the OS UUID from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The OS UUID of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_os_id,
            GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the OS title from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The OS title of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_os_title,
            GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Filter columns for host iterator.
 */
#define HOST_ITERATOR_FILTER_COLUMNS                                        \
 { GET_ITERATOR_FILTER_COLUMNS, "severity", "os", "oss", "hostname", "ip",  \
   "severity_level", "updated", NULL }

/**
 * @brief Host iterator columns.
 */
#define HOST_ITERATOR_COLUMNS                                         \
 {                                                                    \
   GET_ITERATOR_COLUMNS (hosts),                                      \
   {                                                                  \
     "1",                                                             \
     "writable",                                                      \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "0",                                                             \
     "in_use",                                                        \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "(SELECT round (CAST (severity AS numeric), 1)"                  \
     " FROM host_max_severities"                                      \
     " WHERE host = hosts.id"                                         \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT CASE"                                                   \
     "        WHEN best_os_text LIKE '%[possible conflict]%'"         \
     "        THEN best_os_text"                                      \
     "        WHEN best_os_cpe IS NULL"                               \
     "        THEN '[unknown]'"                                       \
     "        ELSE best_os_cpe"                                       \
     "        END"                                                    \
     " FROM (SELECT (SELECT value"                                    \
     "               FROM (SELECT max (id) AS id"                     \
     "                     FROM host_details"                         \
     "                     WHERE host = hosts.id"                     \
     "                     AND name = 'best_os_cpe')"                 \
     "                     AS sub,"                                   \
     "                    host_details"                               \
     "               WHERE sub.id = host_details.id)"                 \
     "              AS best_os_cpe,"                                  \
     "              (SELECT value"                                    \
     "               FROM (SELECT max (id) AS id"                     \
     "                     FROM host_details"                         \
     "                     WHERE host = hosts.id"                     \
     "                     AND name = 'best_os_text')"                \
     "                     AS sub,"                                   \
     "                    host_details"                               \
     "               WHERE sub.id = host_details.id)"                 \
     "              AS best_os_text)"                                 \
     "      AS vars)",                                                \
     "os",                                                            \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT group_concat (name, ', ') FROM oss"                     \
     "  WHERE id IN (SELECT distinct os FROM host_oss"                \
     "               WHERE host = hosts.id))",                        \
     "oss",                                                           \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "(SELECT value"                                                  \
     " FROM host_identifiers"                                         \
     " WHERE host = hosts.id"                                         \
     " AND (name = 'hostname' or name = 'DNS-via-TargetDefinition')"  \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "hostname",                                                      \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT value"                                                  \
     " FROM host_identifiers"                                         \
     " WHERE host = hosts.id"                                         \
     " AND name = 'ip'"                                               \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "ip",                                                            \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

#define HOST_ITERATOR_WHERE_COLUMNS                                   \
 {                                                                    \
   {                                                                  \
     "(SELECT severity_to_level (CAST (severity AS numeric), 0)"      \
     " FROM host_max_severities"                                      \
     " WHERE host = hosts.id"                                         \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "severity_level",                                                \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "modification_time", "updated", KEYWORD_TYPE_INTEGER             \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

/**
 * @brief Initialise a host iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find host, 2 failed to find filter,
 *         -1 error.
 */
int
init_asset_host_iterator (iterator_t *iterator, const get_data_t *get)
{
  static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = HOST_ITERATOR_COLUMNS;
  static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS;

  return init_get_iterator2 (iterator,
                             "host",
                             get,
                             /* Columns. */
                             columns,
                             /* Columns for trashcan. */
                             NULL,
                             /* WHERE Columns. */
                             where_columns,
                             /* WHERE Columns for trashcan. */
                             NULL,
                             filter_columns,
                             0,
                             NULL,
                             NULL,
                             TRUE,
                             FALSE,
                             NULL);
}

/**
 * @brief Get the writable status from an asset iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if writable, else 0.
 */
int
asset_iterator_writable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT);
}

/**
 * @brief Get the "in use" status from an asset iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if in use, else 0.
 */
int
asset_iterator_in_use (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the max severity from an asset host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum severity of the host, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Count number of hosts.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of hosts in filtered set.
 */
int
asset_host_count (const get_data_t *get)
{
  static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = HOST_ITERATOR_COLUMNS;
  static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS;
  return count2 ("host", get, columns, NULL, where_columns, NULL,
                 filter_columns, 0, NULL, NULL, TRUE);
}

/**
 * @brief Filter columns for os iterator.
 */
#define OS_ITERATOR_FILTER_COLUMNS                                           \
 { GET_ITERATOR_FILTER_COLUMNS, "title", "hosts", "latest_severity",         \
   "highest_severity", "average_severity", "average_severity_score", NULL }

/**
 * @brief OS iterator columns.
 */
#define OS_ITERATOR_COLUMNS                                                   \
 {                                                                            \
   GET_ITERATOR_COLUMNS (oss),                                                \
   {                                                                          \
     "0",                                                                     \
     "writable",                                                              \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT count (*) > 0 FROM host_oss WHERE os = oss.id)",                \
     "in_use",                                                                \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT coalesce (cpe_title (oss.name), ''))",                          \
     "title",                                                                 \
     KEYWORD_TYPE_STRING                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT count (distinct host) FROM host_oss WHERE os = oss.id)",        \
     "hosts",                                                                 \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT round (CAST (severity AS numeric), 1) FROM host_max_severities" \
     " WHERE host = (SELECT host FROM host_oss"                               \
     "               WHERE os = oss.id"                                       \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     " ORDER BY creation_time DESC LIMIT 1)",                                 \
     "latest_severity",                                                       \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT round (max (CAST (severity AS numeric)), 1)"                    \
     " FROM host_max_severities"                                              \
     " WHERE host IN (SELECT DISTINCT host FROM host_oss"                     \
     "                WHERE os = oss.id))",                                   \
     "highest_severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT round (CAST (avg (severity) AS numeric), 2)"                    \
     " FROM (SELECT (SELECT severity FROM host_max_severities"                \
     "               WHERE host = hosts.host"                                 \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     "              AS severity"                                              \
     "       FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)"     \
     "       AS hosts)"                                                       \
     " AS severities)",                                                       \
     "average_severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief OS iterator optional filtering columns.
 */
#define OS_ITERATOR_WHERE_COLUMNS                                             \
 {                                                                            \
   {                                                                          \
     "(SELECT round (CAST (avg (severity) AS numeric)"                        \
     "               * (SELECT count (distinct host)"                         \
     "                  FROM host_oss WHERE os = oss.id), 2)"                 \
     " FROM (SELECT (SELECT severity FROM host_max_severities"                \
     "               WHERE host = hosts.host"                                 \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     "              AS severity"                                              \
     "       FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)"     \
     "       AS hosts)"                                                       \
     " AS severities)",                                                       \
     "average_severity_score",                                                \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Initialise an OS iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find os, 2 failed to find filter,
 *         -1 error.
 */
int
init_asset_os_iterator (iterator_t *iterator, const get_data_t *get)
{
  static const char *filter_columns[] = OS_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OS_ITERATOR_COLUMNS;
  static column_t where_columns[] = OS_ITERATOR_WHERE_COLUMNS;

  return init_get_iterator2 (iterator,
                             "os",
                             get,
                             /* Columns. */
                             columns,
                             /* Columns for trashcan. */
                             NULL,
                             /* WHERE Columns. */
                             where_columns,
                             /* WHERE Columns for trashcan. */
                             NULL,
                             filter_columns,
                             0,
                             NULL,
                             NULL,
                             TRUE,
                             FALSE,
                             NULL);
}

/**
 * @brief Get the title from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The title of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_title, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the number of installs from an asset OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Number of hosts that have the OS.
 */
int
asset_os_iterator_installs (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
}

/**
 * @brief Get the latest severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_latest_severity, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the highest severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_highest_severity, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the average severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_average_severity, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Count number of oss.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of oss in filtered set.
 */
int
asset_os_count (const get_data_t *get)
{
  static const char *extra_columns[] = OS_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OS_ITERATOR_COLUMNS;
  return count ("os", get, columns, NULL, extra_columns, 0, 0, 0, TRUE);
}

/**
 * @brief Count number of all assets.
 *
 * @param[in]   get       GET params.
 *
 * @return Total number of assets.
 */
int
total_asset_count (const get_data_t *get)
{
  return sql_int ("SELECT (SELECT count (*) FROM hosts);");
}

/**
 * @brief Initialise an OS host iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  os          OS.
 */
void
init_os_host_iterator (iterator_t* iterator, resource_t os)
{
  assert (os);
  init_iterator (iterator,
                 "SELECT id, uuid, name, comment, iso_time (creation_time),"
                 "       iso_time (modification_time), creation_time,"
                 "       modification_time, owner, owner,"
                 "       (SELECT round (CAST (severity AS numeric), 1)"
                 "        FROM host_max_severities"
                 "        WHERE host = hosts.id"
                 "        ORDER by creation_time DESC"
                 "        LIMIT 1)"
                 " FROM hosts"
                 " WHERE id IN (SELECT DISTINCT host FROM host_oss"
                 "              WHERE os = %llu)"
                 " ORDER BY modification_time DESC;",
                 os);
}

/**
 * @brief Get the severity from an OS host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS host, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (os_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Initialise an asset host detail iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  host        Host.
 */
void
init_host_detail_iterator (iterator_t* iterator, resource_t host)
{
  assert (host);
  init_iterator (iterator,
                 "SELECT sub.id, name, value, source_type, source_id"
                 " FROM (SELECT max (id) AS id FROM host_details"
                 "       WHERE host = %llu"
                 "       GROUP BY name)"
                 "      AS sub,"
                 "      host_details"
                 " WHERE sub.id = host_details.id"
                 " ORDER BY name ASC;",
                 host);
}

/**
 * @brief Get the name from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_name, 1);

/**
 * @brief Get the name from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_value, 2);

/**
 * @brief Get the source type from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source type of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_source_type, 3);

/**
 * @brief Get the source ID from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source ID of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_source_id, 4);

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

/**
 * @brief Check whether a string is an identifier name.
 *
 * @param[in]  name  Possible identifier name.
 *
 * @return 1 if an identifier name, else 0.
 */
static int
identifier_name (const char *name)
{
  return (strcmp ("hostname", name) == 0)
         || (strcmp ("MAC", name) == 0)
         || (strcmp ("DNS-via-TargetDefinition", name) == 0)
         || (strcmp ("OS", name) == 0)
         || (strcmp ("ssh-key", name) == 0);
}

/**
 * @brief Create a host asset.
 *
 * @param[in]  host_name    Host Name.
 * @param[in]  comment      Comment.
 * @param[out] host_return  Created asset.
 *
 * @return 0 success, 1 failed to find report, 99 permission denied, -1 error.
 */
int
create_asset_host (const char *host_name, const char *comment,
                   resource_t* host_return)
{
  resource_t host;
  gchar *quoted_host_name, *quoted_comment;

  if (host_name == NULL)
    return -1;

  sql_begin_immediate ();

  if (acl_user_may ("create_asset") == 0)
    {
      sql_rollback ();
      return 99;
    }

  quoted_host_name = sql_quote (host_name);
  quoted_comment = sql_quote (comment ? comment : "");
  sql ("INSERT into hosts"
       " (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_host_name,
       quoted_comment);
  g_free (quoted_host_name);
  g_free (quoted_comment);

  host = sql_last_insert_id ();

  sql ("INSERT into host_identifiers"
       " (uuid, host, owner, name, comment, value, source_type, source_id,"
       "  source_data, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), %llu, (SELECT id FROM users WHERE uuid = '%s'), 'ip',"
       "  '', '%s', 'User', '%s', '', m_now (), m_now ());",
       host,
       current_credentials.uuid,
       host_name,
       current_credentials.uuid);

  if (host_return)
    *host_return = host;

  sql_commit ();

  return 0;
}

/**
 * @brief Create all available assets from a report.
 *
 * @param[in]  report_id  UUID of report.
 * @param[in]  term       Filter term, for min_qod and apply_overrides.
 *
 * @return 0 success, 1 failed to find report, 99 permission denied, -1 error.
 */
int
create_asset_report (const char *report_id, const char *term)
{
  resource_t report;
  iterator_t hosts;
  gchar *quoted_report_id, *apply_overrides_string, *min_qod_string;
  int apply_overrides, min_qod;

  if (report_id == NULL)
    return -1;

  sql_begin_immediate ();

  if (acl_user_may ("create_asset") == 0)
    {
      sql_rollback ();
      return 99;
    }

  report = 0;
  if (find_report_with_permission (report_id, &report, "get_reports"))
    {
      sql_rollback ();
      return -1;
    }

  if (report == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* These are freed by hosts_set_identifiers. */
  if (identifiers == NULL)
    identifiers = make_array ();
  if (identifier_hosts == NULL)
    identifier_hosts = make_array ();

  quoted_report_id = sql_quote (report_id);
  sql ("DELETE FROM host_identifiers WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_oss WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_max_severities WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_details WHERE source_id = '%s';",
       quoted_report_id);
  g_free (quoted_report_id);

  init_report_host_iterator (&hosts, report, NULL, 0);
  while (next (&hosts))
    {
      const char *host;
      report_host_t report_host;
      iterator_t details;

      host = host_iterator_host (&hosts);
      report_host = host_iterator_report_host (&hosts);

      if (report_host_dead (report_host)
          || report_host_result_count (report_host) == 0)
        continue;

      host_notice (host, "ip", host, "Report Host", report_id, 0, 0);

      init_report_host_details_iterator (&details, report_host);
      while (next (&details))
        {
          const char *name;

          name = report_host_details_iterator_name (&details);

          if (identifier_name (name))
            {
              const char *value;
              identifier_t *identifier;

              value = report_host_details_iterator_value (&details);

              if ((strcmp (name, "OS") == 0)
                  && (g_str_has_prefix (value, "cpe:") == 0))
                continue;

              identifier = g_malloc (sizeof (identifier_t));
              identifier->ip = g_strdup (host);
              identifier->name = g_strdup (name);
              identifier->value = g_strdup (value);
              identifier->source_id = g_strdup (report_id);
              identifier->source_type = g_strdup ("Report Host Detail");
              identifier->source_data
               = g_strdup (report_host_details_iterator_source_name (&details));

              array_add (identifiers, identifier);
              array_add_new_string (identifier_hosts, g_strdup (host));
            }
        }
      cleanup_iterator (&details);
    }
  cleanup_iterator (&hosts);
  hosts_set_identifiers ();
  apply_overrides_string = filter_term_value (term, "apply_overrides");
  if (apply_overrides_string)
    apply_overrides = atoi (apply_overrides_string);
  else
    apply_overrides = 1;
  g_free (apply_overrides_string);
  min_qod_string = filter_term_value (term, "min_qod");
  if (min_qod_string)
    min_qod = atoi (min_qod_string);
  else
    min_qod = MIN_QOD_DEFAULT;
  g_free (min_qod_string);
  hosts_set_max_severity (report, &apply_overrides, &min_qod);
  hosts_set_details (report);

  sql_commit ();

  return 0;
}

/**
 * @brief Modify an asset.
 *
 * @param[in]   asset_id        UUID of asset.
 * @param[in]   comment         Comment on asset.
 *
 * @return 0 success, 1 failed to find asset, 3 asset_id required,
 *         99 permission denied, -1 internal error.
 */
int
modify_asset (const char *asset_id, const char *comment)
{
  gchar *quoted_asset_id, *quoted_comment;
  resource_t asset;

  if (asset_id == NULL)
    return 3;

  sql_begin_immediate ();

  if (acl_user_may ("modify_asset") == 0)
    {
      sql_rollback ();
      return 99;
    }

  /* Host. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM hosts WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset == 0)
    {
      sql_rollback ();
      return 1;
    }

  quoted_comment = sql_quote (comment ?: "");

  sql ("UPDATE hosts SET"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_comment, asset);

  g_free (quoted_comment);

  sql_commit ();

  return 0;
}

/**
 * @brief Delete all asset that came from a report.
 *
 * Assume caller started a transaction.
 *
 * @param[in]  report_id  UUID of report.
 *
 * @return 0 success, 2 failed to find report, 4 UUID
 *         required, 99 permission denied, -1 error.
 */
int
delete_report_assets (const char *report_id)
{
  resource_t report;
  gchar *quoted_report_id;

  report = 0;
  if (find_report_with_permission (report_id, &report, "delete_report"))
    {
      sql_rollback ();
      return -1;
    }

  if (report == 0)
    {
      sql_rollback ();
      return 1;
    }

  quoted_report_id = sql_quote (report_id);

  /* Delete the hosts and OSs identified by this report if they were only
   * identified by this report. */

  sql ("CREATE TEMPORARY TABLE delete_report_assets_hosts (host INTEGER);");

  /* Collect hosts that were only identified by the given source. */
  sql ("INSERT into delete_report_assets_hosts"
       " (host)"
       " SELECT id FROM hosts"
       " WHERE (EXISTS (SELECT * FROM host_identifiers"
       "                WHERE host = hosts.id"
       "                AND source_id = '%s')"
       "        OR EXISTS (SELECT * FROM host_oss"
       "                   WHERE host = hosts.id"
       "                   AND source_id = '%s'))"
       " AND NOT EXISTS (SELECT * FROM host_identifiers"
       "                 WHERE host = hosts.id"
       "                 AND source_id != '%s')"
       " AND NOT EXISTS (SELECT * FROM host_oss"
       "                 WHERE host = hosts.id"
       "                 AND source_id != '%s');",
      quoted_report_id,
      quoted_report_id,
      quoted_report_id,
      quoted_report_id);

  sql ("DELETE FROM host_identifiers WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_oss WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_max_severities WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_details WHERE source_id = '%s';",
       quoted_report_id);

  g_free (quoted_report_id);

  /* The host may have details from sources that did not identify the host. */
  sql ("DELETE FROM host_details"
       " WHERE host in (SELECT host FROM delete_report_assets_hosts);");

  /* The host may have severities from sources that did not identify the
   * host. */
  sql ("DELETE FROM host_max_severities"
       " WHERE host in (SELECT host FROM delete_report_assets_hosts);");

  sql ("DELETE FROM hosts"
       " WHERE id in (SELECT host FROM delete_report_assets_hosts);");

  sql ("DROP TABLE delete_report_assets_hosts;");

  sql_commit ();
  return 0;
}

/**
 * @brief Delete an asset.
 *
 * @param[in]  asset_id   UUID of asset.
 * @param[in]  report_id  UUID of report from which to delete assets.
 *                        Overridden by asset_id.
 * @param[in]  dummy      Dummy arg to match other delete functions.
 *
 * @return 0 success, 1 asset is in use, 2 failed to find asset, 4 UUID
 *         required, 99 permission denied, -1 error.
 */
int
delete_asset (const char *asset_id, const char *report_id, int dummy)
{
  resource_t asset, parent;
  gchar *quoted_asset_id, *parent_id;

  asset = parent = 0;

  sql_begin_immediate ();

  if (acl_user_may ("delete_asset") == 0)
    {
      sql_rollback ();
      return 99;
    }

  if (asset_id == NULL)
    {
      if (report_id == NULL)
        {
          sql_rollback ();
          return 3;
        }
      return delete_report_assets (report_id);
    }

  /* Host identifier. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM host_identifiers WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      parent_id = sql_string ("SELECT uuid FROM hosts"
                              " WHERE id = (SELECT host FROM host_identifiers"
                              "             WHERE id = %llu);",
                              asset);
      parent = 0;
      if (find_host_with_permission (parent_id, &parent, "delete_asset"))
        {
          sql_rollback ();
          return -1;
        }

      if (parent == 0)
        {
          sql_rollback ();
          return 99;
        }

      sql ("DELETE FROM host_identifiers WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  /* Host OS. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM host_oss WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      parent_id = sql_string ("SELECT uuid FROM hosts"
                              " WHERE id = (SELECT host FROM host_oss"
                              "             WHERE id = %llu);",
                              asset);
      parent = 0;
      if (find_host_with_permission (parent_id, &parent, "delete_asset"))
        {
          sql_rollback ();
          return -1;
        }

      if (parent == 0)
        {
          sql_rollback ();
          return 99;
        }

      sql ("DELETE FROM host_oss WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  /* OS. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM oss WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      if (sql_int ("SELECT count (*) FROM host_oss"
                   " WHERE os = %llu;",
                   asset))
        {
          sql_rollback ();
          return 1;
        }

      sql ("DELETE FROM oss WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  /* Host. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM hosts WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      sql ("DELETE FROM host_identifiers WHERE host = %llu;", asset);
      sql ("DELETE FROM host_oss WHERE host = %llu;", asset);
      sql ("DELETE FROM host_max_severities WHERE host = %llu;", asset);
      sql ("DELETE FROM host_details WHERE host = %llu;", asset);
      sql ("DELETE FROM hosts WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  sql_rollback ();
  return 2;
}


/* 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, KEYWORD_TYPE_INTEGER },                     \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                     \
   { "name", NULL, KEYWORD_TYPE_STRING },                     \
   { "comment", NULL, KEYWORD_TYPE_STRING },                  \
   { "value", NULL, KEYWORD_TYPE_STRING },                    \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                       \
 }

/**
 * @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,
                          NULL, 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 " ACL_USER_OWNS () ""
                     " ORDER BY coalesce (owner, 0) 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 Default Severity user setting as a double.
 *
 * @return The user's Default Severity.
 */
double
setting_default_severity_dbl ()
{
  return current_credentials.default_severity;
}

/**
 * @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 Return the Auto Cache Rebuild user setting as an int.
 *
 * @return 1 if cache is rebuilt automatically, 0 if not.
 */
int
setting_auto_cache_rebuild_int ()
{
  return sql_int ("SELECT value FROM settings"
                  " WHERE uuid = 'a09285b0-2d47-49b6-a4ef-946ee71f1d5c'"
                  " AND " ACL_USER_OWNS () ""
                  " ORDER BY coalesce (owner, 0) DESC;",
                  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,
                          NULL, 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 " ACL_USER_OWNS ()
                   /* Force the user's setting to come before the default. */
                   " ORDER BY coalesce (owner, 0) 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 (quoted_uuid);
  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 " ACL_USER_OWNS () ";",
               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 " ACL_USER_OWNS ()
                    /* Force the user's setting to come before the default. */
                    " ORDER BY coalesce (owner, 0) 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 (acl_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, SETTING_UUID_ROWS_PER_PAGE) == 0
               || strcmp (uuid, "f16bb236-a32d-4cd5-a880-e0fcf2599f59") == 0
               || strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0
               || strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0
               || strcmp (uuid, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0
               || strcmp (uuid, "578a1c14-e2dc-45ef-a591-89d31391d007") == 0
               || strcmp (uuid, "02e294fa-061b-11e6-ae64-28d24461215b") == 0
               || strcmp (uuid, "5a9046cc-0628-11e6-ba53-28d24461215b") == 0
               || strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 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 " ACL_IS_GLOBAL () ";",
                   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, SETTING_UUID_ROWS_PER_PAGE) == 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);
        }

      if (strcmp (uuid, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0)
        {
          double severity_dbl;
          /* Default Severity */
          if (sscanf (value, "%lf", &severity_dbl) != 1
              || severity_dbl < 0.0 || severity_dbl > 10.0)
            {
              g_free (value);
              return 2;
            }
          else
            current_credentials.default_severity = severity_dbl;
        }

      if (strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 0)
        {
          int value_int;
          /* Auto Cache Rebuild */
          if (sscanf (value, "%d", &value_int) != 1
              || (strcmp (value, "0") && strcmp (value, "1")))
            {
              g_free (value);
              return 2;
            }
        }

      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 " ACL_IS_GLOBAL ()
             "   LIMIT 1),"
             "  (SELECT comment FROM settings"
             "   WHERE uuid = '%s' AND " ACL_IS_GLOBAL ()
             "   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 " ACL_IS_GLOBAL () "),"
             "  '%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, "0f040d06-abf9-43a2-8f94-9de178b0e978") == 0)
        setting_name = g_strdup ("Assets 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, "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");
      else if (strcmp (uuid, "32b3d606-461b-4770-b3e1-b9ea3cf0f84c") == 0)
        setting_name = g_strdup ("Notes Filter");
      else if (strcmp (uuid, "956d13bd-3baa-4404-a138-5e7eb8f9630e") == 0)
        setting_name = g_strdup ("Overrides 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, "024550b8-868e-4b3c-98bf-99bb732f6a0d") == 0)
        setting_name = g_strdup ("Default SNMP 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, "353304fc-645e-11e6-ba7a-28d24461215b") == 0)
        setting_name = g_strdup ("Default Report Format");

      else if (strcmp (uuid, "778eedad-5550-4de0-abb6-1320d13b5e18") == 0)
        setting_name = g_strdup ("Default Schedule");

      else if (strcmp (uuid, "23409203-940a-4b4a-b70c-447475f18323") == 0)
        setting_name = g_strdup ("Default Target");

      /*
       * Main dashboard
       */
      else if (strcmp (uuid, "d97eca9f-0386-4e5d-88f2-0ed7f60c0646") == 0)
        setting_name = g_strdup ("Main Dashboard Configuration");

      /*
       * Scans dashboards
       */
      else if (strcmp (uuid, "c7584d7c-649f-4f8b-9ded-9e1dc20f24c8") == 0)
        setting_name = g_strdup ("Scans Dashboard Configuration");

      /* Tasks dashboard settings */
      else if (strcmp (uuid, "3d5db3c7-5208-4b47-8c28-48efc621b1e0") == 0)
        setting_name = g_strdup ("Tasks Top Dashboard Configuration");

      /* Reports dashboard settings */
      else if (strcmp (uuid, "e599bb6b-b95a-4bb2-a6bb-fe8ac69bc071") == 0)
        setting_name = g_strdup ("Reports Top Dashboard Configuration");

      /* Results dashboard settings */
      else if (strcmp (uuid, "0b8ae70d-d8fc-4418-8a72-e65ac8d2828e") == 0)
        setting_name = g_strdup ("Results Top Dashboard Configuration");

      /* Notes dashboard settings */
      else if (strcmp (uuid, "ce7b121-c609-47b0-ab57-fd020a0336f4") == 0)
        setting_name = g_strdup ("Notes Top Dashboard Configuration");

      /* Overrides dashboard settings */
      else if (strcmp (uuid, "054862fe-0781-4527-b1aa-2113bcd16ce7") == 0)
        setting_name = g_strdup ("Overrides Top Dashboard Configuration");

      /*
       * Assets dashboards
       */
      else if (strcmp (uuid, "0320e0db-bf30-4d4f-9379-b0a022d07cf7") == 0)
        setting_name = g_strdup ("Assets Dashboard Configuration");

      /* Hosts dashboard settings */
      else if (strcmp (uuid, "d3f5f2de-a85b-43f2-a817-b127457cc8ba") == 0)
        setting_name = g_strdup ("Hosts Top Dashboard Configuration");

      /* Operating Systems dashboard settings */
      else if (strcmp (uuid, "e93b51ed-5881-40e0-bc4f-7d3268a36177") == 0)
        setting_name = g_strdup ("OSs Top Dashboard Configuration");

      /*
       * SecInfo dashboards
       */
      else if (strcmp (uuid, "84ab32da-fe69-44d8-8a8f-70034cf28d4e") == 0)
        setting_name = g_strdup ("SecInfo Dashboard Configuration");

      /* NVTs dashboard settings */
      else if (strcmp (uuid, "f68d9369-1945-477b-968f-121c6029971b") == 0)
        setting_name = g_strdup ("NVTs Top Dashboard Configuration");

      /* CVEs dashboard settings */
      else if (strcmp (uuid, "815ddd2e-8654-46c7-a05b-d73224102240") == 0)
        setting_name = g_strdup ("CVEs Top Dashboard Configuration");

      /* CPEs dashboard settings */
      else if (strcmp (uuid, "9cff9b4d-b164-43ce-8687-f2360afc7500") == 0)
        setting_name = g_strdup ("CPEs Top Dashboard Configuration");

      /* OVAL Definitions dashboard settings */
      else if (strcmp (uuid, "9563efc0-9f4e-4d1f-8f8d-0205e32b90a4") == 0)
        setting_name = g_strdup ("OVAL Definitions Top Dashboard Configuration");

      /* CERT-Bund Advisories dashboard settings */
      else if (strcmp (uuid, "a6946f44-480f-4f37-8a73-28a4cd5310c4") == 0)
        setting_name = g_strdup ("CERT-Bund Advisories Top Dashboard"
                                 " Configuration");

      /* DFN-CERT Advisories */
      else if (strcmp (uuid, "9812ea49-682d-4f99-b3cc-eca051d1ce59") == 0)
        setting_name = g_strdup ("DFN-CERT Advisories Top Dashboard"
                                 " Configuration");

      /* All SecInfo */
      else if (strcmp (uuid, "4c7b1ea7-b7e6-4d12-9791-eb9f72b6f864") == 0)
        setting_name = g_strdup ("All SecInfo Top Dashboard Configuration");
    }

  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 " ACL_IS_GLOBAL () "),"
             "  '%s');",
             uuid,
             current_credentials.uuid,
             setting_name,
             uuid,
             quoted_value);

      g_free (value);
      g_free (quoted_value);

      return 0;
    }

  return 1;
}

/**
 * @brief Return max, adjusted according to maximum allowed rows.
 */
int
manage_max_rows (int max)
{
  int max_rows;

  if (current_credentials.uuid == NULL)
    return max;

  if (ignore_max_rows_per_page
      || setting_value_int (SETTING_UUID_MAX_ROWS_PER_PAGE, &max_rows))
    return max;

  if (max_rows && (max < 0 || max > max_rows))
    return max_rows;
  return max;
}

/**
 * @brief Get the name of a setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return Setting name.
 */
static const gchar *
setting_name (const gchar *uuid)
{
  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
    return "Default CA Cert";
  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    return "Max Rows Per Page";
  return NULL;
}

/**
 * @brief Check whether a setting is the Default CA Cert setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return 1 if Default CA Cert, else 0.
 */
int
setting_is_default_ca_cert (const gchar *uuid)
{
  return strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0;
}

/**
 * @brief Get the description of a setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return Setting description.
 */
static const gchar *
setting_description (const gchar *uuid)
{
  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
    return "Default CA Certificate for Scanners";
  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    return "The default maximum number of rows displayed in any listing.";
  return NULL;
}

/**
 * @brief Get the name of a setting.
 *
 * @param[in]  uuid   UUID of setting.
 * @param[in]  value  Value of setting, to verify.
 * @param[in]  user   User setting is to apply to, or NULL.
 *
 * @return 0 if valid, else 1.
 */
static int
setting_verify (const gchar *uuid, const gchar *value, const gchar *user)
{
  if (value == NULL)
    return 0;

  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
    return 0;

  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    {
      int max_rows;
      max_rows = atoi (value);
      if (user)
        {
          if (max_rows < -1)
            return 1;
        }
      else if (max_rows < 0)
        return 1;
    }
  return 0;
}

/**
 * @brief Normalise the value of a setting.
 *
 * @param[in]  uuid   UUID of setting.
 * @param[in]  value  Value of setting, to verify.
 *
 * @return Normalised value.
 */
static gchar *
setting_normalise (const gchar *uuid, const gchar *value)
{
  if (value == NULL)
    return NULL;

  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    {
      int max_rows;
      max_rows = atoi (value);
      if (max_rows < 0)
        return NULL;
      return g_strdup_printf ("%i", max_rows);
    }

  return g_strdup (value);
}

/**
 * @brief Change value of a setting.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  database        Location of manage database.
 * @param[in]  name            Name of user.
 * @param[in]  uuid            UUID of setting.
 * @param[in]  value           New value.
 *
 * @return 0 success, 1 failed to find user, 2 value out of range, 3 error in
 *         setting uuid, 4 modifying setting for a single user forbidden,
 *         5 syntax error in setting value, -1 error.
 */
int
manage_modify_setting (GSList *log_config, const gchar *database,
                       const gchar *name, const gchar *uuid, const char *value)
{
  int ret;
  gchar *quoted_name, *quoted_description, *quoted_value, *normalised;

  g_info ("   Modifying setting.\n");

  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT)
      && strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE))
    {
      fprintf (stderr, "Error in setting UUID.\n");
      return 3;
    }

  if (setting_verify (uuid, value, name))
    {
      fprintf (stderr, "Syntax error in setting value.\n");
      return 5;
    }

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  sql_begin_immediate ();

  if (name)
    {
      user_t user;

      if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
        {
          sql_rollback ();
          fprintf (stderr,
                   "Modifying this setting for a single user is forbidden.\n");
          return 4;
        }

      if (find_user_by_name (name, &user))
        {
          sql_rollback ();
          fprintf (stderr, "Internal error.\n");
          return -1;
        }

      if (user == 0)
        {
          sql_rollback ();
          fprintf (stderr, "Failed to find user.\n");
          return 1;
        }

      sql ("DELETE FROM settings"
           " WHERE uuid = '%s'"
           " AND owner = %llu;",
           uuid,
           user);

      normalised = setting_normalise (uuid, value);
      if (normalised)
        {
          quoted_value = sql_quote (normalised);
          g_free (normalised);
          quoted_name = sql_quote (setting_name (uuid));
          quoted_description = sql_quote (setting_description (uuid));
          sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
               " VALUES ('%s', %llu, '%s', '%s', '%s');",
               uuid,
               user,
               quoted_name,
               quoted_description,
               quoted_value);
          g_free (quoted_value);
          g_free (quoted_name);
          g_free (quoted_description);
        }
    }
  else
    {
      sql ("DELETE FROM settings"
           " WHERE uuid = '%s'"
           " AND owner IS NULL;",
           uuid);

      normalised = setting_normalise (uuid, value);
      if (normalised)
        {
          quoted_value = sql_quote (normalised);
          g_free (normalised);
          quoted_name = sql_quote (setting_name (uuid));
          quoted_description = sql_quote (setting_description (uuid));
          sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
               " VALUES ('%s', NULL, '%s', '%s', '%s');",
               uuid,
               quoted_name,
               quoted_description,
               quoted_value);
          g_free (quoted_value);
          g_free (quoted_name);
          g_free (quoted_description);
        }
    }

  sql_commit ();
  manage_option_cleanup ();
  return 0;
}

/**
 * @brief Get the default CA cert.
 *
 * @return Freshly allocated value of Default CA Cert setting.
 */
char *
manage_default_ca_cert ()
{
  return sql_string ("SELECT value FROM settings"
                     " WHERE uuid = '" SETTING_UUID_DEFAULT_CA_CERT "';");
}


/* 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", KEYWORD_TYPE_STRING },                     \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                         \
   { "vector", NULL, KEYWORD_TYPE_STRING },                     \
   { "complexity", NULL, KEYWORD_TYPE_STRING },                 \
   { "authentication", NULL, KEYWORD_TYPE_STRING },             \
   { "confidentiality_impact", NULL, KEYWORD_TYPE_STRING },     \
   { "integrity_impact", NULL, KEYWORD_TYPE_STRING },           \
   { "availability_impact", NULL, KEYWORD_TYPE_STRING },        \
   { "products", NULL, KEYWORD_TYPE_STRING },                   \
   { "cvss", NULL, KEYWORD_TYPE_DOUBLE },                       \
   { "description", NULL, KEYWORD_TYPE_STRING },                \
   { "cvss", "severity", KEYWORD_TYPE_DOUBLE },                 \
   { "creation_time", "published", KEYWORD_TYPE_INTEGER },      \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                         \
 }

/**
 * @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", KEYWORD_TYPE_STRING },                     \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                         \
   { "title", NULL, KEYWORD_TYPE_STRING },                      \
   { "status", NULL, KEYWORD_TYPE_STRING },                     \
   { "deprecated_by_id", NULL, KEYWORD_TYPE_INTEGER },          \
   { "max_cvss", NULL, KEYWORD_TYPE_DOUBLE },                   \
   { "cve_refs", "cves", KEYWORD_TYPE_INTEGER },                \
   { "nvd_id", NULL, KEYWORD_TYPE_INTEGER },                    \
   { "max_cvss", "severity", KEYWORD_TYPE_DOUBLE },             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                         \
 }

/**
 * @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", KEYWORD_TYPE_STRING },                      \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "version", NULL, KEYWORD_TYPE_INTEGER },                    \
   { "deprecated", NULL, KEYWORD_TYPE_INTEGER },                 \
   { "def_class", "class", KEYWORD_TYPE_STRING },                \
   { "title", NULL, KEYWORD_TYPE_STRING },                       \
   { "description", NULL, KEYWORD_TYPE_STRING },                 \
   { "xml_file", "file", KEYWORD_TYPE_STRING },                  \
   { "status", NULL, KEYWORD_TYPE_STRING },                      \
   { "max_cvss", NULL, KEYWORD_TYPE_DOUBLE },                    \
   { "cve_refs", "cves", KEYWORD_TYPE_INTEGER },                 \
   { "max_cvss", "severity", KEYWORD_TYPE_DOUBLE },              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                          \
 }

/**
 * @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", KEYWORD_TYPE_STRING },                      \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "title", NULL, KEYWORD_TYPE_STRING },                       \
   { "summary", NULL, KEYWORD_TYPE_STRING },                     \
   { "cve_refs", "cves", KEYWORD_TYPE_INTEGER },                 \
   { "max_cvss", NULL, KEYWORD_TYPE_DOUBLE },                    \
   { "max_cvss", "severity", KEYWORD_TYPE_DOUBLE },              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                          \
 }

/**
 * @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", KEYWORD_TYPE_STRING },                      \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "title", NULL, KEYWORD_TYPE_STRING },                       \
   { "summary", NULL, KEYWORD_TYPE_STRING },                     \
   { "cve_refs", "cves", KEYWORD_TYPE_INTEGER },                 \
   { "max_cvss", NULL, KEYWORD_TYPE_DOUBLE },                    \
   { "max_cvss", "severity", KEYWORD_TYPE_DOUBLE },              \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                          \
 }

/**
 * @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, KEYWORD_TYPE_INTEGER },                                \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                               \
   { "name", NULL, KEYWORD_TYPE_STRING },                               \
   { "comment", NULL, KEYWORD_TYPE_STRING },                            \
   { "iso_time (created)", NULL, KEYWORD_TYPE_STRING },                 \
   { "iso_time (modified)", NULL, KEYWORD_TYPE_STRING },                \
   { "created", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "modified", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "''", "_owner", KEYWORD_TYPE_STRING },                             \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { "type", NULL, KEYWORD_TYPE_STRING },                               \
   { "extra", NULL, KEYWORD_TYPE_STRING },                              \
   { "severity", NULL, KEYWORD_TYPE_DOUBLE },                           \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

/**
 * @brief All SecInfo iterator columns.
 */
#define ALL_INFO_ITERATOR_COLUMNS_ARGS(type, extra, severity)           \
 {                                                                      \
   { "id", NULL, KEYWORD_TYPE_INTEGER },                                \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                               \
   { "name", NULL, KEYWORD_TYPE_STRING },                               \
   { "comment", NULL, KEYWORD_TYPE_STRING },                            \
   { "iso_time (created)", NULL, KEYWORD_TYPE_STRING },                 \
   { "iso_time (modified)", NULL, KEYWORD_TYPE_STRING },                \
   { "created", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "modified", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "''", "_owner", KEYWORD_TYPE_STRING },                             \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { type, "type", KEYWORD_TYPE_STRING },                               \
   { extra, "extra", KEYWORD_TYPE_STRING },                             \
   { severity, "severity", KEYWORD_TYPE_DOUBLE },                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

 /**
 * @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, tag 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 All SecInfo iterator column union, with specifiers for LIMIT clause.
 */
#define ALL_INFO_UNION_COLUMNS_LIMIT                                           \
  "(SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('cve' AS text) AS type,"                       \
  "                       description as extra, cvss as severity"              \
  "                FROM cves"                                                  \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_1"                                              \
  " UNION ALL"                                                                 \
  " SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('cpe' AS text) AS type, title as extra,"       \
  "                       max_cvss as severity"                                \
  "                FROM cpes"                                                  \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_2"                                              \
  " UNION ALL"                                                                 \
  " SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('nvt' AS text) AS type,"                       \
  "                       tag AS extra,"                                       \
  "                       CAST (cvss_base AS float) as severity"               \
  "                FROM nvts"                                                  \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_3"                                              \
  " UNION ALL"                                                                 \
  " SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('cert_bund_adv' AS text) AS type,"             \
  "                       title as extra,"                                     \
  "                       max_cvss as severity"                                \
  "                FROM cert_bund_advs"                                        \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_4"                                              \
  " UNION ALL"                                                                 \
  " SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('dfn_cert_adv' AS text) AS type,"              \
  "                       title as extra,"                                     \
  "                       max_cvss as severity"                                \
  "                FROM dfn_cert_advs"                                         \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_5"                                              \
  " UNION ALL"                                                                 \
  " SELECT * FROM (SELECT " GET_ITERATOR_COLUMNS_STRING ","                    \
  "                       CAST ('' AS text) AS _owner,"                        \
  "                       CAST ('ovaldef' AS text) AS type, title as extra,"   \
  "                       max_cvss as severity"                                \
  "                FROM ovaldefs"                                              \
  "                %s%s"                                                       \
  "                %s"                                                         \
  "                %s)"                                                        \
  "               AS union_sub_6)"                                             \
  " 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]  id  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]  id  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]  id  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, NULL, 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;
  static column_t cve_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                          ("CAST ('cve' AS text)",
                                           "description",
                                           "cvss");
  static column_t cpe_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                          ("CAST ('cpe' AS text)",
                                           "title",
                                           "max_cvss");
  static column_t nvt_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                          ("CAST ('nvt' AS text)",
                                           "tag",
                                           "cvss_base");
  static column_t cert_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                           ("CAST ('cert_bund_adv' AS text)",
                                            "title",
                                            "max_cvss");
  static column_t dfn_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                          ("CAST ('dfn_cert_adv' AS text)",
                                           "title",
                                           "max_cvss");
  static column_t ovaldef_select_columns[] = ALL_INFO_ITERATOR_COLUMNS_ARGS
                                              ("CAST ('ovaldef' AS text)",
                                               "title",
                                               "max_cvss");
  int first, max;
  gchar *columns, *clause, *filter, *order, *limit_clause;
  gchar *subselect_limit_clause, *cve_clause, *cpe_clause, *nvt_clause;
  gchar *cert_clause, *dfn_clause, *ovaldef_clause, *cve_order;
  gchar *cpe_order, *nvt_order, *cert_order, *dfn_order, *ovaldef_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, NULL, get->trash,
                          &order, &first, &max, NULL, NULL);
  columns = columns_build_select (select_columns);

  subselect_limit_clause = g_strdup_printf ("LIMIT %s",
                                            sql_select_limit (max + first));

  limit_clause = g_strdup_printf ("LIMIT %s OFFSET %i",
                                  sql_select_limit (max),
                                  first);

  cve_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                              filter_columns, cve_select_columns, NULL,
                              get->trash, &cve_order, NULL, NULL, NULL, NULL);
  cpe_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                              filter_columns, cpe_select_columns, NULL,
                              get->trash, &cpe_order, NULL, NULL, NULL, NULL);
  nvt_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                              filter_columns, nvt_select_columns, NULL,
                              get->trash, &nvt_order, NULL, NULL, NULL, NULL);
  cert_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                               filter_columns, cert_select_columns, NULL,
                               get->trash, &cert_order, NULL, NULL, NULL, NULL);
  dfn_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                              filter_columns, dfn_select_columns, NULL,
                              get->trash, &dfn_order, NULL, NULL, NULL, NULL);
  ovaldef_clause = filter_clause ("allinfo", filter ? filter : get->filter,
                                  filter_columns, ovaldef_select_columns, NULL,
                                  get->trash, &ovaldef_order, NULL, NULL, NULL,
                                  NULL);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM " ALL_INFO_UNION_COLUMNS_LIMIT
                 " %s%s"
                 " %s"
                 " %s;",
                 /* For the outer SELECT. */
                 columns,
                 /* For the inner SELECTs. */
                 cve_clause ? "WHERE " : "",
                 cve_clause ? cve_clause : "",
                 cve_order,
                 subselect_limit_clause,
                 cpe_clause ? "WHERE " : "",
                 cpe_clause ? cpe_clause : "",
                 cpe_order,
                 subselect_limit_clause,
                 nvt_clause ? "WHERE " : "",
                 nvt_clause ? nvt_clause : "",
                 nvt_order,
                 subselect_limit_clause,
                 cert_clause ? "WHERE " : "",
                 cert_clause ? cert_clause : "",
                 cert_order,
                 subselect_limit_clause,
                 dfn_clause ? "WHERE " : "",
                 dfn_clause ? dfn_clause : "",
                 dfn_order,
                 subselect_limit_clause,
                 ovaldef_clause ? "WHERE " : "",
                 ovaldef_clause ? ovaldef_clause : "",
                 ovaldef_order,
                 subselect_limit_clause,
                 /* For the outer SELECT. */
                 clause ? "WHERE " : "",
                 clause ? clause : "",
                 order,
                 limit_clause);

  g_free (subselect_limit_clause);
  g_free (limit_clause);
  g_free (order);
  g_free (filter);
  g_free (columns);
  g_free (clause);
  g_free (cve_clause);
  g_free (cpe_clause);
  g_free (nvt_clause);
  g_free (cert_clause);
  g_free (dfn_clause);
  g_free (ovaldef_clause);
  g_free (cve_order);
  g_free (cpe_order);
  g_free (nvt_order);
  g_free (cert_order);
  g_free (dfn_order);
  g_free (ovaldef_order);
  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);

/**
 * @brief Initialise an ovaldi file iterator.
 *
 * @param[in]  iterator        Iterator.
 */
void
init_ovaldi_file_iterator (iterator_t* iterator)
{
  init_iterator (iterator,
                 sql_is_sqlite3 ()
                  ? "SELECT DISTINCT xml_file FROM ovaldefs;"
                  // FIX Until we have SCAP in Postgres.
                  : "SELECT * FROM meta WHERE 1 = 0;");
}

/**
 * @brief Get the name from an ovaldi file iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the file, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (ovaldi_file_iterator_name, 0);


/* 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]  password    Password for user.  Autogenerated if NULL.
 * @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 *password,
                    const gchar *role_name)
{
  char *uuid;
  array_t *roles;
  int ret;
  gchar *rejection_msg;

  g_info ("   Creating user.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  roles = make_array ();
  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          array_free (roles);
          cleanup_manage_process (TRUE);
          fprintf (stderr, "Internal Error.\n");
          return -1;
        }
      if (role == 0)
        {
          array_free (roles);
          cleanup_manage_process (TRUE);
          fprintf (stderr, "Failed to find role.\n");
          return -1;
        }
      array_add (roles, role_uuid (role));
    }
  else
    array_add (roles, g_strdup (ROLE_UUID_ADMIN));

  if (password)
    uuid = NULL;
  else
    uuid = openvas_uuid_make ();

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

  ret = create_user (name, password ? password : uuid, NULL, 0, NULL, 0, NULL,
                     NULL, NULL, roles, NULL, &rejection_msg, NULL, 0);

  switch (ret)
    {
      case 0:
        if (password)
          printf ("User created.\n");
        else
          printf ("User created with password '%s'.\n", uuid);
        break;
      case -2:
        fprintf (stderr, "User exists already.\n");
        break;
      default:
        if (rejection_msg)
          {
            fprintf (stderr, "Failed to create user: %s\n", rejection_msg);
          }
        else
          fprintf (stderr, "Failed to create user.\n");
        break;
    }

  current_credentials.uuid = NULL;
  g_free (rejection_msg);

  array_free (roles);
  free (uuid);

  manage_option_cleanup ();

  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.
 * @param[in]  inheritor_name  Name of user that inherits user's resources.
 *
 * @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 *inheritor_name)
{
  int ret;

  g_info ("   Deleting user.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

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

  switch ((ret = delete_user (NULL, name, 1, 0, NULL, inheritor_name)))
    {
      case 0:
        printf ("User deleted.\n");
        break;
      case 2:
        fprintf (stderr, "Failed to find user.\n");
        break;
      case 4:
        fprintf (stderr, "User has active tasks.\n");
        break;
      case 6:
        fprintf (stderr, "Inheritor not found.\n");
        break;
      case 7:
        fprintf (stderr, "Inheritor same as deleted user.\n");
        break;
      case 8:
        fprintf (stderr, "Invalid inheritor.\n");
        break;
      case 9:
        fprintf (stderr,
                 "Resources owned by the user are still in use by others.\n");
        break;
      default:
        fprintf (stderr, "Internal Error.\n");
        break;
    }

  current_credentials.uuid = NULL;

  manage_option_cleanup ();

  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;
  int ret;

  g_info ("   Getting users.\n");

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          fprintf (stderr, "Internal Error.\n");
          manage_option_cleanup ();
          return -1;
        }
      if (role == 0)
        {
          fprintf (stderr, "Failed to find role.\n");
          manage_option_cleanup ();
          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);

  manage_option_cleanup ();

  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 error.
 */
int
manage_set_password (GSList *log_config, const gchar *database,
                     const gchar *name, const gchar *password)
{
  user_t user;
  char *uuid;
  int ret;
  gchar *rejection_msg;

  g_info ("   Modifying user password.\n");

  if (name == NULL)
    {
      fprintf (stderr, "--user required.\n");
      return -1;
    }

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  sql_begin_immediate ();

  if (find_user_by_name (name, &user))
    {
      fprintf (stderr, "Internal error.\n");
      goto fail;
    }

  if (user == 0)
    {
      fprintf (stderr, "Failed to find user.\n");
      goto fail;
    }

  uuid = user_uuid (user);
  if (uuid == NULL)
    {
      fprintf (stderr, "Failed to allocate UUID.\n");
      goto fail;
    }

  rejection_msg = NULL;
  if (set_password (name, uuid, password, &rejection_msg))
    {
      if (rejection_msg)
        {
          fprintf (stderr, "New password rejected: %s\n", rejection_msg);
          g_free (rejection_msg);
        }
      else
        fprintf (stderr, "New password rejected.\n");
      free (uuid);
      goto fail;
    }

  sql_commit ();
  free (uuid);
  manage_option_cleanup ();
  return ret;

 fail:
  sql_rollback ();
  manage_option_cleanup ();
  return -1;
}

/**
 * @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")
       || !strcmp (g_ptr_array_index (allowed_methods, 0), "radius_connect")))
    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 (acl_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 && acl_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 (acl_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.
 * @param[in]  inheritor_id   UUID of user who will inherit owned objects.
 * @param[in]  inheritor_name Name of user who will inherit owned objects.
 *
 * @return 0 success, 2 failed to find user, 4 user has active tasks,
 *         5 attempted suicide, 6 inheritor not found, 7 inheritor same as
 *         deleted user, 8 invalid inheritor, 9 resources still in use,
 *         99 permission denied, -1 error.
 */
int
delete_user (const char *user_id_arg, const char *name_arg, int ultimate,
             int forbid_super_admin,
             const char* inheritor_id, const char *inheritor_name)
{
  iterator_t tasks;
  user_t user, inheritor;
  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 (acl_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, 1);
  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;

  /* Transfer ownership of objects to the inheritor if one is given */
  if (inheritor_id && strcmp (inheritor_id, ""))
    {
      if (strcmp (inheritor_id, "self") == 0)
        {
          sql_int64 (&inheritor, "SELECT id FROM users WHERE uuid = '%s'",
                     current_credentials.uuid);

          if (inheritor == 0)
            {
              sql_rollback ();
              return -1;
            }
        }
      else
        {
          if (find_user_with_permission (inheritor_id, &inheritor, "get_users"))
            {
              sql_rollback ();
              return -1;
            }

          if (inheritor == 0)
            {
              sql_rollback ();
              return 6;
            }
        }
    }
  else if (inheritor_name && strcmp (inheritor_name, ""))
    {
      if (find_user_by_name_with_permission (inheritor_name, &inheritor,
                                             "get_users"))
        {
          sql_rollback ();
          return -1;
        }

      if (inheritor == 0)
        {
          sql_rollback ();
          return 6;
        }
    }
  else
    inheritor = 0;

  if (inheritor)
    {
      gchar *deleted_user_id, *deleted_user_name;
      gchar *real_inheritor_id, *real_inheritor_name;

      if (inheritor == user)
        {
          sql_rollback ();
          return 7;
        }

      real_inheritor_id = user_uuid (inheritor);

      /* only the current user, owned users or global users may inherit */
      if (strcmp (real_inheritor_id, current_credentials.uuid)
          && ! acl_user_owns ("user", inheritor, 0)
          && sql_int ("SELECT owner != 0 FROM users WHERE id = %llu",
                      inheritor))
        {
          g_free (real_inheritor_id);
          sql_rollback ();
          return 8;
        }

      deleted_user_id = user_uuid (user);
      deleted_user_name = user_name (deleted_user_id);
      real_inheritor_name = user_name (real_inheritor_id);

      g_log ("event user", G_LOG_LEVEL_MESSAGE,
             "User %s (%s) is inheriting from %s (%s)",
             real_inheritor_name, real_inheritor_id,
             deleted_user_name, deleted_user_id);

      g_free (deleted_user_id);
      g_free (deleted_user_name);
      g_free (real_inheritor_id);
      g_free (real_inheritor_name);

      /* Transfer owned resources*/
      sql ("UPDATE agents SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE agents_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE alerts SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE alerts_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE configs SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE configs_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE credentials SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE credentials_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE host_identifiers SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE host_oss SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE hosts SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE filters SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE filters_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE notes SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE notes_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE oss SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu",
           inheritor, user);

      sql ("UPDATE port_lists SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE port_lists_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE report_formats SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE report_formats_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE report_counts SET \"user\" = %llu WHERE \"user\" = %llu",
           inheritor, user);
      sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE results SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE overrides SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE overrides_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE schedules SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE schedules_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE settings SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE tags SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE tags_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE targets SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE targets_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE tasks SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE groups SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE roles SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE users SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE groups_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE roles_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

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

  /* Delete settings and miscellaneous resources not referenced directly */
  // Agents
  /* skip the "in use" check because it always returns 0
  if (user_resources_in_use (user,
                             "agents", agent_in_use,
                             "agents_trash", trash_agent_in_use))
    {
      sql_rollback ();
      return 9;
    }
  */
  sql ("DELETE FROM agents WHERE owner = %llu;", user);
  sql ("DELETE FROM agents_trash WHERE owner = %llu;", user);
  // Settings
  sql ("DELETE FROM settings WHERE owner = %llu;", user);

  /* Delete data modifiers (not directly referenced) */
  // Notes
  sql ("DELETE FROM notes WHERE owner = %llu;", user);
  sql ("DELETE FROM notes_trash WHERE owner = %llu;", user);
  // Overrides
  sql ("DELETE FROM overrides WHERE owner = %llu;", user);
  sql ("DELETE FROM overrides_trash WHERE owner = %llu;", user);
  // Tags
  sql ("DELETE FROM tags WHERE owner = %llu;", user);
  sql ("DELETE FROM tags_trash WHERE owner = %llu;", user);

  /* Delete assets (not directly referenced) */
  // Hosts
  sql ("DELETE FROM host_details WHERE host IN"
       " (SELECT id FROM hosts WHERE owner = %llu);", user);
  sql ("DELETE FROM host_max_severities WHERE host IN"
       " (SELECT id FROM hosts WHERE owner = %llu);", user);
  sql ("DELETE FROM host_identifiers WHERE owner = %llu;", user);
  sql ("DELETE FROM host_oss WHERE owner = %llu;", user);
  sql ("DELETE FROM hosts WHERE owner = %llu;", user);
  // OSs
  sql ("DELETE FROM oss WHERE owner = %llu;", user);

  /* Delete report data and tasks (not directly referenced) */
  // Hosts
  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);
  // Results
  sql ("DELETE FROM results"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);
  // Reports
  sql ("DELETE FROM reports WHERE owner = %llu;", user);

  /* Delete tasks (not directly referenced) */
  if (user_resources_in_use (user,
                             "tasks", target_in_use,
                             NULL, NULL))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM task_alerts"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM task_files"
       " 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);

  /* Delete resouces directly used by tasks */
  // Alerts
  if (user_resources_in_use (user,
                             "alerts", alert_in_use,
                             "alerts_trash", trash_alert_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM alert_condition_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_condition_data_trash"
       " 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_trash"
       " 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_trash"
       " 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);
  // Configs
  if (user_resources_in_use (user,
                             "configs", config_in_use,
                             "configs_trash", trash_config_in_use))
    {
      sql_rollback ();
      return 9;
    }
  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);
  // Scanners
  if (user_resources_in_use (user,
                             "scanners", scanner_in_use,
                             "scanners_trash", trash_scanner_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM scanners WHERE owner = %llu;", user);
  sql ("DELETE FROM scanners_trash WHERE owner = %llu;", user);
  // Schedules
  if (user_resources_in_use (user,
                             "schedules", schedule_in_use,
                             "schedules_trash", trash_schedule_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM schedules WHERE owner = %llu;", user);
  sql ("DELETE FROM schedules_trash WHERE owner = %llu;", user);
  // Targets
  if (user_resources_in_use (user,
                             "targets", target_in_use,
                             "targets_trash", trash_target_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM targets_login_data WHERE target IN"
       " (SELECT id FROM targets WHERE owner = %llu);", user);
  sql ("DELETE FROM targets_trash_login_data WHERE target IN"
       " (SELECT id FROM targets_trash WHERE owner = %llu);", user);
  sql ("DELETE FROM targets WHERE owner = %llu;", user);
  sql ("DELETE FROM targets_trash WHERE owner = %llu;", user);

  /* Delete resources used indirectly by tasks */
  // Filters (used by alerts and settings)
  if (user_resources_in_use (user,
                             "filters", filter_in_use,
                             "filters_trash", trash_filter_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM filters WHERE owner = %llu;", user);
  sql ("DELETE FROM filters_trash WHERE owner = %llu;", user);
  // Port lists (used by targets)
  if (user_resources_in_use (user,
                             "port_lists", port_list_in_use,
                             "port_lists_trash", trash_port_list_in_use))
    {
      sql_rollback ();
      return 9;
    }
  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);
  // Report formats (used by alerts)
  if (user_resources_in_use (user,
                             "report_formats",
                             report_format_in_use,
                             "report_formats_trash",
                             trash_report_format_in_use))
    {
      sql_rollback ();
      return 9;
    }
  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);

  /* Delete credentials last because they can be used in various places */
  if (user_resources_in_use (user,
                             "credentials", credential_in_use,
                             "credentials_trash", trash_credential_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM credentials_data WHERE credential IN"
       " (SELECT id FROM credentials WHERE owner = %llu);",
       user);
  sql ("DELETE FROM credentials_trash_data WHERE credential IN"
       " (SELECT id FROM credentials_trash WHERE owner = %llu);",
       user);

  sql ("DELETE FROM credentials WHERE owner = %llu;", user);
  sql ("DELETE FROM credentials_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);
  sql ("UPDATE roles_trash SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE groups_trash 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);

  /* Delete permissions granted by the user */
  sql ("DELETE FROM permissions WHERE owner = %llu;", user);
  sql ("DELETE FROM permissions_trash WHERE owner = %llu;", user);

  /* Remove user from groups and roles */
  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);

  /* Delete 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 (acl_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 (acl_user_can_super_everyone (uuid) && strcmp (uuid, current_credentials.uuid))
    {
      g_free (uuid);
      sql_rollback ();
      return 99;
    }

  was_admin = acl_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 (acl_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 = acl_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 = acl_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 name of a user.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return Newly allocated name if available, else NULL.
 */
gchar*
user_name (const char *uuid)
{
  gchar *name, *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  name = sql_string ("SELECT name FROM users WHERE uuid = '%s';",
                     quoted_uuid);
  g_free (quoted_uuid);
  return name;
}

/**
 * @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 Return the ifaces of a user.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return Newly allocated ifaces value if available, else NULL.
 */
gchar*
user_ifaces (const char *uuid)
{
  gchar *name, *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  name = sql_string ("SELECT ifaces FROM users WHERE uuid = '%s';",
                     quoted_uuid);
  g_free (quoted_uuid);
  return name;
}

/**
 * @brief Return whether ifaces value of a user denotes allowed.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return 1 if allow, else 0.
 */
int
user_ifaces_allow (const char *uuid)
{
  gchar *quoted_uuid;
  int allow;

  quoted_uuid = sql_quote (uuid);
  allow = sql_int ("SELECT ifaces_allow FROM users WHERE uuid = '%s';",
                   quoted_uuid);
  g_free (quoted_uuid);
  return allow;
}

/**
 * @brief Return the hosts of a user.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return Newly allocated hosts value if available, else NULL.
 */
gchar*
user_hosts (const char *uuid)
{
  gchar *name, *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  name = sql_string ("SELECT hosts FROM users WHERE uuid = '%s';",
                     quoted_uuid);
  g_free (quoted_uuid);
  return name;
}

/**
 * @brief Return whether hosts value of a user denotes allowed.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return 1 if allow, else 0.
 */
int
user_hosts_allow (const char *uuid)
{
  gchar *quoted_uuid;
  int allow;

  quoted_uuid = sql_quote (uuid);
  allow = sql_int ("SELECT hosts_allow FROM users WHERE uuid = '%s';",
                   quoted_uuid);
  g_free (quoted_uuid);
  return allow;
}

/**
 * @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, KEYWORD_TYPE_STRING },                                \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                                 \
   { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER },                          \
   {                                                                       \
     "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",                                                              \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   {                                                                       \
     "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",                                                             \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   { "ifaces", NULL, KEYWORD_TYPE_STRING },                                \
   { "ifaces_allow", NULL, KEYWORD_TYPE_INTEGER },                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @brief User iterator columns for trash case.
 */
#define USER_ITERATOR_TRASH_COLUMNS                                        \
 {                                                                         \
   GET_ITERATOR_COLUMNS (users_trash),                                     \
   { "method", NULL, KEYWORD_TYPE_STRING },                                \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                                 \
   { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "ifaces", NULL, KEYWORD_TYPE_STRING },                                \
   { "ifaces_allow", NULL, KEYWORD_TYPE_INTEGER },                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

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

/**
 * @brief Get LDAP info.
 *
 * @param[out]  enabled    Whether LDAP is enabled.
 * @param[out]  host       Freshly allocated host.
 * @param[out]  authdn     Freshly allocated Auth DN.
 * @param[out]  plaintext  Whether plaintext auth is allowed.
 * @param[out]  cacert     CA cert if there's one, else NULL.
 */
void
manage_get_ldap_info (int *enabled, gchar **host, gchar **authdn,
                      int *plaintext, gchar **cacert)
{
  if (enabled)
    *enabled = ldap_auth_enabled ();

  *host = sql_string ("SELECT value FROM meta"
                      " WHERE name = 'ldap_host';");
  if (*host == NULL)
    *host = g_strdup ("127.0.0.1");

  *authdn = sql_string ("SELECT value FROM meta"
                        " WHERE name = 'ldap_authdn';");
  if (*authdn == NULL)
    *authdn = g_strdup ("userid=%s,dc=example,dc=org");

  *plaintext = sql_int ("SELECT coalesce ((SELECT CAST (value AS integer)"
                        "                  FROM meta"
                        "                  WHERE name"
                        "                        = 'ldap_allow_plaintext'),"
                        "                 0);");

  *cacert = sql_string ("SELECT value FROM meta"
                        " WHERE name = 'ldap_cacert';");
}

/**
 * @brief Check if a user still has resources that are in use.
 *
 * @param
 */
int
user_resources_in_use (user_t user,
                       const char *table, int(*in_use)(resource_t),
                       const char *trash_table, int(*trash_in_use)(resource_t))
{
  iterator_t iter;
  int has_resource_in_use = 0;

  init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu",
                 table, user);
  while (next (&iter) && has_resource_in_use == 0)
    {
      resource_t resource = iterator_int64 (&iter, 0);
      has_resource_in_use = in_use (resource);
    }
  cleanup_iterator (&iter);
  if (has_resource_in_use)
    return 1;

  if (trash_table == NULL || trash_in_use == NULL)
    return 0;

  init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu",
                 trash_table, user);
  while (next (&iter) && has_resource_in_use == 0)
    {
      resource_t resource = iterator_int64 (&iter, 0);
      has_resource_in_use = trash_in_use (resource);
    }
  cleanup_iterator (&iter);
  if (has_resource_in_use)
    return 2;

  return 0;
}

/**
 * @brief Set LDAP info.
 *
 * @param[in]  enabled    Whether LDAP is enabled.  -1 to keep current value.
 * @param[in]  host       LDAP host.  NULL to keep current value.
 * @param[in]  authdn     Auth DN.  NULL to keep current value.
 * @param[in]  allow_plaintext  Whether plaintext auth is allowed.  -1 to
 *                              keep current value.
 * @param[in]  cacert     CA certificate.  NULL to keep current value.
 */
void
manage_set_ldap_info (int enabled, gchar *host, gchar *authdn,
                      int allow_plaintext, gchar *cacert)
{
  gchar *quoted;

  sql_begin_immediate ();

  if (enabled >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_enable';");
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_enable', %i);", enabled);
    }

  if (host)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_host';");
      quoted = sql_quote (host);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_host', '%s');",
           quoted);
      g_free (quoted);
    }

  if (authdn)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_authdn';");
      quoted = sql_quote (authdn);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_authdn', '%s');",
           quoted);
      g_free (quoted);
    }

  if (allow_plaintext >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_allow_plaintext';");
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_allow_plaintext', %i);",
           allow_plaintext);
    }

  if (cacert)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_cacert';");
      quoted = sql_quote (cacert);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_cacert', '%s');",
           quoted);
      g_free (quoted);
    }

  sql_commit ();
}

/**
 * @brief Get RADIUS info.
 *
 * @param[out]  enabled     Whether RADIUS is enabled.
 * @param[out]  host        Freshly allocated RADIUS host.
 * @param[out]  key         Freshly allocated RADIUS secret key.
 */
void
manage_get_radius_info (int *enabled, char **host, char **key)
{
  if (enabled)
    *enabled = radius_auth_enabled ();

  *host = sql_string ("SELECT value FROM meta WHERE name = 'radius_host';");
  if (!*host)
    *host = g_strdup ("127.0.0.1");

  *key = sql_string ("SELECT value FROM meta WHERE name = 'radius_key';");
  if (!*key)
    *key = g_strdup ("testing123");
}

/**
 * @brief Set RADIUS info.
 *
 * @param[out]  enabled    Whether RADIUS is enabled. -1 to keep current value.
 * @param[out]  host       RADIUS host. NULL to keep current value.
 * @param[out]  key        Secret key.  NULL to keep current value.
 */
void
manage_set_radius_info (int enabled, gchar *host, gchar *key)
{
  char *quoted;

  sql_begin_immediate ();

  if (enabled >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'radius_enable';");
      sql ("INSERT INTO meta (name, value) VALUES ('radius_enable', %i);",
           enabled);
    }

  if (host)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'radius_host';");
      quoted = sql_quote (host);
      sql ("INSERT INTO meta (name, value) VALUES ('radius_host', '%s');",
           quoted);
      g_free (quoted);
    }

  if (key)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'radius_key';");
      quoted = sql_quote (key);
      sql ("INSERT INTO meta (name, value) VALUES ('radius_key', '%s');",
           quoted);
      g_free (quoted);
    }

  sql_commit ();
}


/* 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 (acl_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 if ((strcmp (lc_resource_type, "host") == 0)
           || (strcmp (lc_resource_type, "os") == 0))
    resource_permission = g_strdup ("get_assets");
  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, "host"))
           && (strcmp (lc_resource_type, "os"))
           && (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 (acl_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 (acl_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 if ((strcmp (lc_resource_type, "host") == 0)
               || (strcmp (lc_resource_type, "os") == 0))
        resource_permission = g_strdup ("get_assets");
      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, "host"))
              && (strcmp (lc_resource_type, "os"))
              && (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, KEYWORD_TYPE_STRING },                           \
   { "resource", NULL, KEYWORD_TYPE_INTEGER },                               \
   { "resource_uuid", NULL, KEYWORD_TYPE_STRING },                           \
   { "resource_location", NULL, KEYWORD_TYPE_INTEGER },                      \
   { "active", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { "value", NULL, KEYWORD_TYPE_STRING },                                   \
   { "(resource = 0)", "orphan", KEYWORD_TYPE_INTEGER },                     \
   {                                                                         \
     "resource_name (resource_type, resource_uuid, resource_location)",      \
     "resource_name",                                                        \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Tag iterator trash columns.
 */
#define TAG_ITERATOR_TRASH_COLUMNS                                           \
 {                                                                           \
   GET_ITERATOR_COLUMNS (tags_trash),                                        \
   { "resource_type", NULL, KEYWORD_TYPE_STRING },                           \
   { "resource", NULL, KEYWORD_TYPE_INTEGER },                               \
   { "resource_uuid", NULL, KEYWORD_TYPE_STRING },                           \
   { "resource_location", NULL, KEYWORD_TYPE_INTEGER },                      \
   { "active", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { "value", NULL, KEYWORD_TYPE_STRING },                                   \
   { "(resource = 0)", "orphan", KEYWORD_TYPE_INTEGER },                     \
   {                                                                         \
     "resource_name (resource_type, resource_uuid, resource_location)",      \
     "resource_name",                                                        \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @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, KEYWORD_TYPE_STRING },                        \
   { "resource_type", NULL, KEYWORD_TYPE_STRING },               \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                          \
 }

/**
 * @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 if ((strcmp (type, "host") == 0)
           || (strcmp (type, "os") == 0))
    permission = g_strdup ("get_assets");
  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)
{
  get_data_t get;
  gchar *owned_clause;

  assert (type);
  assert (resource);
  assert (current_credentials.uuid);

  get.trash = 0;
  owned_clause = acl_where_owned ("tag", &get, 1, "any", 0, NULL);

  init_iterator (iterator,
                 "SELECT id, uuid, name, value, comment"
                 " FROM tags"
                 " WHERE resource_type = '%s'"
                 " AND resource = %llu"
                 "%s"
                 " AND %s"
                 " ORDER BY %s %s;",
                 type,
                 resource,
                 active_only ? " AND active=1" : "",
                 owned_clause,
                 sort_field ? sort_field : "active DESC, name",
                 ascending ? "ASC" : "DESC");

  g_free (owned_clause);
  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 whether a column is one containing timestamp.
 *
 * @param[in]  column  The column name.
 *
 * @return  1 if column contains timestamps, 0 otherwise.
 */
int
column_is_timestamp (const char* column)
{
  return (column
          && (strcmp (column, "created") == 0
              || strcmp (column, "date") == 0
              || strcmp (column, "modified") == 0
              || strcmp (column, "published") == 0
              || strcmp (column, "updated") == 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, "REPORT") == 0)
    {
      static column_t columns[] = REPORT_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "RESULT") == 0)
    {
      static column_t columns[] = RESULT_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "HOST") == 0)
    {
      static column_t columns[] = HOST_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "OS") == 0)
    {
      static column_t columns[] = OS_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 if (strcasecmp (type, "NOTE") == 0)
    {
      static column_t columns[] = NOTE_ITERATOR_COLUMNS;
      return columns_build_select (columns);
    }
  else if (strcasecmp (type, "OVERRIDE") == 0)
    {
      static column_t columns[] = OVERRIDE_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 report_columns[] = REPORT_ITERATOR_COLUMNS;
  static column_t result_columns[] = RESULT_ITERATOR_COLUMNS;
  static column_t host_columns[] = HOST_ITERATOR_COLUMNS;
  static column_t os_columns[] = OS_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;
  static column_t note_columns[] = NOTE_ITERATOR_COLUMNS;
  static column_t override_columns[] = OVERRIDE_ITERATOR_COLUMNS;

  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    return task_columns;
  else if (strcasecmp (type, "REPORT") == 0)
    return report_columns;
  else if (strcasecmp (type, "RESULT") == 0)
    return result_columns;
  else if (strcasecmp (type, "HOST") == 0)
    return host_columns;
  else if (strcasecmp (type, "OS") == 0)
    return os_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;
  else if (strcasecmp (type, "NOTE") == 0)
    return note_columns;
  else if (strcasecmp (type, "OVERRIDE") == 0)
    return override_columns;
  return NULL;
}

/**
 * @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_where_columns (const char *type, int apply_overrides)
{
  static column_t task_columns[] = TASK_ITERATOR_WHERE_COLUMNS;
  static column_t report_columns[] = REPORT_ITERATOR_WHERE_COLUMNS;
  static column_t host_columns[] = HOST_ITERATOR_WHERE_COLUMNS;
  static column_t os_columns[] = OS_ITERATOR_WHERE_COLUMNS;

  if (type == NULL)
    return NULL;
  else if (strcasecmp (type, "TASK") == 0)
    return task_columns;
  else if (strcasecmp (type, "REPORT") == 0)
    return report_columns;
  else if (strcasecmp (type, "HOST") == 0)
    return host_columns;
  else if (strcasecmp (type, "OS") == 0)
    return os_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, "REPORT") == 0)
    {
      static const char *ret[] = REPORT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "RESULT") == 0)
    {
      static const char *ret[] = RESULT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "HOST") == 0)
    {
      static const char *ret[] = HOST_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "OS") == 0)
    {
      static const char *ret[] = OS_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 if (strcasecmp (type, "NOTE") == 0)
    {
      static const char *ret[] = NOTE_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  else if (strcasecmp (type, "OVERRIDE") == 0)
    {
      static const char *ret[] = OVERRIDE_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]  autofp           Whether to apply auto FP filter.
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  min_qod          Minimum QoD.
 *
 * @return The SQL subquery definition.
 */
gchar*
type_opts_table (const char *type, int autofp, 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, 0);
    }
  else if (strcasecmp (type, "REPORT") == 0)
    {
      return report_iterator_opts_table (apply_overrides, min_qod);
    }
  else if (strcasecmp (type, "RESULT") == 0)
    {
      return result_iterator_opts_table (autofp, apply_overrides,
                                         setting_dynamic_severity_int ());
    }
  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)
{
  gchar *success_text;
  int ret;

  g_info ("   Optimizing: %s.\n", name);

  if (name == NULL)
    {
      fprintf (stderr, "Name required for optimize.\n");
      return 1;
    }

  ret = manage_option_setup (log_config, database);
  if (ret)
    return ret;

  ret = 0;
  if (strcasecmp (name, "vacuum") == 0)
    {
      const gchar *db;
      struct stat state;
      long long int old_size, new_size;

      db = database ? database : sql_default_database ();
      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 removed, fixed_values;
      sql ("DELETE FROM config_preferences WHERE id NOT IN"
           " (SELECT min(id) FROM config_preferences"
           "  GROUP BY config, type, name, value);");
      removed = sql_changes();

      sql ("UPDATE config_preferences"
           " SET value = (SELECT value FROM nvt_preferences"
           "              WHERE name='scanner_plugins_timeout')"
           " WHERE name = 'scanner_plugins_timeout'"
           "   AND value = 'SCANNER_NVT_TIMEOUT';");
      fixed_values = sql_changes();

      success_text = g_strdup_printf ("Optimized: cleanup-config-prefs."
                                      " Duplicate config preferences removed:"
                                      " %d. Corrected preference values: %d",
                                      removed, fixed_values);
    }
  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, "cleanup-result-severities") == 0)
    {
      int missing_severity_changes = 0;
      sql_begin_exclusive();

      if (sql_is_sqlite3 ())
        sql ("UPDATE results"
            " SET severity"
            "       = (SELECT value FROM settings"
            "           WHERE uuid = '7eda49c5-096c-4bef-b1ab-d080d87300df'"
            "             AND (settings.owner = results.owner"
            "                  OR settings.owner IS NULL)"
            "          ORDER BY settings.owner DESC)"
            " WHERE severity IS NULL"
            "    OR severity = '';");
      else
        sql ("UPDATE results"
            " SET severity"
            "       = (SELECT CAST (value AS real) FROM settings"
            "           WHERE uuid = '7eda49c5-096c-4bef-b1ab-d080d87300df'"
            "             AND (settings.owner = results.owner"
            "                  OR settings.owner IS NULL)"
            "          ORDER BY settings.owner DESC)"
            " WHERE severity IS NULL;");

      missing_severity_changes = sql_changes();
      sql_commit ();

      success_text = g_strdup_printf ("Optimized: cleanup-result-severities."
                                      " Missing severity scores added: %d.",
                                      missing_severity_changes);

    }
  else if (strcasecmp (name, "cleanup-schedule-times") == 0)
    {
      int changes;

      sql_begin_exclusive ();

      changes = cleanup_schedule_times ();

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: rebuild-report-cache."
                                      " Due date updated for %d tasks.",
                                      changes);
    }
  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"
                                      " reports.",
                                      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"
                                      " reports.",
                                      changes);
    }
  else
    {
      fprintf (stderr, "Error in optimize name.\n");
      ret = 1;
      success_text = NULL;
    }

  if (success_text)
    {
      printf ("%s\n", success_text);
      g_message ("   %s", success_text);
      g_free (success_text);
    }

  current_credentials.uuid = NULL;

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Cancels the current SQL statement.
 *
 * @return 0 on success, -1 on error.
 */
int
sql_cancel ()
{
  g_debug ("%s: cancelling current SQL statement", __FUNCTION__);
  return sql_cancel_internal ();
}
