/* Copyright (C) 2009-2022 Greenbone AG
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/**
 * @file
 * @brief The Greenbone Vulnerability Manager management library.
 */

#undef _XOPEN_SOURCE
/**
 * @brief Enable extra functions.
 *
 * For strptime in time.h.
 */
#define _XOPEN_SOURCE

/**
 * @brief Enable extra GNU functions.
 */
#define _GNU_SOURCE

#include <stdio.h>
#include "debug_utils.h"
#include "ipc.h"
#include "manage_osp.h"
#include "manage_sql.h"
#include "manage_alerts.h"
#include "manage_assets.h"
#include "manage_filters.h"
#include "manage_groups.h"
#include "manage_port_lists.h"
#include "manage_report_formats.h"
#include "manage_roles.h"
#include "manage_runtime_flags.h"
#include "manage_sql_credential_stores.h"
#include "manage_sql_copy.h"
#include "manage_sql_secinfo.h"
#include "manage_sql_nvts.h"
#include "manage_tickets.h"
#include "manage_scan_queue.h"
#include "manage_sql_alerts.h"
#include "manage_sql_assets.h"
#include "manage_sql_configs.h"
#include "manage_sql_filters.h"
#include "manage_sql_groups.h"
#include "manage_sql_oci_image_targets.h"
#include "manage_sql_port_lists.h"
#include "manage_sql_report_configs.h"
#include "manage_sql_report_formats.h"
#include "manage_sql_roles.h"
#include "manage_sql_tickets.h"
#include "manage_sql_tls_certificates.h"
#include "manage_sql_users.h"
#include "manage_acl.h"
#include "manage_commands.h"
#include "manage_authentication.h"
#include "manage_oci_image_targets.h"
#include "manage_users.h"
#include "lsc_user.h"
#include "sql.h"
#include "utils.h"
/* TODO This is for buffer_get_filter_xml, for print_report_xml_start.  We
 *      should not be generating XML in here, that should be done in gmp_*.c. */
#include "gmp_get.h"

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <glib/gstdio.h>
#include <gnutls/x509.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>
#include <grp.h>
#include <gpgme.h>
#include <stdlib.h>
#include <string.h>

#include <gvm/base/gvm_sentry.h>
#include <gvm/base/hosts.h>
#include <gvm/base/pwpolicy.h>
#include <gvm/base/logging.h>
#include <bsd/unistd.h>
#include <gvm/util/fileutils.h>
#include <gvm/util/serverutils.h>
#include <gvm/util/uuidutils.h>
#include <gvm/util/radiusutils.h>
#include <gvm/util/sshutils.h>
#include <gvm/util/authutils.h>
#include <gvm/util/ldaputils.h>
#include <gvm/gmp/gmp.h>
#include "manage_report_configs.h"
#include "manage_sql_agents.h"
#include "manage_sql_agent_groups.h"
#include "manage_sql_agent_installers.h"

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

/**
 * @brief Number of retries for
 *        LOCK TABLE .. IN ACCESS EXLUSIVE MODE NOWAIT
 *        statements.
 */
#define LOCK_RETRIES 64

/**
 * @brief Timeout for trying to acquire a lock in milliseconds.
 */
#define LOCK_TIMEOUT 500

#ifdef DEBUG_FUNCTION_NAMES
#include <dlfcn.h>

void __cyg_profile_func_enter (void *, void *)
   __attribute__((no_instrument_function));

void
__cyg_profile_func_enter (void *func, void *caller)
{
  Dl_info info;

  if (dladdr (func, &info))
      g_debug ("TTT: enter %p %s",
               (int*) func,
               info.dli_sname ? info.dli_sname : "?");
  else
      g_debug ("TTT: enter %p", (int*) func);
}

void __cyg_profile_func_exit (void *, void *)
   __attribute__((no_instrument_function));

void
__cyg_profile_func_exit (void *func, void *caller)
{
  Dl_info info;

  if (dladdr (func, &info))
      g_debug ("TTT: exit  %p %s",
               (int*) func,
               info.dli_sname ? info.dli_sname : "?");
  else
      g_debug ("TTT: exit  %p", (int*) func);
}
#endif

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

int
manage_create_sql_functions ();

void
create_tables ();

void
check_db_sequences ();

int
check_db_extensions ();

static int
check_db_encryption_key ();

void
manage_attach_databases ();


/* 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.
 */
extern int authenticate_allow_all;

int delete_reports (task_t);

int
stop_task_internal (task_t);


/* Static headers. */

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

static char*
task_owner_uuid (task_t);

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

static resource_t
permission_resource (permission_t);

static resource_t
permission_subject (permission_t);

static char *
permission_subject_type (permission_t);

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 gchar*
vulns_extra_where (int);

static gchar*
vuln_iterator_opts_from_filter (const gchar *);

static gchar*
vuln_iterator_extra_with_from_filter (const gchar *);

static gboolean
find_trash_task (const char*, task_t*);

static gboolean
find_trash_report_with_permission (const char *, report_t *, const char *);

static int
cleanup_schedule_times ();

static char *
permission_name (permission_t);

static void
cache_permissions_for_resource (const char *, resource_t, GArray*);

static void
report_cache_counts (report_t, int, int, const char*);

static gchar *
reports_extra_where (int, const gchar *, const char *);

static int
set_credential_data (credential_t, const char*, const char*);

static void
set_credential_name (credential_t, const char *);

static void
set_credential_comment (credential_t, const char *);

static void
set_credential_login (credential_t, const char *);

static void
set_credential_certificate (credential_t, const char *);

static void
set_credential_auth_algorithm (credential_t, const char *);

static void
set_credential_private_key (credential_t, const char *, const char *);

static void
set_credential_password (credential_t, const char *);

static void
set_credential_snmp_secret (credential_t, const char *, const char *,
                            const char *);

static int
setting_auto_cache_rebuild_int ();

static char *
setting_timezone ();

static char*
target_comment (target_t);

static column_t *
type_select_columns (const char *type);

static column_t *
type_where_columns (const char *type);

static char*
trash_target_comment (target_t);

static const char**
type_filter_columns (const char *);

static int
type_build_select (const char *, const char *, const get_data_t *,
                   gboolean, gboolean, const char *, const char *,
                   const char *, gchar **);


/* Variables. */

/**
 * @brief Function to fork a connection that will accept GMP requests.
 */
manage_connection_forker_t manage_fork_connection;

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

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

/**
 * @brief Name of the database file.
 */
db_conn_info_t gvmd_db_conn_info = { NULL, NULL, NULL, NULL, 60 };

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

/**
 * @brief The VT verification collation override
 */
static gchar *vt_verification_collation = NULL;


/* 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.
 *
 * @return Whether resource with name exists.
 */
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.
 *
 * @return Whether resource with name exists.
 */
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 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 successfully failed to find
 *                        resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
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 ISO time into seconds since epoch.
 *
 * If no offset is specified, the timezone of the current user is used.
 * If there is no current user timezone, UTC is used.
 *
 * @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)
{
  return parse_iso_time_tz (text_time, current_credentials.timezone);
}

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

      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.
 */
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 Get last time NVT alerts were checked.
 *
 * @return Last check time.
 */
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.
 *
 * @return Last time SCAP was checked.
 */
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.
 *
 * @return Last time CERT was checked.
 */
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.
 * @param[in]  avoid_db_check_inserts  Whether to avoid inserts in DB check.
 *
 * @return 0 success, -1 error, -2 database is too old,
 *         -3 database needs to be initialised from server,
 *         -5 database is too new.
 */
int
manage_option_setup (GSList *log_config, const db_conn_info_t *database,
                     int avoid_db_check_inserts)
{
  int ret;

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

  ret = init_manage_helper (log_config, database,
                            MANAGE_ABSOLUTE_MAX_IPS_PER_TARGET,
                            avoid_db_check_inserts);
  assert (ret != -4);
  switch (ret)
    {
      case 0:
        break;
      case -2:
        fprintf (stderr, "Database is wrong version.\n");
        fprintf (stderr, "Your database is too old for this version of gvmd.\n");
        fprintf (stderr, "Please migrate to the current data model.\n");
        fprintf (stderr, "Use a command like this: gvmd --migrate\n");
        return ret;
      case -5:
        fprintf (stderr, "Database is wrong version.\n");
        fprintf (stderr, "Your database is too new for this version of gvmd.\n");
        fprintf (stderr, "Please upgrade gvmd to a newer version.\n");
        return ret;
      case -3:
        fprintf (stderr, "Database must be initialised.\n");
        return ret;
      default:
        fprintf (stderr, "Internal error.\n");
        return ret;
    }

  init_manage_process (database);

  return 0;
}

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

/**
 * @brief Copy an array of columns.
 *
 * @param[in]  columns  Columns.
 *
 * @return Freshly allocated array.
 */
static 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", __func__, 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.
 */
static void
column_array_free (column_t *columns)
{
  column_t *point = columns;
  while (point->filter)
    {
      g_free (point->select);
      g_free (point->filter);
      point++;
    }
  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.
 */
static 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++;
    }
}


/* Resources. */

/* TODO Only used by find_scanner, find_permission and check_permission_args. */
/**
 * @brief Find a resource given a UUID.
 *
 * This only looks for resources owned (or effectively owned) by the current user.
 * So no shared resources and no globals.
 *
 * @param[in]   type       Type of resource.
 * @param[in]   uuid       UUID of resource.
 * @param[out]  resource   Resource return, 0 if successfully 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.
 *
 * @param[in]   type       Type of resource.
 * @param[in]   uuid       UUID of resource.
 * @param[out]  resource   Resource return, 0 if successfully failed to find resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on error.
 */
gboolean
find_resource_no_acl (const char* type, const char* uuid, resource_t* resource)
{
  gchar *quoted_uuid;
  quoted_uuid = sql_quote (uuid);

  // 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 successfully 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_db_resource_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%s;",
                     type,
                     (trash && strcmp (type, "task") && strcmp (type, "report"))
                      ? "_trash"
                      : "",
                     quoted_uuid,
                     strcmp (type, "task")
                      ? ""
                      : (trash ? " AND hidden = 2" : " AND hidden < 2"),
                     strcmp (type, "report")
                      ? ""
                      : (trash
                          ? " AND (SELECT hidden FROM tasks"
                            "      WHERE tasks.id = task)"
                            "     = 2"
                          : " AND (SELECT hidden FROM tasks"
                          "        WHERE tasks.id = task)"
                          "       = 0")))
    {
      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 successfully failed to find
 *                        resource.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
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 successfully 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.
 */
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, globally_unique;
  user_t owner;
  resource_t resource;
  resource_t new;
  int ret = -1;

  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);
  globally_unique = type_globally_unique (type);

  if (named && name && *name && resource_with_name_exists (name, type, 0))
    return 1;

  if ((strcmp (type, "tls_certificate") == 0)
      && user_has_tls_certificate (resource, owner))
    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 (globally_unique && make_name_unique)
    uniquify = g_strdup_printf ("uniquify ('%s', name, NULL, '%cClone')",
                                type,
                                strcmp (type, "user") ? ' ' : '_');
  else if (make_name_unique)
    uniquify = g_strdup_printf ("uniquify ('%s', name, %llu, ' Clone')",
                                type,
                                owner);
  else
    uniquify = g_strdup ("name");
  if (named && comment && strlen (comment))
    {
      gchar *quoted_comment;
      quoted_comment = sql_nquote (comment, strlen (comment));
      ret = sql_error ("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)
    ret = sql_error ("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
    ret = sql_error ("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);

  if (ret == 3)
    {
      g_free (quoted_uuid);
      g_free (quoted_name);
      g_free (uniquify);
      return 1;
    }
  else if (ret)
    {
      g_free (quoted_uuid);
      g_free (quoted_name);
      g_free (uniquify);
      return -1;
    }

  new = sql_last_insert_id ();

  /* Copy attached tags */
  sql ("INSERT INTO tag_resources"
       " (tag, resource_type, resource, resource_uuid, resource_location)"
       " SELECT tag, resource_type, %llu,"
       "        (SELECT uuid FROM %ss WHERE id = %llu),"
       "        resource_location"
       "   FROM tag_resources"
       "  WHERE resource_type = '%s' AND resource = %llu"
       "    AND resource_location = " G_STRINGIFY (LOCATION_TABLE) ";",
       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.
 * @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.
 */
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,
               resource_t *old_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, old_resource);

  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;

  GString *query = g_string_new ("");

  if (strcasecmp (type, "note") == 0)
    {
      g_string_printf (query,
                       "SELECT 'Note for: '"
                       " || (SELECT name"
                       "     FROM nvts"
                       "     WHERE nvts.uuid = tnotes.nvt)"
                       " FROM notes%s AS tnotes"
                       " WHERE uuid = $1;",
                       location == LOCATION_TABLE ? "" : "_trash");

      *name = sql_string_ps (query->str, SQL_STR_PARAM (uuid), NULL);
    }
  else if (strcasecmp (type, "override") == 0)
    {
      g_string_printf (query,
                       "SELECT 'Override for: '"
                       " || (SELECT name"
                       "     FROM nvts"
                       "     WHERE nvts.uuid = tovrr.nvt)"
                       " FROM overrides%s AS tovrr"
                       " WHERE uuid = $1;",
                       location == LOCATION_TABLE ? "" : "_trash");

      *name = sql_string_ps (query->str, SQL_STR_PARAM (uuid), NULL);
    }
  else if (strcasecmp (type, "report") == 0)
    {
      *name = sql_string_ps ("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 = $1;",
                             SQL_STR_PARAM (uuid), NULL);
    }
  else if (strcasecmp (type, "result") == 0)
    {
      *name = sql_string_ps ("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 = $1;",
                             SQL_STR_PARAM (uuid), NULL);
    }
  else if (location == LOCATION_TABLE)
    {
      g_string_printf (query,
                       "SELECT name"
                       " FROM %ss"
                       " WHERE uuid = $1;",
                       type);
      *name = sql_string_ps (query->str, SQL_STR_PARAM (uuid), NULL);
    }
  else if (type_has_trash (type))
    {
      g_string_printf (query,
                       "SELECT name"
                       " FROM %ss%s"
                       " WHERE uuid = $1;",
                       type, strcmp (type, "task") ? "_trash" : "");

      *name = sql_string_ps (query->str, SQL_STR_PARAM (uuid), NULL);
    }
  else
    *name = NULL;

  g_string_free (query, TRUE);
  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 Check if a resource has been marked as deprecated.
 *
 * @param[in]  type         Resource type.
 * @param[in]  resource_id  UUID of the resource.
 *
 * @return 1 if deprecated, else 0.
 */
int
resource_id_deprecated (const char *type, const char *resource_id)
{
  int ret;
  gchar *quoted_type = sql_quote (type);
  gchar *quoted_uuid = sql_quote (resource_id);

  ret = sql_int ("SELECT count(*) FROM deprecated_feed_data"
                 " WHERE type = '%s' AND uuid = '%s';",
                 quoted_type, quoted_uuid);

  g_free (quoted_type);
  g_free (quoted_uuid);

  return ret != 0;
}

/**
 * @brief Mark whether resource is deprecated.
 *
 * @param[in]  type         Resource type.
 * @param[in]  resource_id  UUID of the resource.
 * @param[in]  deprecated   Whether the resource is deprecated.
 */
void
set_resource_id_deprecated (const char *type, const char *resource_id,
                            gboolean deprecated)
{
  gchar *quoted_type = sql_quote (type);
  gchar *quoted_uuid = sql_quote (resource_id);

  if (deprecated)
    {
      sql ("INSERT INTO deprecated_feed_data (type, uuid, modification_time)"
           " VALUES ('%s', '%s', m_now ())"
           " ON CONFLICT (uuid, type)"
           " DO UPDATE SET modification_time = m_now ()",
           quoted_type, quoted_uuid);
    }
  else
    {
      sql ("DELETE FROM deprecated_feed_data"
           " WHERE type = '%s' AND uuid = '%s'",
           quoted_type, quoted_uuid);
    }
  g_free (quoted_type);
  g_free (quoted_uuid);
}

/**
 * @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 extra_with 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]  extra_where_single  Extra WHERE clauses.  Used 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]  extra_with      Extra WITH clauses.
 * @param[in]  acl_with_optional  Whether default permission WITH clauses are
 *                                 optional.
 * @param[in]  assume_permitted   Whether to skip permission checks.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1
 *         error.
 */
int
init_get_iterator2_with (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,
                         const char *extra_where_single, int owned,
                         int ignore_id,
                         const char *extra_order,
                         const char *extra_with,
                         int acl_with_optional,
                         int assume_permitted)
{
  int first, max;
  gchar *clause, *order, *filter, *owned_clause, *with_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 == 0 || (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 (type_is_asset_subtype (type))
        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, FILT_ID_NONE))
    {
      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, get->ignore_max_rows_per_page,
                          &order, &first, &max, &permissions, &owner_filter);

  g_free (filter);

  with_clause = NULL;

  if (resource || assume_permitted)
    /* Ownership test of single resources is done above by find function
     * but acl_where_owned has to be called to generate WITH clause
     * in case subqueries depend on it.
     */
    owned_clause = acl_where_owned (type, get, 0, owner_filter, resource,
                                    permissions, acl_with_optional,
                                    &with_clause);
  else
    owned_clause = acl_where_owned (type, get, owned, owner_filter, resource,
                                    permissions, acl_with_optional,
                                    &with_clause);

  if (extra_with)
    {
      if (with_clause)
        {
          gchar *old_with;

          old_with = with_clause;
          with_clause = g_strdup_printf ("%s, %s", old_with, extra_with);
          g_free (old_with);
        }
      else
        with_clause = g_strdup_printf ("WITH %s", extra_with);
    }

  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 %ss%s.id = %llu"
                   " AND %s%s"
                   "%s%s;",
                   with_clause ? with_clause : "",
                   columns,
                   type,
                   type_trash_in_table (type) ? "" : "_trash",
                   extra_tables ? extra_tables : "",
                   type,
                   type_trash_in_table (type) ? "" : "_trash",
                   resource,
                   owned_clause,
                   extra_where_single ? extra_where_single : "",
                   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;",
                   with_clause ? with_clause : "",
                   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 %ss.id = %llu"
                   " AND %s%s"
                   "%s%s;",
                   with_clause ? with_clause : "",
                   columns,
                   type,
                   extra_tables ? extra_tables : "",
                   type,
                   resource,
                   owned_clause,
                   extra_where_single ? extra_where_single : "",
                   order ? order : "",
                   order ? (extra_order ? extra_order : "") : "");
  else
    init_iterator (iterator,
                   "%s%sSELECT %s"
                   " FROM %ss %s"
                   " WHERE"
                   " %s%s%s%s%s%s%s"
                   " LIMIT %s OFFSET %i%s;",
                   with_clause ? with_clause : "",
                   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 (with_clause);
  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]  extra_where_single  Extra WHERE clauses.  Used 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.
 */
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, const char *extra_where_single,
                    int owned, int ignore_id,
                    const char *extra_order)
{
  return init_get_iterator2_with (iterator, type, get, select_columns,
                                 trash_select_columns, where_columns,
                                 trash_where_columns, filter_columns, distinct,
                                 extra_tables, extra_where, extra_where_single,
                                 owned, ignore_id, extra_order, NULL, 0, 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.
 */
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, NULL, owned,
                             FALSE, NULL);
}

/**
 * @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]  given_extra_where  Extra WHERE clauses.  Skipped for single
 *                                resource.
 *
 * @return 0 success, 1 failed to find resource, 2 failed to find filter,
 *         3 invalid data_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 *given_extra_where)
{
  column_t *select_columns, *where_columns;
  const char **filter_columns;
  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;
  gchar *inner_select;
  int build_select_ret;

  assert (get);

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

  if ((manage_scap_loaded () == FALSE
       && (strcmp (type, "cve") == 0
           || strcmp (type, "cpe") == 0))
      || (manage_cert_loaded () == FALSE
          && (strcmp (type, "cert_bund_adv") == 0
              || strcmp (type, "dfn_cert_adv") == 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;
    }

  select_columns = type_select_columns (type);
  where_columns = type_where_columns (type);
  filter_columns = type_filter_columns (type);

  if (filter_columns == NULL)
    return 5;
  if (get->trash && type_has_trash (type) == 0)
    return 6;

  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)
            {
              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)
            {
              return 7;
            }
        }
    }
  if (group_column && vector_find_filter (filter_columns, group_column) == 0)
    {
      return 4;
    }
  if (subgroup_column
      && vector_find_filter (filter_columns, subgroup_column) == 0)
    {
      return 8;
    }
  select_data_columns = g_array_new (TRUE, TRUE, sizeof (gchar*));
  select_text_columns = g_array_new (TRUE, TRUE, sizeof (gchar*));

  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))
    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))
    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;
                 data_columns && 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_sum_%d)"
                                                      " / sum(aggregate_count)",
                                                      index);
                    else
                      order_column = g_strdup_printf ("%s (aggregate_%s_%d)",
                                                      sort_stat, sort_stat,
                                                      index);
                  }
              }

            for (index = 0;
                text_columns && 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 ("%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 (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 = NULL;
    }

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

  inner_select = NULL;
  build_select_ret = type_build_select (type, aggregate_select->str, get,
                                        distinct, 0, extra_tables,
                                        given_extra_where, aggregate_group_by,
                                        &inner_select);

  if (build_select_ret == 0)
    {
      init_iterator (iterator,
                    "SELECT sum(aggregate_count) AS outer_count,"
                    " %s AS outer_group_column,"
                    " %s AS outer_subgroup_column"
                    " %s"
                    " FROM (%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,
                    inner_select,
                    order_clause->str,
                    sql_select_limit (max_groups),
                    first_group);
    }

  g_string_free (order_clause, TRUE);
  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);
  g_free (inner_select);

  switch (build_select_ret)
    {
      case 0:
        break;
      case 1:
        return 2;
      default:
        return -1;
    }

  return 0;
}

/**
 * @brief Offset for aggregate iterator.
 */
#define AGGREGATE_ITERATOR_OFFSET 3

/**
 * @brief Number of stats, for aggregate iterator.
 */
#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 iterator_int (iterator, 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 iterator_double (iterator,
                          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 iterator_double (iterator,
                          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 iterator_double (iterator,
                          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 iterator_double (iterator,
                          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*) iterator_string (iterator,
                                       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*) iterator_string (iterator, 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*) iterator_string (iterator, 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]  extra_with        Extra WITH clauses.
 * @param[in]  owned             Only count items owned by current user.
 *
 * @return Total number of resources in filtered set.
 */
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,
        const char *extra_with, int owned)
{
  int ret;
  gchar *clause, *owned_clause, *owner_filter, *columns, *filter, *with;
  array_t *permissions;

  assert (get);

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

  g_debug ("%s", __func__);

  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, get->ignore_max_rows_per_page,
                          NULL, NULL, NULL, &permissions,
                          &owner_filter);

  g_free (filter);

  owned_clause = acl_where_owned (type, get, owned, owner_filter, 0,
                                  permissions, 0, &with);

  if (extra_with)
    {
      if (with)
        {
          gchar *old_with;

          old_with = with;
          with = g_strdup_printf ("%s, %s", old_with, extra_with);
          g_free (old_with);
        }
      else
        with = g_strdup_printf ("WITH %s", extra_with);
    }

  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 ("%sSELECT count (*) FROM %ss%s;",
                   with ? with : "", type,
                   get->trash && strcmp (type, "task") ? "_trash" : "");
  else
    ret = sql_int ("%sSELECT count (%scount_id)"
                   " FROM (SELECT %ss%s.id AS count_id"
                   "       FROM %ss%s%s"
                   "       WHERE %s"
                   "       %s%s%s%s) AS subquery;",
                   with ? with : "",
                   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 (with);
  g_free (columns);
  g_free (owned_clause);
  g_free (clause);

  g_debug ("%s: done", __func__);

  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.
 */
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, NULL,
                 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 the database version supported by this manager.
 *
 * @return Database version supported by this manager.
 */
int
manage_db_supported_version ()
{
  return GVMD_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 GVMD_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 GVMD_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 Set the database version of the actual database.
 *
 * Caller must organise transaction.
 *
 * @param  version  New version number.
 */
void
set_db_version (int version)
{
  sql ("INSERT INTO %s.meta (name, value)"
       " VALUES ('database_version', '%i')"
       " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;",
       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'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'community'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'privacy_password')"
                 " FROM credentials");

  char *encryption_key_uid = current_encryption_key_uid (TRUE);
  iterator.crypt_ctx = lsc_crypt_new (encryption_key_uid);
  free (encryption_key_uid);

  sql_begin_immediate ();

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

      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);
      community        = iterator_string (&iterator, 4);
      privacy_password = iterator_string (&iterator, 5);

      /* If there is no secret, password or private key, skip the row.  */
      if (!secret && !password && !privkey && !privacy_password)
        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);
          community        = lsc_crypt_decrypt (iterator.crypt_ctx,
                                                secret, "community");
          privacy_password = lsc_crypt_decrypt (iterator.crypt_ctx,
                                                secret, "privacy_password");

          /* If there is none of the expected secrets, skip the row.  */
          if (!password && !privkey && !community && !privacy_password)
            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, "community", community);
          set_credential_data (rowid, "privacy_password", privacy_password);
          set_credential_data (rowid, "secret", NULL);
          sql ("UPDATE credentials SET"
               " modification_time = m_now ()"
               " WHERE id = %llu;", rowid);
          ndecrypted++;
        }
      else
        {
          GHashTable *plaintext_hashtable;
          char *encblob;

          plaintext_hashtable
            = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);

          if (password)
            g_hash_table_insert (plaintext_hashtable,
                                 "password",
                                 g_strdup (password));
          if (privkey)
            g_hash_table_insert (plaintext_hashtable,
                                 "private_key",
                                 g_strdup (privkey));
          if (community)
            g_hash_table_insert (plaintext_hashtable,
                                 "community",
                                 g_strdup (community));
          if (privacy_password)
            g_hash_table_insert (plaintext_hashtable,
                                 "privacy_password",
                                 g_strdup (privacy_password));

          encblob = lsc_crypt_encrypt_hashtable (iterator.crypt_ctx,
                                                 plaintext_hashtable);
          g_hash_table_destroy (plaintext_hashtable);

          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, "community", NULL);
          set_credential_data (rowid, "privacy_password", 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, re-encrypt or decrypt all auth settings.
 *
 * All plaintext settings in the meta table are
 * encrypted, all already encrypted settings are encrypted again
 * using the latest key.
 *
 * @param[in] decrypt_flag  If true decrypt all settings.
 *
 * @return 0 success, -1 error.
 */
static int
encrypt_all_auth_settings (gboolean decrypt_flag)
{
  unsigned long ntotal, nencrypted, nreencrypted, ndecrypted;
  char *radius_key = NULL;
  gboolean radius_key_encrypted;
  ntotal = ndecrypted = nencrypted = nreencrypted = 0;

  sql_begin_immediate ();

  radius_key = get_radius_key (&radius_key_encrypted);
  if (radius_key && strcmp (radius_key, ""))
    {
      ntotal ++;

      if (decrypt_flag)
        {
          if (radius_key_encrypted)
            ndecrypted ++;
        }
      else
        {
          if (radius_key_encrypted)
            nreencrypted ++;
          else
            nencrypted ++;
        }

      set_radius_key (radius_key, decrypt_flag == FALSE);
    }
  free (radius_key);

  sql_commit ();

  if (ntotal)
    {
      if (decrypt_flag)
        g_message ("%lu out of %lu auth settings decrypted",
                  ndecrypted, ntotal);
      else
        g_message ("%lu out of %lu auth settings encrypted and %lu re-encrypted",
                  nencrypted, ntotal, nreencrypted);
    }
  else
    g_message ("No auth settings to encrypt or decrypt");

  return 0;
}

/**
 * @brief Encrypt or re-encrypt all credentials and auth settings
 *
 * All plaintext credentials and auth settings are
 * encrypted, all already encrypted credentials and auth settings
 * 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 too old, -3 database needs to be initialised
 *         from server, -5 database is too new.
 */
int
manage_encrypt_all_credentials (GSList *log_config,
                                const db_conn_info_t *database)
{
  int ret;

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

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return ret;

  ret = encrypt_all_credentials (FALSE);
  if (ret)
    {
      printf ("Encryption failed.\n");
      manage_option_cleanup ();
      return ret;
    }

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

  manage_option_cleanup ();

  return ret;
}

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

  g_info ("   Decrypting all credentials.");

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return ret;

  ret = encrypt_all_credentials (TRUE);
  if (ret)
    {
      printf ("Decryption failed.\n");
      manage_option_cleanup ();
      return ret;
    }

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

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Gets the UID of the currently configured encryption key.
 *
 * @param[in]  with_fallback  If TRUE, set and return old key UID if
 *                            the key UID is undefined.
 *
 * @return The encryption key UID.
 */
char *
current_encryption_key_uid (gboolean with_fallback)
{
  char *key_uid = sql_string ("SELECT value FROM meta"
                              " WHERE name = 'encryption_key_uid';");

  if (key_uid)
    return key_uid;

  if (!with_fallback)
    return NULL;

  // Check if an old, fixed UID key exists
  lsc_crypt_ctx_t ctx = lsc_crypt_new (OLD_ENCRYPTION_KEY_UID);
  if (lsc_crypt_enckey_exists (ctx))
    {
      lsc_crypt_flush(ctx);
      set_current_encryption_key_uid (OLD_ENCRYPTION_KEY_UID);
      return strdup (OLD_ENCRYPTION_KEY_UID);
    }
  lsc_crypt_flush(ctx);

  // Generate a new key UID
  time_t now = time(NULL);
  gchar *generated_uid
    = g_strdup_printf (ENCRYPTION_KEY_UID_TEMPLATE, iso_time (&now));
  set_current_encryption_key_uid (generated_uid);
  key_uid = strdup (generated_uid);
  g_free (generated_uid);
  return key_uid;
}


/**
 * @brief Sets the database field defining the encryption key UID.
 *
 * Note: This does not have any effects on any already created
 *       encryption contexts that may be using the old UID.
 *
 * @param[in]  new_uid  The new UID to set.
 */
void
set_current_encryption_key_uid (const char *new_uid)
{
  gchar *quoted_new_uid = sql_quote (new_uid);

  sql ("INSERT INTO meta (name, value)"
       " VALUES ('encryption_key_uid', '%s')"
       " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;",
       quoted_new_uid);

  g_free (quoted_new_uid);
}

/**
 * @brief Validates sort_field for nvts table
 *
 * @return 0 success, 1 invalid
 */
int
validate_sort_field (const gchar *table, const gchar *sort_field)
{
  return !db_table_has_column ("public", table, sort_field);
}


/* Task subject iterators. */

/**
 * @brief Initialise a task user iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  task      Task.
 */
static 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);
}

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


/* Task functions. */

/**
 * @brief  Generate an extra WHERE clause for selecting tasks
 *
 * @param[in]  trash        Whether to get tasks from the trashcan.
 * @param[in]  usage_type   The usage type to limit the selection to.
 *
 * @return Newly allocated where clause string.
 */
static gchar *
tasks_extra_where (int trash, const char *usage_type)
{
  gchar *extra_where = NULL;
  if (usage_type && strcmp (usage_type, ""))
    {
      gchar *quoted_usage_type;
      quoted_usage_type = sql_quote (usage_type);
      extra_where = g_strdup_printf (" AND hidden = %d"
                                     " AND usage_type = '%s'",
                                     trash ? 2 : 0,
                                     quoted_usage_type);
      g_free (quoted_usage_type);
    }
  else
    extra_where = g_strdup_printf (" AND hidden = %d",
                                   trash ? 2 : 0);

  return extra_where;
}

/**
 * @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 Severity columns for TASK_ITERATOR_FILTER_COLUMNS.
 */
#define SEVERITY_FILTER_COLUMNS \
  "false_positive", "log", "low", "medium", "high", "critical"

/**
 * @brief Agent group columns for TASK_ITERATOR_FILTER_COLUMNS.
 */
#if ENABLE_AGENTS
  #define TASK_AGENT_GROUP_FILTER_COLUMNS "agent_group_id", "agent_group",
#else
  #define TASK_AGENT_GROUP_FILTER_COLUMNS
#endif

/**
 * @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", SEVERITY_FILTER_COLUMNS, "hosts", "result_hosts",       \
    "fp_per_host", "log_per_host", "low_per_host", "medium_per_host",        \
    "high_per_host", "critical_per_host", "target", "usage_type",            \
    "first_report_created", "last_report_created", "scanner_type",           \
    TASK_AGENT_GROUP_FILTER_COLUMNS                                          \
    NULL}

/**
 * @brief Group columns for TASK_ITERATOR_WHERE_COLUMNS_INNER.
 */
#if ENABLE_AGENTS
  #define TASK_AGENT_GROUP_ITERATOR_COLUMNS                                   \
    ,{                                                                        \
      "(SELECT uuid FROM agent_groups"                                        \
      " WHERE agent_groups.id = tasks.agent_group)",                          \
      "agent_group_id",                                                       \
      KEYWORD_TYPE_STRING                                                     \
    },                                                                        \
    {                                                                         \
      "(SELECT name FROM agent_groups"                                        \
      " WHERE agent_groups.id = tasks.agent_group)",                          \
      "agent_group",                                                          \
      KEYWORD_TYPE_STRING                                                     \
    }
#else
  #define TASK_AGENT_GROUP_ITERATOR_COLUMNS
#endif

/**
 * @brief Query for TASK_SEV_CASE_GUARD.
 */
#if ENABLE_AGENTS && ENABLE_CONTAINER_SCANNING
  #define TASK_NO_TARGET_CTX "(target IS NULL AND agent_group IS NULL AND oci_image_target IS NULL)"
#elif ENABLE_AGENTS && !ENABLE_CONTAINER_SCANNING
  #define TASK_NO_TARGET_CTX "(target IS NULL AND agent_group IS NULL)"
#elif !ENABLE_AGENTS && ENABLE_CONTAINER_SCANNING
  #define TASK_NO_TARGET_CTX "(target IS NULL AND oci_image_target IS NULL)"
#else
  #define TASK_NO_TARGET_CTX "(target IS NULL)"
#endif

/**
 * @brief SQL for TASK_ITERATOR_WHERE_COLUMNS_INNER.
 */
#define TASK_SEV_CASE_GUARD "(" TASK_NO_TARGET_CTX " OR opts.ignore_severity != 0)"

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

/**
 * @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 creation_time FROM reports WHERE task = tasks.id"              \
     /* TODO 1 == TASK_STATUS_DONE */                                        \
     " AND scan_run_status = 1"                                              \
     " ORDER BY creation_time ASC LIMIT 1)",                                 \
     "first",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT creation_time FROM reports WHERE task = tasks.id"              \
     /* TODO 1 == TASK_STATUS_DONE */                                        \
     " AND scan_run_status = 1"                                              \
     " ORDER BY creation_time DESC LIMIT 1)",                                \
     "last",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " 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 " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Log')"           \
     " END",                                                                 \
     "log",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Low')"           \
     " END",                                                                 \
     "low",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Medium')"        \
     " END",                                                                 \
     "medium",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'High')"          \
     " END",                                                                 \
     "high",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Critical')"      \
     " END",                                                                 \
     "critical",                                                             \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_host_count (task_last_report (id))"                            \
     " END",                                                                 \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " report_result_host_count (task_last_report (id), opts.min_qod)"       \
     " END",                                                                 \
     "result_hosts",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " 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 " TASK_SEV_CASE_GUARD " 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 " TASK_SEV_CASE_GUARD " 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 " TASK_SEV_CASE_GUARD " 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 " TASK_SEV_CASE_GUARD " 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                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN " TASK_SEV_CASE_GUARD " THEN 0 ELSE"                         \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Critical') * 1.0"                    \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "critical_per_host",                                                    \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT name FROM targets WHERE id = target)",                         \
     "target",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "(SELECT creation_time FROM reports WHERE task = tasks.id"              \
     /* TODO 1 == TASK_STATUS_DONE */                                        \
     " AND scan_run_status = 1"                                              \
     " ORDER BY creation_time ASC LIMIT 1)",                                 \
     "first_report_created",                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT creation_time FROM reports WHERE task = tasks.id"              \
     /* TODO 1 == TASK_STATUS_DONE */                                        \
     " AND scan_run_status = 1"                                              \
     " ORDER BY creation_time DESC LIMIT 1)",                                \
     "last_report_created",                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT type FROM scanners WHERE scanners.id = tasks.scanner)",        \
     "scanner_type",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   } TASK_AGENT_GROUP_ITERATOR_COLUMNS

/**
 * @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.
 */
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, 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;
  int overrides, min_qod;
  const char *usage_type;
  gchar *extra_tables, *extra_where;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  overrides = filter_term_apply_overrides (filter ? filter : get->filter);
  min_qod = filter_term_min_qod (filter ? filter : get->filter);

  free (filter);

  extra_tables = task_iterator_opts_table (overrides, min_qod, 0);
  usage_type = get_data_get_extra (get, "usage_type");
  extra_where = tasks_extra_where (get->trash, usage_type);

  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,
                            extra_where,
                            NULL,
                            current_credentials.uuid ? TRUE : FALSE,
                            FALSE,
                            NULL);

  g_free (extra_tables);
  g_free (extra_where);
  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_INTERRUPTED;
  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 Get the UUID of task scanner from a task iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Task scanner if found, NULL otherwise.
 */
const char *
task_iterator_usage_type (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8);
}

/**
 * @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_QUEUED
         || status == TASK_STATUS_STOP_REQUESTED
         || status == TASK_STATUS_STOP_WAITING
         || status == TASK_STATUS_PROCESSING;
}

/**
 * @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: open db.
 *
 * @param[in]  database          Location of manage database.
 *
 * @return 1 if open already, else 0.
 */
static int
init_manage_open_db (const db_conn_info_t *database)
{
  if (sql_is_open ())
    return 1;

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

  /* Ensure the user session variables always exists. */
  sql ("SET SESSION \"gvmd.user.id\" = 0;");
  sql ("SET SESSION \"gvmd.tz_override\" = '';");

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

  return 0;
}

/**
 * @brief Initialize the manage library: define SQL functions.
 */
static void
init_manage_create_functions ()
{
  lockfile_t lockfile;

  /* Lock to avoid an error return from Postgres when multiple processes
   * create a function at the same time. */
  if (lockfile_lock (&lockfile, "gvm-create-functions"))
    abort ();
  if (manage_create_sql_functions ())
    {
      lockfile_unlock (&lockfile);
      g_warning ("%s: failed to create functions", __func__);
      abort ();
    }
  lockfile_unlock (&lockfile);
}

/**
 * @brief Initialize the manage library for a process.
 *
 * Open the SQL database, attach secondary databases, and define functions.
 *
 * @param[in]  database          Location of manage database.
 */
void
init_manage_process (const db_conn_info_t *database)
{
  if (reinit_semaphore_set())
    abort ();
  if (init_manage_open_db (database))
    return;
  init_manage_create_functions ();
}

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

/**
 * @brief Update the memory cache of NVTs.
 *
 * @param[in]  nvt  NVT.
 *
 * @return NVTi if found, else NULL.
 */
nvti_t *
lookup_nvti (const gchar *nvt)
{
  return nvtis_lookup (nvti_cache, nvt);
}

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

  nvtis_free (nvti_cache);

  nvti_cache = nvtis_new ();

  /* Because there are many NVTs and many refs it's slow to query the refs
   * for each NVT.  So this query gets the NVTs and their refs at the same
   * time.
   *
   * The NVT data is duplicated in the result of the query when there are
   * multiple refs for an NVT, but the loop below uses nvtis_lookup to
   * check if we've already seen the NVT.  This also means we don't have
   * to sort the data by NVT, which would make the query too slow. */
  init_iterator (&nvts,
                 "SELECT nvts.oid, vt_refs.type, vt_refs.ref_id,"
                 "       vt_refs.ref_text, nvts.qod"
                 " FROM nvts"
                 " LEFT OUTER JOIN vt_refs ON nvts.oid = vt_refs.vt_oid;");

  init_iterator (&prefs,
                 "SELECT pref_id, pref_nvt, pref_name, value"
                 " FROM nvt_preferences"
                 " WHERE NOT (pref_type = 'entry' AND pref_name = 'timeout')");

  while (next (&nvts))
    {
      nvti_t *nvti;

      nvti = nvtis_lookup (nvti_cache, iterator_string (&nvts, 0));
      if (nvti == NULL)
        {
          nvti = nvti_new ();
          nvti_set_oid (nvti, iterator_string (&nvts, 0));
          nvti_set_qod(nvti, iterator_string (&nvts, 4));

          nvtis_add (nvti_cache, nvti);

          while (next (&prefs))
            if (iterator_string (&prefs, 1)
                && (strcmp (iterator_string (&prefs, 1),
                            iterator_string (&nvts, 0))
                    == 0))
              nvti_add_pref (nvti,
                             nvtpref_new (iterator_int (&prefs, 0),
                                          iterator_string (&prefs, 2),
                                          iterator_string (&prefs, 3),
                                          NULL));
          iterator_rewind (&prefs);
        }

      if (iterator_null (&nvts, 2))
        /* No refs. */;
      else
        nvti_add_vtref (nvti,
                        vtref_new (iterator_string (&nvts, 1),
                                   iterator_string (&nvts, 2),
                                   iterator_string (&nvts, 3)));
    }

  cleanup_iterator (&nvts);
  cleanup_iterator (&prefs);

  malloc_trim (0);
}

/**
 * @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 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)
    {
      /* Create default scanner */
      if (feature_enabled (FEATURE_ID_OPENVASD_SCANNER))
        {
          sql ("INSERT INTO scanners"
               " (uuid, owner, name, host, port, type, ca_pub, credential,"
               "  creation_time, modification_time)"
               " VALUES ('" SCANNER_UUID_DEFAULT "', NULL, 'OpenVAS Default',"
               " 'localhost', 3000, %d, NULL, NULL, m_now (), m_now ());",
               SCANNER_TYPE_OPENVASD);
        }
      else
        {
          sql ("INSERT INTO scanners"
               " (uuid, owner, name, host, port, type, ca_pub, credential,"
               "  creation_time, modification_time)"
               " VALUES ('" SCANNER_UUID_DEFAULT "', NULL, 'OpenVAS Default',"
               " '%s', 0, %d, NULL, NULL, m_now (), m_now ());",
               OPENVAS_DEFAULT_SOCKET,
               SCANNER_TYPE_OPENVAS);
        }
    }
  else
    {
      /* Check existing default scanner against runtime feature flag */
      scanner_type_t sc_type = get_scanner_type_by_uuid (SCANNER_UUID_DEFAULT);

      if (feature_enabled (FEATURE_ID_OPENVASD_SCANNER))
        {
          /* Runtime: openvasd scanner should be used */
          if (sc_type != SCANNER_TYPE_OPENVASD)
            {
              g_warning ("The openvasd scanner feature is enabled, but the "
                         "default scanner with UUID %s uses a different type. "
                         "Not updating the scanner automatically; please adjust "
                         "the scanner configuration manually if needed.",
                         SCANNER_UUID_DEFAULT);
            }
        }
      else
        {
          /* Runtime: ospd-openvas scanner should be used */
          if (sc_type == SCANNER_TYPE_OPENVASD)
            {
              g_warning ("Default scanner with UUID %s is of type openvasd, "
                         "but the openvasd scanner feature is disabled. "
                         "Not updating the scanner automatically; please adjust "
                         "the scanner configuration manually if needed.",
                         SCANNER_UUID_DEFAULT);
            }
        }
    }

#if ENABLE_CONTAINER_SCANNING
  if (sql_int ("SELECT count(*) FROM scanners WHERE uuid = '%s';",
                SCANNER_UUID_CONTAINER_IMAGE_DEFAULT) == 0)
    {
      sql ("INSERT INTO scanners"
        " (uuid, owner, name, host, port, type, ca_pub, credential,"
        "  creation_time, modification_time)"
        " VALUES ('" SCANNER_UUID_CONTAINER_IMAGE_DEFAULT "', NULL, "
        " 'Container Image Default', 'localhost', 3000, %d, NULL, NULL,"
        " m_now (), m_now ());",
        SCANNER_TYPE_CONTAINER_IMAGE);
    }
#endif

  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 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 = '" SETTING_UUID_PREFERRED_LANG "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_PREFERRED_LANG "', 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 = '" SETTING_UUID_DYNAMIC_SEVERITY "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_DYNAMIC_SEVERITY "', NULL, 'Dynamic Severity',"
         "  'Whether to use dynamic severity scores by default.',"
         "  '0');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_AUTO_REFRESH "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_AUTO_REFRESH "', NULL, 'Auto-Refresh',"
         "  'The delay between automatic page refreshs in seconds.',"
         "  '0');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_FILE_DETAILS "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_FILE_DETAILS "', 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 = '" SETTING_UUID_FILE_LIST "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_FILE_LIST "', 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 = '" SETTING_UUID_FILE_REPORT "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_FILE_REPORT "', 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 = '" SETTING_UUID_DEFAULT_SEVERITY "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_DEFAULT_SEVERITY "', 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 = '" SETTING_UUID_AUTO_CACHE_REBUILD "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_AUTO_CACHE_REBUILD "', NULL,"
         "  'Auto Cache Rebuild',"
         "  'Whether to rebuild report caches on changes affecting severity.',"
         "  '1');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '9246a0f6-c6ad-44bc-86c2-557a527c8fb3'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('9246a0f6-c6ad-44bc-86c2-557a527c8fb3', NULL,"
         "  'Note/Override Excerpt Size',"
         "  'The maximum length of notes and override text shown in' ||"
         "  ' reports without enabling note/override details.',"
         "  '%d');",
         EXCERPT_SIZE_DEFAULT);

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_LSC_DEB_MAINTAINER "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_LSC_DEB_MAINTAINER "', NULL,"
         "  'Debian LSC Package Maintainer',"
         "  'Maintainer email address used in generated Debian LSC packages.',"
         "  '');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_FEED_IMPORT_ROLES "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_FEED_IMPORT_ROLES "', NULL,"
         "  'Feed Import Roles',"
         "  'Roles given access to new resources from feed.',"
         "  '" ROLE_UUID_ADMIN "," ROLE_UUID_USER "');");

  if (sql_int ("SELECT count(*) FROM settings"
               " WHERE uuid = '" SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD "', NULL,"
         "  'SecInfo SQL Buffer Threshold',"
         "  'Buffer size threshold in MiB for running buffered SQL statements'"
         "  || ' in SecInfo updates before the end of the file'"
         "  || ' being processed.',"
         "  '100' );");

  if (sql_int ("SELECT count(*) FROM settings"
              " WHERE uuid = '" SETTING_UUID_USER_INTERFACE_TIME_FORMAT "'"
              " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
          " VALUES"
          " ('" SETTING_UUID_USER_INTERFACE_TIME_FORMAT "', NULL,"
          "  'User Interface Time Format',"
          "  'Preferred time format to be used in client user interfaces.',"
          "  'system_default' );");

  if (sql_int ("SELECT count(*) FROM settings"
              " WHERE uuid = '" SETTING_UUID_USER_INTERFACE_DATE_FORMAT "'"
              " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
          " VALUES"
          " ('" SETTING_UUID_USER_INTERFACE_DATE_FORMAT "', NULL,"
          "  'User Interface Date Format',"
          "  'Preferred date format to be used in client user interfaces.',"
          "  'system_default' );");

  if (sql_int ("SELECT count(*) FROM settings"
              " WHERE uuid = '" SETTING_UUID_CVE_CPE_MATCHING_VERSION "'"
              " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
          " VALUES"
          " ('" SETTING_UUID_CVE_CPE_MATCHING_VERSION "', NULL,"
          "  'CVE-CPE Matching Version',"
          "  'Version of the CVE-CPE matching used in CVE scans.',"
          "  '0' );");
}

/**
 * @brief Add command permission to role.
 *
 * Caller must ensure args are SQL escaped.
 *
 * @param[in]  role_id     Role.
 * @param[in]  permission  Permission.
 */
static void
add_role_permission (const gchar *role_id, const gchar *permission)
{
  if (sql_int ("SELECT EXISTS (SELECT * FROM permissions"
               "               WHERE owner IS NULL"
               "               AND name = lower ('%s')"
               "               AND resource_type = ''"
               "               AND resource = 0"
               "               AND subject_type = 'role'"
               "               AND subject = (SELECT id FROM roles"
               "                              WHERE uuid = '%s'));",
               permission,
               role_id) == 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"
         " (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_id);
}

/**
 * @brief Add resource permission to role.
 *
 * Caller must ensure args are SQL escaped.
 *
 * @param[in]  role_id      Role ID.
 * @param[in]  permission   Permission.
 * @param[in]  type         Resource type.
 * @param[in]  resource_id  Resource ID.
 */
void
add_role_permission_resource (const gchar *role_id, const gchar *permission,
                              const gchar *type, const gchar *resource_id)
{
  if (sql_int ("SELECT EXISTS (SELECT * FROM permissions"
               "               WHERE owner IS NULL"
               "               AND name = lower ('%s')"
               "               AND resource_type = '%s'"
               "               AND resource = (SELECT id FROM %ss"
               "                               WHERE uuid = '%s')"
               "               AND subject_type = 'role'"
               "               AND subject = (SELECT id FROM roles"
               "                              WHERE uuid = '%s'));",
               permission,
               type,
               type,
               resource_id,
               role_id)
      == 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"
         " (make_uuid (), NULL, lower ('%s'), '', '%s',"
         "  (SELECT id FROM %ss WHERE uuid = '%s'), '%s',"
         "  " G_STRINGIFY (LOCATION_TABLE) ", 'role',"
         "  (SELECT id FROM roles WHERE uuid = '%s'),"
         "  " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());",
         permission,
         type,
         type,
         resource_id,
         resource_id,
         role_id);
}

/**
 * @brief Ensure that the databases are the right versions.
 *
 * @return 0 success, -1 error, -2 database is too old, -5 database is too new.
 */
static int
check_db_versions ()
{
  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 (database_version)
    {
      if (strcmp (database_version,
                  G_STRINGIFY (GVMD_DATABASE_VERSION)))
        {
          int existing;

          g_message ("%s: database version of database: %s",
                     __func__,
                     database_version);
          g_message ("%s: database version supported by manager: %s",
                     __func__,
                     G_STRINGIFY (GVMD_DATABASE_VERSION));

          existing = atoi (database_version);

          g_free (database_version);

          return GVMD_DATABASE_VERSION > existing ? -2 : -5;
        }
      g_free (database_version);
    }

  /* Check SCAP database version. */

  scap_db_version = manage_scap_db_version ();
  if (scap_db_version == -1)
    g_message ("No SCAP database found");
  else
    {
      int supported;

      supported = manage_scap_db_supported_version ();
      if (scap_db_version != supported)
        {
          g_message ("%s: database version of SCAP database: %i",
                     __func__,
                     scap_db_version);
          g_message ("%s: SCAP database version supported by manager: %s",
                     __func__,
                     G_STRINGIFY (GVMD_SCAP_DATABASE_VERSION));

          return supported > scap_db_version ? -2 : -5;
        }
    }

  /* Check CERT database version. */

  cert_db_version = manage_cert_db_version ();
  if (cert_db_version == -1)
    g_message ("No CERT database found");
  else
    {
      int supported;

      supported = manage_cert_db_supported_version ();
      if (cert_db_version != supported)
        {
          g_message ("%s: database version of CERT database: %i",
                     __func__,
                     cert_db_version);
          g_message ("%s: CERT database version supported by manager: %s",
                     __func__,
                     G_STRINGIFY (GVMD_CERT_DATABASE_VERSION));

          return supported > cert_db_version ? -2 : -5;
        }
    }
  return 0;
}

/**
 * @brief Ensures the sanity of nvts cache in DB.
 */
static void
check_db_nvt_selectors ()
{
  /* 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 = '" OID_PING_HOST "';")
      == 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_PING_HOST "', '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');");
    }
}

/**
 * @brief Add permissions for all global resources.
 *
 * @param[in]  role_uuid  UUID of role.
 */
static void
add_permissions_on_globals (const gchar *role_uuid)
{
  iterator_t scanners;

  /* Scanners are global when created from the command line. */
  init_iterator (&scanners,
                  "SELECT id, uuid FROM scanners WHERE owner is NULL;");
  while (next (&scanners))
    add_role_permission_resource (role_uuid, "GET_SCANNERS",
                                  "scanner",
                                  iterator_string (&scanners, 1));
  cleanup_iterator (&scanners);

  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_ADMIN);
  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_GUEST);
  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_INFO);
  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_MONITOR);
  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_USER);
  add_role_permission_resource (role_uuid, "GET_ROLES",
                                "role",
                                ROLE_UUID_OBSERVER);
}

/**
 * @brief Ensure the predefined permissions exists.
 */
static void
check_db_permissions ()
{
  command_t *command;

  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, "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, "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, "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)
    {
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_USER "');");
    }
  command = gmp_commands;
  while (command[0].name)
    {
      if (strstr (command[0].name, "DESCRIBE_AUTH") == NULL
          && strcmp (command[0].name, "GET_VERSION")
          && strcmp (command[0].name, "MODIFY_LICENSE")
          && 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)
    {
      /* Clean-up any remaining permissions. */
      sql ("DELETE FROM permissions WHERE subject_type = 'role'"
           " AND subject = (SELECT id FROM roles"
           "                WHERE uuid = '" ROLE_UUID_OBSERVER "');");
    }
  command = gmp_commands;
  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");


  add_permissions_on_globals (ROLE_UUID_ADMIN);
  add_permissions_on_globals (ROLE_UUID_GUEST);
  add_permissions_on_globals (ROLE_UUID_OBSERVER);
  add_permissions_on_globals (ROLE_UUID_USER);
}

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

#if ENABLE_CREDENTIAL_STORES
/**
 * @brief Create or update the CyberArk credential store if needed.
 *
 * @param owner  The owner if the credential store is newly created.
 */
static void
check_db_cyberark_credential_store (user_t owner)
{
  const char *current_version = "0.1";
  credential_store_preference_data_t *new_pref;
  credential_store_selector_data_t *new_selector;
  GList *preferences = NULL, *selectors = NULL;

  if (sql_int ("SELECT count(*) FROM credential_stores"
               " WHERE uuid = '%s' AND version = '%s'",
               CREDENTIAL_STORE_UUID_CYBERARK,
               current_version))
    return;

  new_pref = credential_store_preference_new
               ("app_id",
                FALSE, CREDENTIAL_STORE_PREFERENCE_TYPE_STRING,
                "", "", "", NULL);
  preferences = g_list_append (preferences, new_pref);

  new_pref = credential_store_preference_new
               ("client_key",
                TRUE, CREDENTIAL_STORE_PREFERENCE_TYPE_X509_PRIVKEY,
                "", "", "", "passphrase");
  preferences = g_list_append (preferences, new_pref);

  new_pref = credential_store_preference_new
               ("client_cert",
                FALSE, CREDENTIAL_STORE_PREFERENCE_TYPE_X509_CERTS,
                "", "", "", NULL);
  preferences = g_list_append (preferences, new_pref);

  new_pref = credential_store_preference_new
               ("server_ca_cert",
                FALSE, CREDENTIAL_STORE_PREFERENCE_TYPE_X509_CERTS,
                "", "", "", NULL);
  preferences = g_list_append (preferences, new_pref);

  new_pref = credential_store_preference_new
               ("client_pkcs12_file",
                TRUE, CREDENTIAL_STORE_PREFERENCE_TYPE_PKCS12_FILE,
                "", "", "", "passphrase");
  preferences = g_list_append (preferences, new_pref);

  new_pref = credential_store_preference_new
               ("passphrase",
                TRUE, CREDENTIAL_STORE_PREFERENCE_TYPE_STRING,
                "", "", "", NULL);
  preferences = g_list_append (preferences, new_pref);

  new_selector = credential_store_selector_new
                  ("username_password_object", "", "", 0);
  credential_store_selector_add_credential_type (new_selector, "cs-up");
  selectors = g_list_append (selectors, new_selector);

  create_or_update_credential_store (CREDENTIAL_STORE_UUID_CYBERARK,
                                     "CyberArk",
                                     "localhost",
                                     "api",
                                     current_version,
                                     preferences,
                                     selectors,
                                     owner);

  g_list_free_full (preferences,
                    (GDestroyNotify) credential_store_preference_free);
  g_list_free_full (selectors,
                    (GDestroyNotify) credential_store_selector_free);
}
#endif

/**
 * @brief Ensure the predefined credential stores exists.
 */
static void
check_db_credential_stores ()
{
#if ENABLE_CREDENTIAL_STORES
  gchar *feed_owner_uuid;
  user_t owner = 0;
  char *old_user_uuid, *old_username;

  setting_value (SETTING_UUID_FEED_IMPORT_OWNER, &feed_owner_uuid);

  if (feed_owner_uuid != NULL && strlen (feed_owner_uuid) > 0)
    {
      gchar *quoted_user_id = sql_quote (feed_owner_uuid);
      owner = sql_int64_0 ("SELECT id FROM users WHERE uuid = '%s'",
                           quoted_user_id);
      g_free (quoted_user_id);
    }
  if (owner == 0)
    {
      g_message ("%s: No feed owner set, skipping credential store creation",
               __func__);
      g_free (feed_owner_uuid);
      return;
    }

  old_user_uuid = current_credentials.uuid;
  old_username = current_credentials.username;
  current_credentials.uuid = feed_owner_uuid;
  current_credentials.username = user_name (feed_owner_uuid);

  check_db_cyberark_credential_store (owner);

  current_credentials.uuid = old_user_uuid;
  current_credentials.username = old_username;
  g_free (feed_owner_uuid);
#endif /* ENABLE_CREDENTIAL_STORES */
}

/**
 * @brief Cleanup the auth_cache table.
 */
static void
clean_auth_cache ()
{
  sql ("DELETE FROM auth_cache;");
}

/**
 * @brief Ensure that the database is in order.
 *
 * Only called by init_manage_internal, and ultimately only by the main process.
 *
 * @param[in]  check_encryption_key  Whether to check encryption key.
 * @param[in]  avoid_db_check_inserts  Whether to avoid inserts in DB check.
 * @return 0 success, -1 error.
 */
static int
check_db (int check_encryption_key, int avoid_db_check_inserts)
{
  /* The file locks managed at startup ensure that this is the only Manager
   * process accessing the db.  Nothing else should be accessing the db, access
   * should always go through Manager. */
  sql_begin_immediate ();
  if (check_db_extensions ())
    goto fail;
  create_tables ();
  check_db_sequences ();
  set_db_version (GVMD_DATABASE_VERSION);
  if (avoid_db_check_inserts == 0)
    {
      check_db_roles ();
      check_db_nvt_selectors ();
    }
  check_db_nvts ();
  check_db_port_lists (avoid_db_check_inserts);
  clean_auth_cache ();
  if (avoid_db_check_inserts == 0 && check_db_scanners ())
    goto fail;
  if (check_db_report_formats (avoid_db_check_inserts))
    goto fail;
  if (check_db_report_formats_trash ())
    goto fail;
  if (avoid_db_check_inserts == 0)
    {
      check_db_permissions ();
      check_db_settings ();
      check_db_credential_stores ();
    }
  cleanup_schedule_times ();
  if (check_encryption_key && check_db_encryption_key ())
    goto fail;

  sql_commit ();
  return 0;

 fail:
  sql_rollback ();
  return -1;
}

/**
 * @brief Stop any active tasks.
 */
static void
stop_active_tasks ()
{
  iterator_t tasks;
  get_data_t get;

  /* Clear the scan queue */
  scan_queue_clear ();

  /* Set requested and running tasks to stopped. */

  assert (current_credentials.uuid == NULL);
  memset (&get, '\0', sizeof (get));
  get.ignore_pagination = 1;
  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_QUEUED:
          case TASK_STATUS_STOP_REQUESTED:
          case TASK_STATUS_STOP_WAITING:
          case TASK_STATUS_PROCESSING:
            {
              task_t index = get_iterator_resource (&tasks);
              /* Set the current user, for event checks. */
              current_credentials.uuid = task_owner_uuid (index);
              task_last_report_any_status (index, &global_current_report);
              set_task_interrupted (index,
                                    "Task process exited abnormally"
                                    " (e.g. machine lost power or process was"
                                    " sent SIGKILL)."
                                    "  Setting scan status to Interrupted.");
              global_current_report = 0;
              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"
       " OR scan_run_status = %u;",
       TASK_STATUS_INTERRUPTED,
       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_QUEUED,
       TASK_STATUS_STOP_REQUESTED,
       TASK_STATUS_STOP_WAITING,
       TASK_STATUS_PROCESSING);
}

/**
 * @brief Clean up database tables.
 *
 * Remove superfluous entries from tables.
 */
static 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));");

  sql ("DELETE FROM permissions_get_tasks"
       " WHERE \"user\" NOT IN (SELECT id FROM users);");
}

/**
 * @brief Initialize the manage library.
 *
 * Check DB version, do startup database checks, load the NVT cache.
 * Optionally also stop active tasks.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  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]  stop_tasks          Stop any active tasks.
 * @param[in]  fork_connection     Function to fork a connection that will
 *                                 accept GMP requests.  Used to start tasks
 *                                 with GMP when an alert occurs.
 * @param[in]  skip_db_check       Skip DB check.
 * @param[in]  check_encryption_key  Check encryption key if doing DB check.
 * @param[in]  avoid_db_check_inserts  Whether to avoid inserts in DB check.
 *
 * @return 0 success, -1 error, -2 database is too old,
 *         -4 max_ips_per_target out of range, -5 database is too new.
 */
static int
init_manage_internal (GSList *log_config,
                      const db_conn_info_t *database,
                      int max_ips_per_target,
                      int max_email_attachment_size,
                      int max_email_include_size,
                      int max_email_message_size,
                      int stop_tasks,
                      manage_connection_forker_t fork_connection,
                      int skip_db_check,
                      int check_encryption_key,
                      int avoid_db_check_inserts)
{
  int ret;

  /* Summary of init cases:
   *
   *     daemon [--foreground]
   *         init_gmpd  cache 0
   *             init_manage
   *         serve_and_schedule
   *             forks child (serve_gmp)
   *                 init_gmpd_process
   *                     init_manage_process
   *                 ...
   *                 event
   *                   fork_connection_for_event
   *                       fork one
   *                           init_gmpd_process
   *                               init_manage_process
   *                           serve_client
   *                       fork two
   *                           gmp_auth, gmp_start_task_report.
   *                 ...
   *             manage_schedule
   *                 fork_connection_for_scheduler
   *                     fork one
   *                         init_gmpd_process
   *                             init_manage_process
   *                         serve_client
   *                     fork two
   *                         gmp_auth, gmp_start_task_report, gmp_resume_task_report.
   *     --create-user --delete-user --get-users
   *         manage_create, ...
   *             init_manage_helper
   *     --encrypt/decrypt-all-credentials
   *         manage_encrypt_...
   *             init_manage_helper
   *     --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)
    set_max_email_attachment_size (max_email_attachment_size);
  if (max_email_include_size)
    set_max_email_include_size (max_email_include_size);
  if (max_email_message_size)
    set_max_email_message_size (max_email_message_size);

  g_log_set_handler (G_LOG_DOMAIN,
                     ALL_LOG_LEVELS,
                     (GLogFunc) gvm_log_func,
                     log_config);

  memset (&current_credentials, '\0', sizeof (current_credentials));

  init_manage_open_db (database);

  /* Check that the versions of the databases are correct. */

  ret = check_db_versions ();
  if (ret)
    return ret;

  init_manage_create_functions ();

  /* 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)
    {
      /* This only happens for init_manage callers with skip_db_check set to 0
       * and init_manage_helper callers.  So there are only 2 callers:
       *
       *   1 the main process
       *   2 a helper processes (--create-user, --get-users, etc) when the
       *     main process is not running. */

      ret = check_db (check_encryption_key, avoid_db_check_inserts);
      if (ret)
        return ret;

      cleanup_tables ();

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

      sql ("INSERT INTO meta (name, value)"
           " VALUES ('max_hosts', %i)"
           " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;",
           max_hosts);
    }

  if (stop_tasks)
    /* Stop any active tasks. */
    stop_active_tasks ();

  /* Load the NVT cache into memory. */

  if (nvti_cache == NULL && !skip_update_nvti_cache ())
    update_nvti_cache ();

  if (skip_update_nvti_cache ())
    avoid_db_check_inserts = TRUE;

  if (skip_db_check == 0)
    /* Requires NVT cache if avoid_db_check_inserts == FALSE */
    check_db_configs (avoid_db_check_inserts);

  sql_close ();
  gvmd_db_conn_info.name = database->name ? g_strdup (database->name) : NULL;
  gvmd_db_conn_info.host = database->host ? g_strdup (database->host) : NULL;
  gvmd_db_conn_info.port = database->port ? g_strdup (database->port) : NULL;
  gvmd_db_conn_info.user = database->user ? g_strdup (database->user) : NULL;
  gvmd_db_conn_info.semaphore_timeout = database->semaphore_timeout;

  if (fork_connection)
    manage_fork_connection = fork_connection;
  return 0;
}

/**
 * @brief Initialize the manage library.
 */
void
init_manage_funcs () {
  init_manage_filter_utils_funcs (filter_term_sql);
  init_manage_settings_funcs (setting_value_sql,
                              setting_value_int_sql);
}

/**
 * @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]  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]  fork_connection     Function to fork a connection that will
 *                                 accept GMP requests.  Used to start tasks
 *                                 with GMP when an alert occurs.
 * @param[in]  skip_db_check       Skip DB check.
 *
 * @return 0 success, -1 error, -2 database is too old, -3 database needs
 *         to be initialised from server, -4 max_ips_per_target out of range,
 *         -5 database is too new.
 */
int
init_manage (GSList *log_config, const db_conn_info_t *database,
             int max_ips_per_target, int max_email_attachment_size,
             int max_email_include_size, int max_email_message_size,
             manage_connection_forker_t fork_connection,
             int skip_db_check)
{
  return init_manage_internal (log_config,
                               database,
                               max_ips_per_target,
                               max_email_attachment_size,
                               max_email_include_size,
                               max_email_message_size,
                               1,  /* Stop active tasks. */
                               fork_connection,
                               skip_db_check,
                               1, /* Check encryption key if checking db. */
                               0  /* Do not avoid inserts 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.
 *
 * @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]  avoid_db_check_inserts  Whether to avoid inserts in DB check.
 *
 * @return 0 success, -1 error, -2 database is too old, -3 database needs
 *         to be initialised from server, -4 max_ips_per_target out of range,
 *         -5 database is too new.
 */
int
init_manage_helper (GSList *log_config, const db_conn_info_t *database,
                    int max_ips_per_target, int avoid_db_check_inserts)
{
  return init_manage_internal (log_config,
                               database,
                               max_ips_per_target,
                               0,   /* Default max_email_attachment_size. */
                               0,   /* Default max_email_include_size. */
                               0,   /* Default max_email_message_size */
                               0,   /* Stop active tasks. */
                               NULL,
                               /* Skip DB check if main process is running, to
                                * avoid locking issues when creating tables.
                                *
                                * Safe because main process did the check. */
                               lockfile_locked ("gvm-serving")
                                ? 1    /* Skip DB check. */
                                : 0,   /* Do DB check. */
                               0, /* Dummy. */
                               avoid_db_check_inserts);
}

/**
 * @brief Cleanup the manage library.
 *
 * Optionally put any running task in the interrupted 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)
            {
              if (global_current_report)
                {
                  result_t result;
                  result = make_result (current_scanner_task,
                                        "", "", "", "", "Error Message",
                                        "Interrupting scan because GVM is"
                                        " exiting.",
                                        NULL);
                  report_add_result (global_current_report, result);
                }
              set_task_run_status (current_scanner_task, TASK_STATUS_INTERRUPTED);
            }
          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_message ("Received %s signal", strsignal (signal));
  if (sql_is_open ())
    {
      if (current_scanner_task)
        {
          g_warning ("%s: Error exit, setting running task to Interrupted",
                     __func__);
          set_task_interrupted (current_scanner_task,
                                "Error exit, setting running task to"
                                " Interrupted.");
        }
      sql_close ();
    }
}

/**
 * @brief Cleanup as immediately as possible.
 */
void
manage_reset_currents ()
{
  global_current_report = 0;
  current_scanner_task = (task_t) 0;
  sql ("RESET \"gvmd.user.id\";");
  sql ("RESET \"gvmd.tz_override\";");
  free_credentials (&current_credentials);
}

/**
 * @brief Get user hash.
 *
 * This is for "file" users, now entirely stored in db.
 *
 * @param[in]  username  User name.
 *
 * @return Hash.
 */
static 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 Check whether LDAP is enabled.
 *
 * @return 0 no, else yes.
 */
int
ldap_auth_enabled ()
{
  if (gvm_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.
 */
int
radius_auth_enabled ()
{
  if (gvm_auth_radius_enabled ())
    return sql_int ("SELECT coalesce ((SELECT CAST (value AS INTEGER) FROM meta"
                    "                  WHERE name = 'radius_enable'),"
                    "                 0);");
  return 0;
}

/**
 * @brief Ensure the user exists in the database.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 *
 * @return 0 success.
 */
static 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, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (),"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '', NULL, NULL, '%s', '', 2, m_now (), m_now ());",
       current_credentials.uuid,
       quoted_name,
       quoted_method);

  g_free (quoted_method);
  g_free (quoted_name);

  return 0;
}

/**
 * @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 (acl_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_GLOBAL_OR_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_GLOBAL_OR_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_GLOBAL_OR_USER_OWNS ()
                  " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                  credentials->uuid);

  credentials->excerpt_size
    = sql_int ("SELECT value FROM settings"
                " WHERE name = 'Note/Override Excerpt Size'"
                " AND " ACL_GLOBAL_OR_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 () - %d"
                     " FOR UPDATE;",
                     quoted_username, method, get_auth_timeout()*60);
  g_free (quoted_username);
  if (!hash)
    return -1;

  // verify for VALID or OUTDATED but don't update
  ret = manage_authentication_verify (hash, password);
  switch (ret)
    {
    case GMA_HASH_INVALID:
      ret = 1;
      break;
    case GMA_HASH_VALID_BUT_DATED:
      ret = 0;
      break;
    case GMA_SUCCESS:
      ret = 0;
      break;
    default:
      ret = -1;
      break;
    }
  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 = manage_authentication_hash (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 () - %d",
       get_auth_timeout()*60);
  g_free (hash);
}

/**
 * @brief Delete the credentials of a user from the authentication
 * cache.
 *
 * @param[in]  username     Username.
 */
static void
auth_cache_delete (const char *username)
{
  sql ("DELETE from auth_cache WHERE username = '%s'", username);
}

/**
 * @brief Refresh the authentication of a user in the authentication
 * cache.
 *
 * @param[in]  username     Username.
 */
static void
auth_cache_refresh (const char *username)
{
  sql ("UPDATE auth_cache SET creation_time = m_now() WHERE username = '%s'",
       username);
}

/**
 * @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.
 */
static int
authenticate_any_method (const gchar *username, const gchar *password,
                         auth_method_t *auth_method)
{
  int ret;
  gchar *hash;
  gboolean use_cache = TRUE;

  sql_begin_immediate ();

  ret = sql_table_lock_wait ("auth_cache", 30000);
  if (ret != 1)
    {
      sql_rollback ();
      g_warning ("%s: Failed to acquire auth_cache lock", __func__);
      use_cache = FALSE;
      // this begin is needed for other queries.
      sql_begin_immediate ();
    }

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

      *auth_method = AUTHENTICATION_METHOD_LDAP_CONNECT;
      /* Search the LDAP authentication cache first. */
      if (use_cache && auth_cache_find (username, password, 0) == 0)
        {
          auth_cache_refresh (username);
          sql_commit ();
          return 0;
        }

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

      if (ret == 0)
        {
          if (use_cache)
            auth_cache_insert (username, password, 0);
          sql_commit ();
        }
      else
        {
          sql_rollback ();
        }
      return ret;
    }
  if (gvm_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 (use_cache && auth_cache_find (username, password, 1) == 0)
        {
          auth_cache_refresh (username);
          sql_commit ();
          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)
        {
          if (use_cache)
            auth_cache_insert (username, password, 1);
          sql_commit ();
        }
      else
        {
          sql_rollback ();
        }
      return ret;
    }
  *auth_method = AUTHENTICATION_METHOD_FILE;
  if (use_cache && auth_cache_find (username, password, 2) == 0)
    {
      auth_cache_refresh (username);
      sql_commit ();
      return 0;
    }
  hash = manage_user_hash (username);
  ret = manage_authentication_verify (hash, password);
  switch (ret)
    {
    case GMA_HASH_INVALID:
      ret = 1;
      break;
    case GMA_HASH_VALID_BUT_DATED:
      g_free(hash);
      hash = manage_authentication_hash (password);
      sql ("UPDATE users SET password = '%s', modification_time = m_now () WHERE name = '%s';",
           hash, username);
      if(use_cache)
        auth_cache_insert (username, password, 2);
      ret = 0;
      break;
    case GMA_SUCCESS:
      if(use_cache)
        auth_cache_insert (username, password, 2);
      ret = 0;
      break;
    default:
      ret = -1;
      break;
    }

  if (ret)
    sql_rollback ();
  else
    sql_commit ();

  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 = g_strdup (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->uuid == NULL)
            /* Can happen if user is deleted while logged in to GSA. */
            return 1;

          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 Perform actions necessary at user logout
 */
void
logout_user ()
{
  auth_cache_delete(current_credentials.username);
  manage_reset_currents ();
}

/**
 * @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;
  gchar *extra_where, *extra_with, *extra_tables;
  int rc;

  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 min_qod=0";
  else
    count_get.filter = "rows=-1 first=1 permission=any min_qod=0";

  extra_with = extra_tables = NULL;

  if (strcasecmp (type, "config") == 0)
    {
      const gchar *usage_type = get_data_get_extra (get, "usage_type");
      extra_where = configs_extra_where (usage_type);
    }
  else if (strcmp (type, "task") == 0)
    {
      const gchar *usage_type = get_data_get_extra (get, "usage_type");
      extra_where = tasks_extra_where (get->trash, usage_type);
    }
  else if (strcmp (type, "report") == 0)
    {
      const gchar *usage_type = get_data_get_extra (get, "usage_type");
      extra_where = reports_extra_where (0, NULL, usage_type);
    }
  else if (strcmp (type, "result") == 0)
    {
      extra_where
        = g_strdup (" AND (severity != " G_STRINGIFY (SEVERITY_ERROR) ")");
    }
  else if (strcmp (type, "vuln") == 0)
    {
      extra_where = vulns_extra_where (filter_term_min_qod (count_get.filter));
      extra_with = vuln_iterator_extra_with_from_filter (count_get.filter);
      extra_tables = vuln_iterator_opts_from_filter (count_get.filter);
    }
  else
    extra_where = NULL;

  rc = count2 (get->subtype ? get->subtype : type,
               &count_get,
               type_owned (type) ? select_columns : NULL,
               type_owned (type) ? select_columns : NULL,
               NULL,
               NULL,
               type_owned (type) ? filter_columns : NULL,
               0,
               extra_tables,
               extra_where,
               extra_with,
               type_owned (type));

  g_free (extra_where);
  g_free (extra_with);
  g_free (extra_tables);
  return rc;
}

/**
 * @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;
  int overrides, min_qod;
  const char *usage_type;
  gchar *extra_tables, *extra_where;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  overrides = filter_term_apply_overrides (filter ? filter : get->filter);
  min_qod = filter_term_min_qod (filter ? filter : get->filter);

  free (filter);

  extra_tables = task_iterator_opts_table (overrides, min_qod, 0);
  usage_type = get_data_get_extra (get, "usage_type");
  extra_where = tasks_extra_where (get->trash, usage_type);

  ret = count2 ("task", get,
                columns,
                columns,
                where_columns,
                where_columns,
                extra_columns, 0,
                extra_tables,
                extra_where,
                NULL,
                TRUE);

  g_free (extra_tables);
  g_free (extra_where);
  return ret;
}

/**
 * @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 whether a task is in the trashcan.
 *
 * Assume the UUID is properly formatted.
 *
 * @param[in]  task_id  Task UUID.
 *
 * @return 1 if in trashcan, else 0.
 */
int
task_in_trash_id (const gchar *task_id)
{
  return sql_int ("SELECT hidden = 2"
                  " FROM tasks WHERE uuid = '%s';",
                  task_id);
}

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

#if ENABLE_AGENTS
/**
 * @brief Set the agent group of a task, also updating the agent group location.
 *
 * @param[in]  task    Task.
 * @param[in]  agent_group  Agent group.
 */
void
set_task_agent_group_and_location (task_t task, agent_group_t agent_group)
{
  sql ("UPDATE tasks SET agent_group = %llu, agent_group_location = 0,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       agent_group,
       task);
}

/**
 * @brief Return whether any task exists for agent groups of a scanner.
 *
 * @param[in] scanner  The row ID of the scanner.
 *
 * @return 1 if at least one task exists, else 0.
 */
int
agent_group_tasks_exist_by_scanner (scanner_t scanner)
{
  return !!sql_int (
    "SELECT COUNT(*) FROM tasks"
    " WHERE agent_group IN ("
    "  SELECT id FROM agent_groups WHERE scanner = %llu)"
    " AND agent_group_location = "
    G_STRINGIFY (LOCATION_TABLE)
    " AND hidden = 0;",
    scanner);
}
/**
 * @brief Return whether any hidden task exists for agent groups of a scanner.
 *
 * @param[in] scanner  The row ID of the scanner.
 *
 * @return 1 if at least one task exists, else 0.
 */
int
agent_group_hidden_tasks_exist_by_scanner (scanner_t scanner)
{
  return !!sql_int (
    "SELECT COUNT(*) FROM tasks "
    "WHERE hidden != 0 "
    " AND ("
    "  (agent_group_location = %d"
    "   AND agent_group IN ("
    "    SELECT id FROM agent_groups WHERE scanner = %llu ))"
    " OR "
    "  (agent_group_location = %d "
    "   AND agent_group IN ("
    "    SELECT id FROM agent_groups_trash WHERE scanner = %llu ))"
    ");",
    LOCATION_TABLE, scanner, LOCATION_TRASH, scanner);
}

/**
 * @brief Return the agent_group of a task.
 *
 * @param[in]  task  Task.
 *
 * @return Agent group of task.
 */
agent_group_t
task_agent_group (task_t task)
{
  agent_group_t agent_group = 0;
  switch (sql_int64 (&agent_group,
                     "SELECT agent_group FROM tasks WHERE id = %llu;",
                     task))
    {
    case 0:
      return agent_group;
      break;
    case 1:        /* Too few rows in result of query. */
    default:       /* Programming error. */
      assert (0);
    case -1:
      return 0;
      break;
    }
}

/**
 * @brief Return whether the agent group target of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_agent_group_in_trash (task_t task)
{
  return sql_int ("SELECT agent_group_location = "
                  G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks WHERE id = %llu;",
                  task);
}
#endif /*ENABLE_AGENTS*/

#if ENABLE_CONTAINER_SCANNING
/**
 * @brief Return the OCI image target of a task.
 *
 * @param[in]  task  Task.
 *
 * @return OCI image target of task.
 */
oci_image_target_t
task_oci_image_target (task_t task)
{
  oci_image_target_t oci_image_target = 0;
  switch (sql_int64 (&oci_image_target,
                     "SELECT oci_image_target FROM tasks WHERE id = %llu;",
                     task))
    {
      case 0:
        return oci_image_target;
        break;
      case 1:        /* Too few rows in result of query. */
      default:       /* Programming error. */
        assert (0);
      case -1:
        return 0;
        break;
    }
}

/**
 * @brief Return whether the OCI image target of a task is in the trashcan.
 *
 * @param[in]  task  Task.
 *
 * @return 1 if in trash, else 0.
 */
int
task_oci_image_target_in_trash (task_t task)
{
  return sql_int ("SELECT oci_image_target_location = "
                  G_STRINGIFY (LOCATION_TRASH)
                  " FROM tasks WHERE id = %llu;",
                  task);
}

/**
 * @brief Set the OCI image target of a task.
 *
 * @param[in]  task    Task.
 * @param[in]  target  Target.
 */
void
set_task_oci_image_target (task_t task, oci_image_target_t oci_image_target)
{
  sql ("UPDATE tasks SET oci_image_target = %llu,"
       " oci_image_target_location = 0,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       oci_image_target,
       task);
}

/**
 * @brief Clear default asset preferences if set.
 *
 * @param[in]  task  Task.
 */
void
clear_task_asset_preferences (task_t task)
{
  if (sql_int ("SELECT COUNT(*) FROM task_preferences"
                " WHERE task = %llu AND name = 'in_assets';",
                task))
    sql ("UPDATE task_preferences"
          " SET value = 'no'"
          " WHERE task = %llu AND name = 'in_assets';",
          task);

  if (sql_int ("SELECT COUNT(*) FROM task_preferences"
                " WHERE task = %llu AND name = 'assets_apply_overrides';",
                task))
    sql ("UPDATE task_preferences"
          " SET value = 'no'"
          " WHERE task = %llu AND name = 'assets_apply_overrides';",
          task);
}

#endif

/**
 * @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 usage type of a task.
 *
 * @param[in]  task  Task.
 * @param[out] usage_type  Pointer to a newly allocated string.
 *
 * @return 0 if successful, -1 otherwise.
 */
int
task_usage_type (task_t task, char ** usage_type)
{
  *usage_type = sql_string ("SELECT usage_type FROM tasks WHERE id = %llu;",
                            task);
  if (usage_type == NULL)
    return -1;

  return 0;
}

/**
 * @brief Set the usage_type of a task.
 *
 * @param[in]  task       Task.
 * @param[in]  usage_type New usage type ("scan" or "audit").
 */
void
set_task_usage_type (task_t task, const char *usage_type)
{
  const char *actual_usage_type;
  if (usage_type && strcasecmp (usage_type, "audit") == 0)
    actual_usage_type = "audit";
  else
    actual_usage_type = "scan";

  sql ("UPDATE tasks SET usage_type = '%s', modification_time = m_now ()"
       " WHERE id = %llu;", actual_usage_type, 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.
 *
 * @return Scheduled flag.
 */
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) && global_current_report)
    {
      sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;",
           status,
           global_current_report);
      if (setting_auto_cache_rebuild_int ())
        report_cache_counts (global_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 (EVENT_TASK_RUN_STATUS_CHANGED,
         (void*) status,
         task,
         (task == current_scanner_task) ? global_current_report : 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
      || run_status == TASK_STATUS_QUEUED)
    {
      return (unsigned int) sql_int ("SELECT max(id) FROM reports"
                                     " WHERE task = %llu AND end_time IS NULL"
                                     " AND (scan_run_status = %u "
                                     " OR scan_run_status = %u "
                                     " OR scan_run_status = %u);",
                                     task,
                                     TASK_STATUS_REQUESTED,
                                     TASK_STATUS_RUNNING,
                                     TASK_STATUS_QUEUED);
    }
  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_QUEUED
      || run_status == TASK_STATUS_DELETE_REQUESTED
      || run_status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED
      || run_status == TASK_STATUS_STOP_REQUESTED
      || run_status == TASK_STATUS_STOPPED
      || run_status == TASK_STATUS_INTERRUPTED
      || run_status == TASK_STATUS_PROCESSING)
    {
      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"
                                     " OR scan_run_status = %u);",
                                     task,
                                     TASK_STATUS_REQUESTED,
                                     TASK_STATUS_RUNNING,
                                     TASK_STATUS_QUEUED,
                                     TASK_STATUS_DELETE_REQUESTED,
                                     TASK_STATUS_DELETE_ULTIMATE_REQUESTED,
                                     TASK_STATUS_STOP_REQUESTED,
                                     TASK_STATUS_STOPPED,
                                     TASK_STATUS_INTERRUPTED,
                                     TASK_STATUS_PROCESSING);
    }
  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;
      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"
                      " greatest (least (((%i * 100) / upload_result_count), 100), -1)"
                      " FROM tasks"
                      " WHERE id = %llu;",
                      count,
                      task);
    }
  return -1;
}

/**
 * @brief Set the start time of a task.
 *
 * @param[in]  task  Task.
 * @param[in]  time  New time.  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.  UTC ctime format.  Freed before return.
 */
void
set_task_start_time_ctime (task_t task, char* time)
{
  sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()"
       " WHERE id = %llu;",
       parse_utc_ctime (time),
       task);
  free (time);
}

/**
 * @brief Get most recently completed report that precedes a report.
 *
 * @param[in]  task      The task.
 * @param[out] report    Report.
 * @param[out] previous  Report return, 0 if successfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
int
task_report_previous (task_t task, report_t report, report_t *previous)
{
  switch (sql_int64 (previous,
                     "SELECT id FROM reports"
                     " WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " AND creation_time < (SELECT creation_time FROM reports"
                     "                      WHERE id = %llu)"
                     " ORDER BY creation_time DESC LIMIT 1;",
                     task,
                     TASK_STATUS_DONE,
                     report))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *previous = 0;
        return 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        return -1;
        break;
    }
  return 0;
}

/**
 * @brief Get the report from the most recently completed invocation of task.
 *
 * @param[in]  task    The task.
 * @param[out] report  Report return, 0 if successfully 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 creation_time 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 the most recently invocation of task.
 *
 * @param[in]  task    The task.
 * @param[out] report  Report return, 0 if successfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
int
task_last_report_any_status (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT max (id) FROM reports"
                     " WHERE task = %llu"
                     " LIMIT 1;",
                     task))
    {
      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 successfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
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 creation_time 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 successfully failed to select report.
 *
 * @return 0 success, -1 error.
 */
int
task_last_resumable_report (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " AND (scan_run_status = %u"
                     "      OR scan_run_status = %u)"
                     " ORDER BY creation_time DESC LIMIT 1;",
                     task,
                     TASK_STATUS_STOPPED,
                     TASK_STATUS_INTERRUPTED))
    {
      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 creation_time 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.
 */
static 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_with_permission (group_id, &group, "modify_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 task schedule and schedule periods.
 *
 * If a valid schedule_id is provided, looks up the schedule and sets it for the task.
 * If not, falls back to setting schedule_periods if available.
 *
 * @param[in,out] task              Task object to update.
 * @param[in]     schedule_id       UUID of the schedule.
 * @param[in]     schedule_periods  Optional string representing number of periods.
 *
 * @return 0  on success,
 *         -1 if schedule lookup failed with permission,
 *         -2 if schedule not found.
 */
int
set_task_schedule_and_periods (task_t task, const gchar *schedule_id,
                               const gchar *schedule_periods)
{
  if (schedule_id)
    {
      schedule_t schedule;
      int periods = schedule_periods ? atoi (schedule_periods) : 0;

      if (find_schedule_with_permission (schedule_id,
                                         &schedule, "get_schedules"))
        {
          return -1;
        }
      if (schedule == 0)
        {
          return -2;
        }
      /** @todo
       *
       * This is a contention hole.  Some other process could remove
       * the schedule at this point.  The variable "schedule" would
       * still refer to the removed schedule.
       *
       * This happens all over the place.  Anywhere that a libmanage
       * client gets a reference to a resource, in fact.
       *
       * Possibly libmanage should lock the db whenever it hands out a
       * reference, and the client should call something to release
       * the lock when it's done.
       *
       * In many cases, like this one, the client could pass the UUID
       * directly to libmanage, instead of getting the reference.  In
       * this case the client would then need something like
       * set_task_schedule_uuid.
       */
      set_task_schedule (task, schedule, periods);
    }
  else if (schedule_periods
           && strlen (schedule_periods))
    set_task_schedule_periods_id (task,
                                  atoi (schedule_periods));
  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_ical (icalendar,"
       "                                              m_now()::bigint,"
       "                                              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.  -1 to use existing value.
 *
 * @return 0 success, -1 error.
 */
int
set_task_schedule_uuid (const gchar *task_id, schedule_t schedule, int periods)
{
  gchar *quoted_task_id, *schedule_periods;

  if (periods == -1)
    schedule_periods = g_strdup ("");
  else
    schedule_periods = g_strdup_printf ("schedule_periods = %i,",
                                        periods);

  quoted_task_id = sql_quote (task_id);
  sql ("UPDATE tasks"
       " SET schedule = %llu,"
       "%s"
       " schedule_next_time = (SELECT next_time_ical (icalendar,"
       "                                              m_now()::bigint,"
       "                                              timezone)"
       "                       FROM schedules"
       "                       WHERE id = %llu),"
       " modification_time = m_now ()"
       " WHERE uuid = '%s';",
       schedule, schedule_periods, schedule, quoted_task_id);
  g_free (quoted_task_id);

  g_free (schedule_periods);

  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 Get the next time a scheduled task will be due.
 *
 * @param[in]  task_id  Task UUID.
 *
 * @return Next scheduled time.
 */
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 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 /* import task. */)
    return SEVERITY_MISSING;

  report = sql_int64_0 ("SELECT id FROM reports"
                        "           WHERE reports.task = %llu"
                        "           AND reports.scan_run_status = %u"
                        "           ORDER BY reports.creation_time 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;

          if (validate_username (name))
            {
              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 NOT (SELECT icalendar LIKE '%%\nBEGIN:VEVENT%%\nRRULE%%'"
       "              OR icalendar LIKE '%%\nBEGIN:VEVENT%%\nRDATE%%'"
       "            FROM schedules WHERE schedules.id = schedule)"
       " 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_ical (icalendar, m_now()::bigint, 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 icalendar LIKE '%%\nBEGIN:VEVENT%%\nRRULE%%'"
       "          OR icalendar LIKE '%%\nBEGIN:VEVENT%%\nRDATE%%'"
       "       FROM schedules WHERE schedules.id = schedule)"
       " 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", __func__);

  GArray *reports_to_delete = g_array_new (TRUE, TRUE, sizeof(report_t));

  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)", __func__,
              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))
        {
          report_t report;

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

          g_debug ("%s: %llu to be deleted", __func__, report);

          g_array_append_val (reports_to_delete, report);
        }
      cleanup_iterator (&reports);
    }
  cleanup_iterator (&tasks);

  for (int i = 0; i < reports_to_delete->len; i++)
    {
      int ret;
      report_t report = g_array_index (reports_to_delete, report_t, i);

      sql_begin_immediate ();

      /* As in delete_report, this prevents other processes from getting the
       * report ID. */
      if (sql_int ("SELECT try_exclusive_lock('reports');") == 0)
        {
          g_debug ("%s: could not acquire lock on reports table", __func__);
          sql_rollback ();
          g_array_free (reports_to_delete, TRUE);
          return;
        }

      /* Check if report still exists in case another process has deleted it
       *  in the meantime. */
      if (sql_int ("SELECT count(*) FROM reports WHERE id = %llu",
                    report) == 0)
        {
          g_debug ("%s: %llu no longer exists", __func__, report);
          sql_rollback ();
          continue;
        }

      g_debug ("%s: deleting report %llu", __func__, report);
      ret = delete_report_internal (report);
      if (ret == 2)
        {
          /* Report is in use. */
          g_debug ("%s: %llu is in use", __func__, report);
          sql_rollback ();
          continue;
        }
      if (ret)
        {
          g_warning ("%s: failed to delete %llu (%i)",
                      __func__, report, ret);
          sql_rollback ();
          continue;
        }
      sql_commit ();
    }
  g_array_free (reports_to_delete, TRUE);
}


/**
 * @brief Get definitions file from a task's config.
 *
 * @param[in]  task  Task.
 *
 * @return Definitions file.
 */
static 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'", __func__, 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 successfully 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 Ensure an NVT occurs in the result_nvts table.
 *
 * @param[in]  nvt  NVT OID.
 */
static void
result_nvt_notice (const gchar *nvt)
{
  if (nvt == NULL)
    return;
  sql ("INSERT INTO result_nvts (nvt) VALUES ('%s') ON CONFLICT DO NOTHING;",
       nvt);
}

/**
 * @brief Make an OSP result.
 *
 * @param[in]  task         The task associated with the result.
 * @param[in]  host         Target host of result.
 * @param[in]  hostname     Hostname of the result.
 * @param[in]  nvt          A title for the result.
 * @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.
 * @param[in]  path         Result path, e.g. file location of a product.
 * @param[in]  hash_value   Hash value of the result.
 *
 * @return A result descriptor for the new result, 0 if error.
 */
result_t
make_osp_result (task_t task, const char *host, const char *hostname,
                 const char *nvt, const char *type, const char *description,
                 const char *port, const char *severity, int qod,
                 const char *path, const char *hash_value)
{
  char *nvt_revision = NULL;
  gchar *quoted_type, *quoted_desc, *quoted_nvt, *result_severity, *quoted_port;
  gchar *quoted_host, *quoted_hostname, *quoted_path, *quoted_hash_value;

  assert (task);
  assert (type);

  quoted_hash_value = sql_quote(hash_value ?: "");
  quoted_type = sql_quote (type ?: "");
  quoted_desc = sql_quote (description ?: "");
  quoted_nvt = sql_quote (nvt ?: "");
  quoted_port = sql_quote (port ?: "");
  quoted_host = sql_quote (host ?: "");
  quoted_hostname = sql_quote (hostname ? hostname : "");
  quoted_path = sql_quote (path ? path : "");

  if (nvt)
    {
      if (g_str_has_prefix (nvt, "1.3.6.1.4.1.25623."))
        nvt_revision = sql_string ("SELECT iso_time (modification_time)"
                                   " FROM nvts WHERE oid='%s'",
                                   quoted_nvt);
    }

  if (!severity || !strcmp (severity, ""))
    {
      if (!strcmp (type, severity_to_type (SEVERITY_ERROR)))
        result_severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR));
      else
        {
          /*
            result_severity
              = g_strdup_printf ("%0.1f",
                                 setting_default_severity_dbl ());
          */
          g_warning ("%s: Result without severity for test %s",
                     __func__, nvt ? nvt : "(unknown)");
          return 0;
        }
    }
  else
    result_severity = sql_quote (severity);
  result_nvt_notice (quoted_nvt);
  sql ("INSERT into results"
       " (owner, date, task, host, hostname, port, nvt,"
       "  nvt_version, severity, type, qod, qod_type, description,"
       "  path, uuid, result_nvt, hash_value)"
       " VALUES (NULL, m_now(), %llu, '%s', '%s', '%s', '%s',"
       "         '%s', '%s', '%s', %d, '', '%s',"
       "         '%s', make_uuid (),"
       "         (SELECT id FROM result_nvts WHERE nvt = '%s'), '%s');",
       task, quoted_host, quoted_hostname, quoted_port, quoted_nvt,
       nvt_revision ?: "", result_severity ?: "0",
       quoted_type, qod, quoted_desc, quoted_path, quoted_nvt,
       quoted_hash_value);
  g_free (result_severity);
  g_free (nvt_revision);
  g_free (quoted_type);
  g_free (quoted_desc);
  g_free (quoted_nvt);
  g_free (quoted_port);
  g_free (quoted_host);
  g_free (quoted_hostname);
  g_free (quoted_path);
  g_free (quoted_hash_value);

  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, "package_unreliable") == 0)
    return 30;
  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 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 || 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
           || strcasecmp (type, "alarm") == 0)
    g_warning ("%s result type requires an NVT", type);
  else if (strcasecmp (type, "Log Message") == 0
           || strcasecmp (type, "log") == 0)
    severity = g_strdup (G_STRINGIFY (SEVERITY_LOG));
  else if (strcasecmp (type, "Error Message") == 0
           || strcasecmp (type, "error") == 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 IP address.
 * @param[in]  hostname     Hostname.
 * @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: "Alarm", "Error Message" or
 *                          "Log Message".
 * @param[in]  description  Description of the result.
 * @param[in]  path         Result path, e.g. file location of a product.
 *
 * @return A result descriptor for the new result, 0 if error.
 */
result_t
make_result (task_t task, const char* host, const char *hostname,
             const char* port, const char* nvt,
             const char* type, const char* description,
             const char* path)
{
  result_t result;
  gchar *nvt_revision, *severity, *qod, *qod_type;
  gchar *quoted_nvt, *quoted_hostname, *quoted_descr, *quoted_path;
  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", nvt);
      return 0;
    }

  severity = nvt_severity (nvt, type);
  if (!severity)
    {
      g_warning ("NVT '%s' has no severity.  Result not created.", nvt);
      return 0;
    }

  quoted_nvt = NULL;
  if (nvt && strcmp (nvt, ""))
    {
      quoted_nvt = sql_quote (nvt);

      qod = g_strdup_printf ("(SELECT qod FROM nvts WHERE id = %llu)",
                             nvt_id);
      qod_type = g_strdup_printf ("(SELECT qod_type FROM nvts WHERE id = %llu)",
                                  nvt_id);

      if (g_str_has_prefix (nvt, "1.3.6.1.4.1.25623."))
        nvt_revision = sql_string ("SELECT iso_time (modification_time)"
                                   " FROM nvts WHERE oid='%s'",
                                   quoted_nvt);
      else if (g_str_has_prefix (nvt, "CVE-"))
        nvt_revision = sql_string ("SELECT iso_time (modification_time)"
                                   " FROM scap.cves WHERE uuid='%s'",
                                   quoted_nvt);
      else
        nvt_revision = strdup ("");
    }
  else
    {
      qod = g_strdup (G_STRINGIFY (QOD_DEFAULT));
      qod_type = g_strdup ("''");
      nvt_revision = g_strdup ("");
    }

  if (!strcmp (severity, ""))
    {
      g_free (severity);
      severity = g_strdup ("0.0");
    }
  quoted_hostname = sql_quote (hostname ? hostname : "");
  quoted_descr = sql_quote (description ?: "");
  quoted_path = sql_quote (path ? path : "");
  result_nvt_notice (nvt);
  sql ("INSERT into results"
       " (owner, date, task, host, hostname, port,"
       "  nvt, nvt_version, severity, type,"
       "  description, uuid, qod, qod_type, path, result_nvt)"
       " VALUES"
       " (NULL, m_now (), %llu, '%s', '%s', '%s',"
       "  '%s', '%s', '%s', '%s',"
       "  '%s', make_uuid (), %s, %s, '%s',"
       "  (SELECT id FROM result_nvts WHERE nvt = '%s'));",
       task, host ?: "", quoted_hostname, port ?: "",
       quoted_nvt ?: "", nvt_revision, severity, type,
       quoted_descr, qod, qod_type, quoted_path, quoted_nvt ? quoted_nvt : "");

  g_free (quoted_nvt);
  g_free (quoted_hostname);
  g_free (quoted_descr);
  g_free (qod);
  g_free (qod_type);
  g_free (nvt_revision);
  g_free (severity);
  g_free (quoted_path);
  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 ?: "");
  result_nvt_notice (nvt);
  sql ("INSERT into results"
       " (owner, date, task, host, port, nvt, nvt_version, severity, type,"
       "  description, uuid, qod, qod_type, path, result_nvt)"
       " VALUES"
       " (NULL, m_now (), %llu, '%s', '', '%s',"
       "  (SELECT iso_time (modification_time)"
       "     FROM scap.cves WHERE uuid='%s'),"
       "  '%1.1f', '%s', '%s', make_uuid (), %i, '', '',"
       "  (SELECT id FROM result_nvts WHERE nvt = '%s'));",
       task, host ?: "", nvt, nvt, cvss, severity_to_type (cvss),
       quoted_descr, QOD_DEFAULT, nvt);

  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[in]   report      Report of result.
 * @param[in]   host        Host of result.
 * @param[in]   port        Port of result.
 * @param[in]   path        Path of result.
 * @param[out]  oid         Detection script OID.
 * @param[out]  ref         Detection result UUID.
 * @param[out]  product     Product name.
 * @param[out]  location    Product location.
 * @param[out]  name        Detection script name.
 *
 * @return -1 on error, 0 on success.
 */
int
result_detection_reference (result_t result, report_t report,
                            const char *host,
                            const char *port,
                            const char *path,
                            char **oid, char **ref, char **product,
                            char **location, char **name)
{
  gchar *quoted_location, *quoted_host;

  if ((ref == NULL) || (product == NULL) || (location == NULL)
      || (name == NULL))
    return -1;

  if ((report == 0) || (host == NULL) || (oid == NULL))
    return -1;

  quoted_location = NULL;
  *oid = *ref = *product = *location = *name = NULL;
  quoted_host = sql_quote (host);


  if (path && strcmp (path, ""))
    {
      *location = strdup (path);
    }
  else if (port && strcmp (port, "")
           && !(g_str_has_prefix (port, "general/")))
    {
      *location = strdup (port);
    }
  else
    {
      *location = sql_string ("SELECT value"
                              " FROM report_host_details"
                              " WHERE report_host = (SELECT id"
                              "                      FROM report_hosts"
                              "                      WHERE report = %llu"
                              "                      AND host = '%s')"
                              " AND name = 'detected_at'"
                              " AND source_name = (SELECT nvt"
                              "                    FROM results"
                              "                    WHERE id = %llu);",
                              report, quoted_host, result);
    }
  if (*location == NULL)
    {
        goto detect_cleanup;
    }

  quoted_location = sql_quote (*location);

  *oid
    = sql_string ("SELECT value"
                  " FROM report_host_details"
                  " WHERE report_host = (SELECT id"
                  "                      FROM report_hosts"
                  "                      WHERE report = %llu"
                  "                      AND host = '%s')"
                  " AND name = 'detected_by@%s'"
                  " AND source_name = (SELECT nvt"
                  "                    FROM results"
                  "                    WHERE id = %llu)"
                  " LIMIT 1",
                  report, quoted_host, quoted_location, result);
  if (*oid == NULL)
    {
      *oid
        = sql_string ("SELECT value"
                      " FROM report_host_details"
                      " WHERE report_host = (SELECT id"
                      "                      FROM report_hosts"
                      "                      WHERE report = %llu"
                      "                      AND host = '%s')"
                      " AND name = 'detected_by'"
                      " AND source_name = (SELECT nvt"
                      "                    FROM results"
                      "                    WHERE id = %llu)"
                      " LIMIT 1",
                      report, quoted_host, result);
    }

  if (*oid == NULL)
    {
        goto detect_cleanup;
    }

  *product = sql_string ("SELECT name"
                         " FROM report_host_details"
                         " WHERE report_host = (SELECT id"
                         "                      FROM report_hosts"
                         "                      WHERE report = %llu"
                         "                      AND host = '%s')"
                         " AND source_name = '%s'"
                         " AND name != 'detected_at'"
                         " AND value = '%s';",
                         report, quoted_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 = %llu"
                     " AND host = '%s'"
                     " AND nvt = '%s'"
                     " AND (description LIKE '%%%s%%'"
                     "      OR port LIKE '%%%s%%');",
                     report, quoted_host, *oid, quoted_location,
                     quoted_location);

  if (*ref == NULL)
    goto detect_cleanup;

  g_free (quoted_host);
  g_free (quoted_location);

  return 0;

detect_cleanup:
  g_free (quoted_host);
  g_free (quoted_location);

  return -1;
}



/* Prognostics. */

/**
 * @brief Initialize an iterator of locations of an App for a report's host.
 *
 * @param[in]  iterator     Iterator.
 * @param[in]  report_host  Report host.
 * @param[in]  app          CPE.
 */
void
init_app_locations_iterator (iterator_t *iterator,
                             report_host_t report_host,
                             const gchar *app)
{
  gchar *quoted_app;

  assert (app);

  quoted_app = sql_quote (app);

  init_iterator (iterator,
                 "SELECT string_agg(DISTINCT value, ', ')"
                 " FROM report_host_details"
                 " WHERE report_host = %llu"
                 " AND name = '%s'"
                 " AND source_type = 'nvt'"
                 " AND source_name"
                 "     IN (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);
}

/**
 * @brief Get a location from an app locations iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The location.
 */
const char *
app_locations_iterator_location (iterator_t *iterator)
{
  return iterator_string (iterator, 0);
}

/**
 * @brief Initialize an iterator of CPEs for a report's host.
 *
 * @param[in]  iterator     Iterator.
 * @param[in]  report_host  Report host.
 */
void
init_host_details_cpe_iterator (iterator_t *iterator, report_host_t report_host)
{
  init_iterator (iterator,
                 "SELECT DISTINCT LOWER (value) FROM report_host_details"
                 " WHERE name = 'App' and report_host = %llu;",
                 report_host);
}

/**
 * @brief Get a CPE from an CPE iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The CPE.
 */
DEF_ACCESS (host_details_cpe_iterator_cpe, 0);

/**
 * @brief Initialize an iterator of CPEs for a product of a report's host.
 *
 * @param[in]  iterator     Iterator.
 * @param[in]  product      The product for which to get the CPEs.
 * @param[in]  report_host  Report host.
 */
void
init_host_details_cpe_product_iterator (iterator_t* iterator, const char *product, report_host_t report_host)
{
  gchar *quoted_product;
  quoted_product = sql_quote (product);
  init_iterator (iterator,
                 "SELECT DISTINCT LOWER (value) FROM report_host_details"
                 " WHERE name = 'App' AND report_host = %llu"
                 " AND value like '%s%s';",
                 report_host, quoted_product, "%");
  g_free (quoted_product);
}

/**
 * @brief Get a CPE from an CPE product iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The CPE.
 */
DEF_ACCESS (host_details_cpe_product_iterator_value, 0);

/**
 * @brief Initialize an iterator of root_ids of CPE match nodes.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  criteria  The criteria for the match nodes.
 */
void
init_cpe_match_nodes_iterator (iterator_t* iterator, const char *criteria)
{
  gchar *quoted_criteria;
  quoted_criteria = sql_quote (criteria);
  init_iterator (iterator,
                 "SELECT DISTINCT n.root_id"
                 " FROM scap.cpe_match_nodes n"
                 " JOIN scap.cpe_nodes_match_criteria c"
                 " ON n.id = c.node_id"
                 " JOIN scap.cpe_match_strings r"
                 " ON c.match_criteria_id = r.match_criteria_id"
                 " WHERE criteria like '%s%%';",
                 quoted_criteria);
  g_free (quoted_criteria);
}

/**
 * @brief Initialize an iterator of CPE match nodes root_ids for a CVE.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  cve       The CVE contained in the match nodes.
 */
void
init_cve_cpe_match_nodes_iterator (iterator_t* iterator, const char *cve)
{
  gchar *quoted_cve;
  quoted_cve = sql_quote (cve);
  init_iterator (iterator,
                 "SELECT DISTINCT root_id"
                 " FROM scap.cpe_match_nodes"
                 " WHERE cve_id = (SELECT id FROM scap.cves"
                 " WHERE uuid = '%s');",
                 quoted_cve);
  g_free (quoted_cve);
}

/**
 * @brief Initialize an iterator of references for a CVE.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  cve       The CVE with the references.
 */
void
init_cve_reference_iterator (iterator_t* iterator, const char *cve)
{
  gchar *quoted_cve;
  quoted_cve = sql_quote (cve);
  init_iterator (iterator,
                 "SELECT url, array_length(tags, 1), tags"
                 " FROM scap.cve_references"
                 " WHERE cve_id = (SELECT id FROM scap.cves"
                 " WHERE uuid = '%s');",
                 quoted_cve);
  g_free (quoted_cve);
}

/**
 * @brief Get a URL from a CVE reference iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The URL.
 */
DEF_ACCESS (cve_reference_iterator_url, 0);

/**
 * @brief Get the length of the tags array from a CVE reference iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  Length of the tags array.
 */
DEF_ACCESS (cve_reference_iterator_tags_count, 1);

/**
 * @brief Get the tags array from a CVE reference iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The tags array.
 */
DEF_ACCESS (cve_reference_iterator_tags, 2);

/**
 * @brief Get a root id from an CPE match node iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The root id.
 */
long long int
cpe_match_nodes_iterator_root_id (iterator_t* iterator)
{
  return iterator_int64 (iterator, 0);
}

/**
 * @brief Initialize an iterator of childs of an CPE match node.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  node      The match node with the childs.
 */
void
init_cpe_match_node_childs_iterator (iterator_t* iterator, long long int node)
{
  init_iterator (iterator,
                 "SELECT id FROM scap.cpe_match_nodes"
                 " WHERE root_id = %llu"
                 " AND root_id <> id;",
                 node);
}

/**
 * @brief Get a child from an CPE match node childs iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The id of the child node.
 */
long long int
cpe_match_node_childs_iterator_id (iterator_t* iterator)
{
  return iterator_int64 (iterator, 0);
}

/**
 * @brief Initialize an iterator of match strings of an CPE match node.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  node      The match node with match strings.
 */
void
init_cpe_match_string_iterator (iterator_t* iterator, long long int node)
{
  init_iterator (iterator,
                 "SELECT n.vulnerable, r.criteria, r.match_criteria_id, r.status,"
                 " r.version_start_incl, r.version_start_excl,"
                 " r.version_end_incl, r.version_end_excl"
                 " FROM scap.cpe_match_strings r"
                 " JOIN scap.cpe_nodes_match_criteria n"
                 " ON r.match_criteria_id = n.match_criteria_id"
                 " WHERE n.node_id = %llu;",
                 node);
}

/**
 * @brief Return if the match criteria is vulnerable.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  1 if the match criteria is vulnerable, 0 otherwise.
 */
int
cpe_match_string_iterator_vulnerable (iterator_t* iterator)
{
  return iterator_int64 (iterator, 0);
}

/**
 * @brief Return the criteria of the CPE match string.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The criteria of the match string.
 */
DEF_ACCESS (cpe_match_string_iterator_criteria, 1);

/**
 * @brief Return the match criteria id of the CPE match string.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The match criteria id, if any. NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_match_criteria_id, 2);

/**
 * @brief Return the status of the CPE match criteria.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The status of the CPE match criteria, if any.
 *           NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_status, 3);

/**
 * @brief Return the start included version of the match criteria.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The start included version of the match criteria, if any.
 *           NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_version_start_incl, 4);

/**
 * @brief Return the start excluded version of the match criteria.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The start excluded version of the match criteria, if any.
 *           NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_version_start_excl, 5);

/**
 * @brief Return the end included version of the match criteria.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The end included version of the match criteria, if any.
 *           NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_version_end_incl, 6);

/**
 * @brief Return the end excluded version of the match criteria.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return   The end excluded version of the match criteria, if any.
 *           NULL otherwise.
 */
DEF_ACCESS (cpe_match_string_iterator_version_end_excl, 7);

/**
 * @brief Initialize an iterator of CPE matches for a match string
 *        given a match criteria id.
 *
 * @param[in]  iterator           Iterator.
 * @param[in]  match_criteria_id  The match criteria id to get the matches for.
 * @param[in]  schema             Schema name, NULL for the default "scap".
 */
void
init_cpe_match_string_matches_iterator (iterator_t* iterator,
                                        const char *match_criteria_id,
                                        const char *schema)
{
  init_iterator (iterator,
                 "SELECT cpe_name_id, cpe_name"
                 " FROM %s.cpe_matches"
                 " WHERE match_criteria_id = '%s'",
                 schema ? schema : "scap",
                 match_criteria_id);
}

/**
 * @brief Get the CPE name id from a CPE match string matches iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The CPE name id.
 */
const char *
cpe_matches_cpe_name_id (iterator_t* iterator)
{
  return iterator_string (iterator, 0);
}

/**
 * @brief Get the CPE name from a CPE match string matches iterator.
 *
 * @param[in]  iterator   Iterator.
 *
 * @return  The CPE name id.
 */
const char *
cpe_matches_cpe_name (iterator_t* iterator)
{
  return iterator_string (iterator, 1);
}

/**
 * @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.
 */
void
init_host_prognosis_iterator (iterator_t* iterator, report_host_t report_host)
{
  init_iterator (iterator,
                 "SELECT cves.name AS vulnerability,"
                 "       max(cves.severity) AS severity,"
                 "       max(cves.description) AS 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 LOWER(cpes.name) = LOWER(report_host_details.value)"
                 " AND report_host_details.name = 'App'"
                 " AND cpes.id=affected_products.cpe"
                 " AND cves.id=affected_products.cve"
                 " GROUP BY cves.id, vulnerability, location, host"
                 " ORDER BY cves.id ASC"
                 " LIMIT %s OFFSET 0;",
                 report_host,
                 report_host,
                 sql_select_limit (-1));
}

DEF_ACCESS (prognosis_iterator_cve, 0);

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

DEF_ACCESS (prognosis_iterator_description, 2);
DEF_ACCESS (prognosis_iterator_cpe, 3);


/* Reports. */

/**
 * @brief Create a new GHashTable for containing resource rowids.
 *
 * @return The newly allocated GHashTable
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static GHashTable *
reports_hashtable ()
{
  GHashTable *reports_table;
  reports_table = new_resources_hashtable ();

  reports_add_all (reports_table);

  return reports_table;
}

/**
 * @brief Clear the report count cache for all reports of a user.
 *
 * @param[in]  uuid  UUID of user.
 */
static void
reports_clear_count_cache (const gchar *uuid)
{
  gchar *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  sql ("DELETE FROM report_counts"
       " WHERE report_counts.user = (SELECT id FROM users"
       "                             WHERE uuid = '%s');",
       quoted_uuid);
  g_free (quoted_uuid);
}

/**
 * @brief Clear all report counts for all dynamic severity users.
 */
void
reports_clear_count_cache_dynamic ()
{
  sql ("DELETE FROM report_counts"
       " WHERE report_counts.user IN (SELECT owner FROM settings"
       "                              WHERE name = 'Dynamic Severity'"
       "                              AND value = '1');");
}

/**
 * @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.
 */
static void
reports_build_count_cache (int clear, int* changes_out)
{
  int changes;
  iterator_t reports;
  changes = 0;

  /* Clear cache of trashcan reports, we won't count them. */
  sql ("DELETE FROM report_counts"
       " WHERE (SELECT hidden = 2 FROM tasks"
       "        WHERE tasks.id = (SELECT task FROM reports"
       "                          WHERE reports.id = report_counts.report));");

  init_iterator (&reports,
                 "SELECT id FROM reports"
                 " WHERE (SELECT hidden = 0 FROM tasks"
                 "        WHERE tasks.id = task);");

  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.
 */
static 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.
 */
static 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.
 */
static 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.
 *
 * @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.
 */
static void
report_cache_counts (report_t report, int clear_original, int clear_overridden,
                     const char* users_where)
{
  iterator_t cache_iterator;
  int 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, 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);
        }

      int criticals;
      report_counts_id (report, &criticals, &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.
 */
static 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, task, creation_time,"
       " modification_time, comment, scan_run_status, slave_progress)"
       " VALUES ('%s',"
       " (SELECT owner FROM tasks WHERE tasks.id = %llu),"
       " %llu, %i, %i, '', %u, 0);",
       uuid, task, task, time (NULL), 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 global_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 (global_current_report == (report_t) 0);

  if (global_current_report) return -1;

  if (report_id == NULL) report_id = &id;

  /* Generate report UUID. */

  *report_id = gvm_uuid_make ();
  if (*report_id == NULL) return -2;

  /* Create the report. */

  global_current_report = make_report (task, *report_id, status);

  set_report_scheduled (global_current_report);

  return 0;
}

/**
 * @brief Create the current report for an agent task.
 *
 * @param[in]   task       The agent task.
 * @param[in]   report_id  Report ID.
 * @param[in]   status     Run status of scan associated with report.
 *
 * @return 0 success, -1 global_current_report is already set, -2 failed if
 *         there is no report ID.
 */
int
create_agent_task_current_report (task_t task, char *report_id, task_status_t status)
{

  assert (global_current_report == (report_t) 0);

  if (global_current_report) return -1;

  if (report_id == NULL) return -2;

  /* Create the report. */

  global_current_report = make_report (task, report_id, status);

  set_report_scheduled (global_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.
 * @param[in]   hash_value  The detail's hash 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, const char *hash_value)
{
  char *quoted_host, *quoted_source_name, *quoted_source_type;
  char *quoted_source_desc, *quoted_name, *quoted_value;
  char *quoted_hash_value;

  quoted_host = sql_quote (host ? host : "");
  quoted_source_type = sql_quote (s_type ? s_type : "");
  quoted_source_name = sql_quote (s_name ? s_name : "");
  quoted_source_desc = sql_quote (s_desc ? s_desc : "");
  quoted_name = sql_quote (name ? name : "");
  quoted_value = sql_quote (value ? value : "");
  quoted_hash_value = sql_quote (hash_value ? hash_value : "");
  sql ("INSERT INTO report_host_details"
       " (report_host, source_type, source_name, source_description,"
       "  name, value, hash_value)"
       " VALUES"
       " ((SELECT id FROM report_hosts"
       "   WHERE report = %llu AND host = '%s'),"
       "  '%s', '%s', '%s', '%s', '%s', '%s');",
       report, quoted_host, quoted_source_type, quoted_source_name,
       quoted_source_desc, quoted_name, quoted_value, quoted_hash_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);
  g_free (quoted_hash_value);
}

/**
 * @brief Maximum number of COPY statements per transaction,
 *        when uploading report.
 */
#define CREATE_REPORT_COPY_CHUNK_SIZE 3000

/**
 * @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 Size of the buffer used for COPY statements in bytes.
 */
#define BUFFER_SIZE (20 * 1048576)  /* 20 MiB */

/**
 * @brief Set whether processing of a report is required
 *        and whether to add it to assets.
 *
 * @param[in]  report               The report to set the flags.
 * @param[in]  processing_required  Whether processing is required.
 * @param[in]  in_assets            Whether to add to assets.
 */
void
report_set_processing_required (report_t report,
                                int processing_required, int in_assets)
{
  sql ("UPDATE reports SET processing_required = %d, in_assets = %d"
       " WHERE id = %llu;",
       processing_required ? 1 : 0,
       in_assets ? 1 : 0,
       report);
}

/**
 * @brief Set Discovery flag to the report.
 *
 * @param[in]  report               The report to set the flags.
 * @param[in]  discovery            Discovery flag for validating Scan config.
 */
void
report_set_discovery (report_t report, gboolean discovery)
{
  sql ("UPDATE reports SET discovery = %d"
       " WHERE id = %llu;",
       discovery ? 1 : 0,
       report);
}

/**
 * @brief Check Discovery flag from the report.
 *
 * @param[in]  report  The report to check the flags.
 *
 * @return TRUE if report is from discovery scan.
 */
gboolean
check_report_discovery (report_t report)
{
  return sql_int ("SELECT discovery FROM reports WHERE id = %llu;", report);
}

/**
 * @brief Process imported report.
 *
 * Adds TLS certificates to the database and creates assets from the report.
 *
 * @param[in]  report  Report to process.
 *
 * @return 0 success, -1 error.
 */
int
process_report_import (report_t report)
{
  iterator_t hosts;
  task_t task;
  int uuid_was_null;

  init_report_host_iterator (&hosts, report, NULL, 0);

  report_task (report, &task);
  if (task == 0)
    {
      cleanup_iterator (&hosts);
      return -1;
    }

  uuid_was_null = 0;
  if (current_credentials.uuid == NULL)
    {
      current_credentials.uuid = task_owner_uuid (task);
      uuid_was_null = 1;
    }

  sql_begin_immediate ();

  while (next (&hosts))
    {
      const char* host = host_iterator_host (&hosts);
      gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
      add_assets_from_host_in_report (report, host);
    }

  sql_commit ();
  cleanup_iterator (&hosts);

  current_scanner_task = task;
  global_current_report = report;
  set_task_run_status (task, TASK_STATUS_PROCESSING);

  if (sql_int ("SELECT coalesce (in_assets, 0) = 1 FROM reports"
               " WHERE id = %llu;",
               report))
    {
      if (create_asset_report (report_uuid (report), ""))
        {
          g_warning ("%s: failed to create assets from report %llu",
                     __func__,
                     report);
          set_task_run_status (task, TASK_STATUS_INTERRUPTED);
          return -1;
        }
    }

  sql ("UPDATE reports SET processing_required = 0"
       " WHERE id = %llu;",
       report);

  /* Store reports host identifies to the asset snapshot. */
  int resp = asset_snapshot_collect_report_identifiers (report_uuid (report));
  if (resp != 0)
    {
      g_warning ("%s: unable to collect host identifiers with (resp=%d)",
                 __func__, resp);
    }
  asset_snapshots_target (report, task, check_report_discovery (report));

  set_task_run_status (task, TASK_STATUS_DONE);
  current_scanner_task = 0;
  global_current_report = 0;

  if (uuid_was_null)
    {
      g_free (current_credentials.uuid);
      current_credentials.uuid = NULL;
    }

  return 0;
}

/**
 * @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 import task, or NULL to create new one.
 * @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_id is NULL, -4 failed to find task, -5 task must be
 *         import task, -6 permission to create assets denied.
 */
int
create_report (array_t *results, const char *task_id, 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, rc;
  create_report_result_t *result, *end, *start;
  report_t report;
  user_t owner;
  task_t task;
  pid_t pid;
  host_detail_t *detail;
  db_copy_buffer_t copy_buffer;
  resource_t result_rowid, report_host_details_rowid;

  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", __func__);

  if (acl_user_may ("create_report") == 0)
    return 99;

  if (task_id == NULL)
    return -3;

  sql_begin_immediate ();

  /* Find the task. */

  rc = 0;

  /* It's important that the task is not in the trash, because we
   * are inserting results below.  This find function will fail if
   * the task is in the trash. */
  if (find_task_with_permission (task_id, &task, "modify_task"))
    rc = -1;
  else if (task == 0)
    rc = -4;
  else if (task_target (task))
    rc = -5;
  if (rc)
    {
      sql_rollback ();
      return rc;
    }

  /* Generate report UUID. */

  *report_id = gvm_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.
           *
           * Fork again so the parent can wait on the child, to prevent
           * zombies. */
          init_sentry ();
          cleanup_manage_process (FALSE);
          pid = fork ();
          switch (pid)
            {
              case 0:
                /* Grandchild.  Reopen the database (required after fork) and carry on
                 * to import the reports, . */
                init_sentry ();
                reinit_manage_process ();
                break;
              case -1:
                /* Grandchild's parent when error. */
                g_warning ("%s: fork: %s", __func__, strerror (errno));
                gvm_close_sentry ();
                exit (EXIT_FAILURE);
                break;
              default:
                /* Grandchild's parent.  Exit, to close parent's wait. */
                g_debug ("%s: %i forked %i", __func__, getpid (), pid);
                gvm_close_sentry ();
                exit (EXIT_SUCCESS);
                break;
            }
        }
        break;
      case -1:
        /* Parent when error. */
        g_warning ("%s: fork: %s", __func__, strerror (errno));
        global_current_report = report;
        set_task_interrupted (task,
                              "Failed to fork child to import report."
                              "  Setting task status to Interrupted.");
        global_current_report = 0;
        return -1;
        break;
      default:
        {
          int status;

          /* Parent.  Wait to prevent zombie, then return to respond to client. */
          g_debug ("%s: %i forked %i", __func__, getpid (), pid);
          while (waitpid (pid, &status, 0) < 0)
            {
              if (errno == ECHILD)
                {
                  g_warning ("%s: Failed to get child exit status",
                             __func__);
                  return -1;
                }
              if (errno == EINTR)
                continue;
              g_warning ("%s: waitpid: %s",
                         __func__,
                         strerror (errno));
              return -1;
            }
          return 0;
          break;
        }
    }

  setproctitle ("Importing results");

  /* Add the results. */

  db_copy_buffer_init (&copy_buffer,
                       BUFFER_SIZE,
                       "COPY results"
                       " (id, uuid, owner, date, task, host, hostname, port,"
                       "  nvt, type, description,"
                       "  nvt_version, severity, qod, qod_type,"
                       "  result_nvt, report)"
                       " FROM STDIN;");

  if (sql_int64 (&owner,
                 "SELECT owner FROM tasks WHERE tasks.id = %llu",
                 task))
    {
      g_warning ("%s: failed to get owner of task", __func__);
      return -1;
    }

  sql_begin_immediate ();
  g_debug ("%s: add hosts", __func__);
  index = 0;
  while ((start = (create_report_result_t*) g_ptr_array_index (host_starts,
                                                               index++)))
    if (start->host && start->description)
      manage_report_host_add (report, start->host,
                              parse_iso_time (start->description),
                              0);

  g_debug ("%s: add results", __func__);
  index = 0;
  count = 0;
  while ((result = (create_report_result_t*) g_ptr_array_index (results,
                                                                index++)))
    {
      gchar *quoted_host, *quoted_hostname, *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", __func__, index);

      quoted_host = sql_copy_escape (result->host ? result->host : "");
      quoted_hostname = sql_copy_escape (result->hostname ? result->hostname : "");
      quoted_port = sql_copy_escape (result->port ? result->port : "");
      quoted_nvt_oid = sql_copy_escape (result->nvt_oid ? result->nvt_oid : "");
      quoted_description = sql_copy_escape (result->description
                                       ? result->description
                                       : "");
      quoted_scan_nvt_version = sql_copy_escape (result->scan_nvt_version
                                       ? result->scan_nvt_version
                                       : "");
      quoted_severity =  sql_copy_escape (result->severity ? result->severity : "");
      if (result->qod && strcmp (result->qod, "") && strcmp (result->qod, "0"))
        quoted_qod = sql_copy_escape (result->qod);
      else
        quoted_qod = g_strdup (G_STRINGIFY (QOD_DEFAULT));
      quoted_qod_type = sql_copy_escape (result->qod_type ? result->qod_type : "");
      result_nvt_notice (quoted_nvt_oid);

      if (sql_int64 (&result_rowid,
        "SELECT nextval('results_id_seq');"))
        {
          g_warning ("%s: failed to get result row ID", __func__);
          return -1;
        }

      char* uuid = gvm_uuid_make ();
      if (uuid == NULL)
        {
          g_warning ("%s: failed to generate result UUID", __func__);
          return -2;
        }
      time_t date = time (NULL);

      resource_t result_nvt;

      if (sql_int64 (&result_nvt,
                     "SELECT id FROM result_nvts WHERE nvt = '%s';",
                     quoted_nvt_oid))
        {
          g_warning ("%s: failed to get result_nvt ID", __func__);
          return -1;
        }

      int ret = db_copy_buffer_append_printf
                  (&copy_buffer,
                  "%llu\t%s\t%llu\t%lld\t%llu\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%llu\t%llu\n",
                  result_rowid,
                  uuid,
                  owner,
                  (long long) date,
                  task,
                  quoted_host,
                  quoted_hostname,
                  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,
                  result_nvt,
                  report);

      g_free (quoted_host);
      g_free (quoted_hostname);
      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);
      g_free (uuid);

      if (ret)
        {
          g_warning ("%s: failed to write to database copy buffer",
                     __func__);
          db_copy_buffer_cleanup (&copy_buffer);
          return -1;
        }

      count++;

      if (count == CREATE_REPORT_COPY_CHUNK_SIZE)
        {
          if (db_copy_buffer_commit (&copy_buffer, FALSE))
            {
              db_copy_buffer_cleanup (&copy_buffer);
              return -1;
            }
          report_cache_counts (report, 1, 1, NULL);
          sql_commit ();
          gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
          sql_begin_immediate ();
          count = 0;
        }
    }

  if (count > 0)
    {
      if (db_copy_buffer_commit (&copy_buffer, TRUE))
      {
        db_copy_buffer_cleanup (&copy_buffer);
        return -1;
      }
      report_cache_counts (report, 1, 1, NULL);
      sql_commit ();
      gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
      sql_begin_immediate ();
    }

  sql ("INSERT INTO result_nvt_reports (result_nvt, report)"
       " SELECT distinct result_nvt, %llu FROM results"
       " WHERE results.report = %llu;",
       report,
       report);

  g_debug ("%s: add host ends", __func__);
  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 ();
            gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
            sql_begin_immediate ();
            count = 0;
          }
      }

  g_debug ("%s: add host details", __func__);


  db_copy_buffer_init (&copy_buffer,
                       BUFFER_SIZE,
                       "COPY report_host_details"
                       " (id, report_host, source_type, source_name,"
                           "  source_description, name, value)"
                       " FROM STDIN;");

  index = 0;
  count = 0;
  while ((detail = (host_detail_t*) g_ptr_array_index (details, index++)))
    if (detail->ip && detail->name)
      {
        char *quoted_host, *quoted_source_name, *quoted_source_type;
        char *quoted_source_desc, *quoted_name, *quoted_value;

        quoted_host = sql_copy_escape (detail->ip);
        quoted_source_type = sql_copy_escape (detail->source_type ?: "");
        quoted_source_name = sql_copy_escape (detail->source_name ?: "");
        quoted_source_desc = sql_copy_escape (detail->source_desc ?: "");
        quoted_name = sql_copy_escape (detail->name);
        quoted_value = sql_copy_escape (detail->value ?: "");

        if (sql_int64 (&report_host_details_rowid,
          "SELECT nextval('report_host_details_id_seq');"))
          {
            g_warning ("%s: failed to get report_host_details row ID", __func__);
            return -1;
          }

        resource_t report_host;

        sql_int64 (&report_host,
                    "SELECT id FROM report_hosts"
                    "   WHERE report = %llu AND host = '%s';",
                    report,
                    quoted_host);

        int ret = db_copy_buffer_append_printf
                    (&copy_buffer,
                    "%llu\t%llu\t%s\t%s\t%s\t%s\t%s\n",
                    report_host_details_rowid,
                    report_host,
                    quoted_source_type,
                    quoted_source_name,
                    quoted_source_desc,
                    quoted_name,
                    quoted_value);

        if (ret)
        {
          g_warning ("%s: failed to write to database copy buffer",
                      __func__);
          db_copy_buffer_cleanup (&copy_buffer);
          return -1;
        }

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

        count++;
        if (count == CREATE_REPORT_COPY_CHUNK_SIZE)
          {
            if (db_copy_buffer_commit (&copy_buffer, FALSE))
            {
              db_copy_buffer_cleanup (&copy_buffer);
              return -1;
            }
            sql_commit ();
            gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
            sql_begin_immediate ();
            count = 0;
          }
      }
  if (db_copy_buffer_commit (&copy_buffer, TRUE))
    {
      db_copy_buffer_cleanup (&copy_buffer);
      return -1;
    }

  sql_commit ();

  report_set_processing_required (report, 1, in_assets_int);

  gvm_close_sentry ();
  cleanup_manage_process (FALSE); //check this
  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 successfully 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 Get compliance counts for a report.
 *
 * @param[in]  report_id              UUID of the report.
 * @param[out] compliance_yes         Number of "YES" results.
 * @param[out] compliance_no          Number of "NO" results.
 * @param[out] compliance_incomplete  Number of "INCOMPLETE" results.
 * @param[out] compliance_undefined   Number of "UNDEFINED" results.
 */
void
report_compliance_by_uuid (const char *report_id,
                           int *compliance_yes,
                           int *compliance_no,
                           int *compliance_incomplete,
                           int *compliance_undefined)
{
  report_t report;
  gchar *quoted_uuid = sql_quote (report_id);
  sql_int64 (&report,
             "SELECT id FROM reports WHERE uuid = '%s';",
             quoted_uuid);

  if (compliance_yes)
    {
      *compliance_yes
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description ~ '^Compliant:\\s*YES\\s*';",
                   report);
    }

  if (compliance_no)
    {
      *compliance_no
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description ~ '^Compliant:\\s*NO\\s*';",
                   report);
    }

  if (compliance_incomplete)
    {
      *compliance_incomplete
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description ~ '^Compliant:\\s*INCOMPLETE\\s*';",
                   report);
    }
  if (compliance_undefined)
    {
      *compliance_undefined
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description !~ '^Compliant:\\s*';",
                   report);
    }

  g_free (quoted_uuid);
}

/**
 * @brief Add a result to a report.
 *
 * @param[in]  report  The report.
 * @param[in]  result  The result.
 */
static void
report_add_result_for_buffer (report_t report, result_t result)
{
  double severity, ov_severity;
  int qod;
  rowid_t rowid;
  iterator_t cache_iterator;
  user_t previous_user = 0;

  assert (result);

  if (report == 0)
    return;

  if (sql_int ("SELECT NOT EXISTS (SELECT * from result_nvt_reports"
               "                   WHERE result_nvt = (SELECT result_nvt"
               "                                       FROM results"
               "                                       WHERE id = %llu)"
               "                   AND report = %llu);",
       result,
       report))
    sql ("INSERT INTO result_nvt_reports (result_nvt, report)"
         " VALUES ((SELECT result_nvt FROM results WHERE id = %llu),"
         "         %llu);",
         result,
         report);

  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, *with_clause;

          owned_clause = acl_where_owned_for_get ("override", NULL, NULL,
                                                  &with_clause);

          ov_severity_str
            = sql_string ("%s"
                          " 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",
                          with_clause ? with_clause : "",
                          severity,
                          result,
                          owned_clause,
                          report,
                          severity);

          g_free (with_clause);
          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);
}

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

  report_add_result_for_buffer (report, result);

  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 Add results from an array to a report.
 *
 * @param[in]  report   The report to add the results to.
 * @param[in]  results  GArray containing the row ids of the results to add.
 */
void
report_add_results_array (report_t report, GArray *results)
{
  GString *array_sql;
  int index;

  if (report == 0 || results == NULL || results->len == 0)
    return;

  array_sql = g_string_new ("(");
  for (index = 0; index < results->len; index++)
    {
      result_t result;
      result = g_array_index (results, result_t, index);

      if (index)
        g_string_append (array_sql, ", ");
      g_string_append_printf (array_sql, "%llu", result);
    }
  g_string_append_c (array_sql, ')');

  sql ("UPDATE results SET report = %llu,"
       "                   owner = (SELECT reports.owner"
       "                            FROM reports WHERE id = %llu)"
       " WHERE id IN %s;",
       report, report, array_sql->str);

  for (index = 0; index < results->len; index++)
    {
      result_t result;
      result = g_array_index (results, result_t, index);

      report_add_result_for_buffer (report, result);
    }

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

  g_string_free (array_sql, TRUE);
}

/**
 * @brief Filter columns for report iterator.
 */
#define REPORT_ITERATOR_FILTER_COLUMNS                                         \
 { ANON_GET_ITERATOR_FILTER_COLUMNS, "task_id", "name", "creation_time",       \
   "date", "status", "task", "severity", "false_positive", "log", "low",       \
   "medium", "high", "critical", "hosts", "result_hosts", "fp_per_host",       \
   "log_per_host", "low_per_host", "medium_per_host", "high_per_host",         \
   "critical_per_host", "duration", "duration_per_host", "start_time",         \
   "end_time", "scan_start", "scan_end", "compliance_yes", "compliance_no",    \
   "compliance_incomplete", "compliant", NULL }

/**
 * @brief Report iterator columns.
 */
#define REPORT_ITERATOR_COLUMNS                                              \
 {                                                                           \
   { "id", NULL, KEYWORD_TYPE_INTEGER },                                     \
   { "uuid", NULL, KEYWORD_TYPE_STRING },                                    \
   { "iso_time (creation_time)", "name", KEYWORD_TYPE_STRING },              \
   { "''", NULL, KEYWORD_TYPE_STRING },                                      \
   { "creation_time", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "modification_time", NULL, KEYWORD_TYPE_INTEGER },                      \
   { "creation_time", "created", KEYWORD_TYPE_INTEGER },                     \
   { "modification_time", "modified", KEYWORD_TYPE_INTEGER },                \
   { "(SELECT name FROM users WHERE users.id = reports.owner)",              \
     "_owner",                                                               \
     KEYWORD_TYPE_STRING },                                                  \
   { "owner", NULL, KEYWORD_TYPE_INTEGER },                                  \
   { "start_time", "scan_start", KEYWORD_TYPE_INTEGER },                     \
   { "end_time", "scan_end", 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                                                     \
   },                                                                        \
   { "creation_time", "date", KEYWORD_TYPE_INTEGER },                        \
   { "(SELECT name FROM tasks WHERE tasks.id = task)", "task" },             \
   {                                                                         \
     "report_severity (id, opts.override, opts.min_qod)",                    \
     "severity",                                                             \
     KEYWORD_TYPE_DOUBLE                                                     \
   },                                                                        \
   {                                                                         \
     "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                                                    \
   },                                                                        \
   {                                                                         \
     "report_severity_count (id, opts.override, opts.min_qod, 'Critical')",  \
     "critical",                                                             \
     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                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Critical') * 1.0"                    \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "critical_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                                                    \
   },                                                                        \
   {                                                                         \
     "report_compliance_count (id, 'YES')",                                  \
     "compliance_yes",                                                       \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_compliance_count (id, 'NO')",                                   \
     "compliance_no",                                                        \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_compliance_count (id, 'INCOMPLETE')",                           \
     "compliance_incomplete",                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_compliance_status (id)",                                        \
     "compliant",                                                            \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   { 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 Return SQL WHERE for restricting a SELECT to compliance statuses.
 *
 * @param[in]  compliance  String describing compliance statuses of reports
 *                         to include (for example, "yniu" for yes (compliant),
 *                         no (not compliant), i (incomplete) and u (undefined))
 *                         All compliance statuses if NULL.
 *
 * @return WHERE clause for compliance if one is required, else NULL.
 */

static gchar*
where_compliance_status (const char *compliance)
{
  int count;
  GString *compliance_sql;

  /* Generate SQL for constraints on compliance status, according to compliance. */

  compliance_sql = g_string_new ("");
  count = 0;

  g_string_append_printf (compliance_sql,
    " AND report_compliance_status(reports.id) IN (");

  if (strchr (compliance, 'y'))
    {
      g_string_append (compliance_sql, "'yes'");
      count++;
    }
  if (strchr (compliance, 'n'))
    {
      g_string_append (compliance_sql, count ? ", 'no'" : "'no'");
      count++;
    }
  if (strchr (compliance, 'i'))
    {
      g_string_append (compliance_sql, count ? ", 'incomplete'" : "'incomplete'");
      count++;
    }
  if (strchr (compliance, 'u'))
    {
      g_string_append (compliance_sql, count ? ", 'undefined'" : "'undefined'");
      count++;
    }

  g_string_append (compliance_sql, ")");

  if ((count == 4) || (count == 0))
    {
      /* All compliance levels or no valid ones selected. */
      g_string_free (compliance_sql, TRUE);
      return NULL;
    }

   return g_string_free (compliance_sql, FALSE);;
}

/**
 * @brief  Generate an extra WHERE clause for selecting reports
 *
 * @param[in]  trash        Whether to get results from trashcan.
 * @param[in]  filter       Filter string.
 * @param[in]  usage_type   The usage type to limit the selection to.
 *
 * @return Newly allocated where clause string.
 */
static gchar *
reports_extra_where (int trash, const gchar *filter, const char *usage_type)
{

  GString *extra_where = g_string_new ("");
  gchar *trash_clause;

  if (trash)
    {
      trash_clause = g_strdup_printf (" AND (SELECT hidden FROM tasks"
                                      "      WHERE tasks.id = task)"
                                      "     = 2");
    }
  else
    {
      trash_clause = g_strdup_printf (" AND (SELECT hidden FROM tasks"
                                      "      WHERE tasks.id = task)"
                                      "     = 0");
    }


  g_string_append_printf(extra_where, "%s", trash_clause);
  g_free (trash_clause);

  gchar *usage_type_clause, *compliance_clause = NULL;
  gchar *compliance_filter = NULL;
  if (usage_type && strcmp (usage_type, ""))
    {
      gchar *quoted_usage_type;
      quoted_usage_type = sql_quote (usage_type);
      usage_type_clause = g_strdup_printf (" AND task in (SELECT id from tasks"
                                          "              WHERE usage_type='%s')",
                                          quoted_usage_type);

      g_free (quoted_usage_type);
    }
  else
    usage_type_clause = NULL;

  if (filter)
    compliance_filter = filter_term_value(filter, "report_compliance_levels");

  compliance_clause = where_compliance_status (compliance_filter ?: "yniu");

  g_string_append_printf (extra_where, "%s%s", usage_type_clause ?: "", compliance_clause ?: "");
  g_free (compliance_filter);
  g_free (compliance_clause);
  g_free (usage_type_clause);

  return g_string_free (extra_where, FALSE);
}

/**
 * @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, *extra_where;
  int ret;

  extra_tables = report_iterator_opts_table (0, MIN_QOD_DEFAULT);

  const gchar *usage_type = get_data_get_extra (get, "usage_type");
  extra_where = reports_extra_where(get->trash, get->filter, usage_type);

  ret = count2 ("report", get, columns, NULL, where_columns, NULL,
                filter_columns, 0,
                extra_tables,
                extra_where,
                NULL,
                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;
  int overrides, min_qod;
  const char *usage_type;
  gchar *extra_tables, *extra_where;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  overrides = filter_term_apply_overrides (filter ? filter : get->filter);
  min_qod = filter_term_min_qod (filter ? filter : get->filter);

  extra_tables = report_iterator_opts_table (overrides, min_qod);
  usage_type = get_data_get_extra (get, "usage_type");

  extra_where = reports_extra_where (get->trash,
                                     filter ? filter : get->filter,
                                     usage_type);

  free (filter);

  ret = init_get_iterator2 (iterator,
                            "report",
                            get,
                            /* Columns. */
                            columns,
                            NULL,
                            /* Filterable columns not in SELECT columns. */
                            where_columns,
                            NULL,
                            filter_columns,
                            0,
                            extra_tables,
                            extra_where,
                            NULL,
                            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);
}

/**
 * @brief Initialise a report iterator for reports that require processing.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  max       Maximum number of reports returned.
 */
void
init_report_awaiting_processing_iterator (iterator_t* iterator, int max)
{
  if (max < 1)
    max = -1;

  init_iterator (iterator,
                 "SELECT id FROM reports"
                 " WHERE scan_run_status = %u"
                 " AND processing_required = 1"
                 " ORDER BY creation_time LIMIT %s;",
                 TASK_STATUS_RUNNING,
                 sql_select_limit (max));
}

/**
 * @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, "hmlg" for
 *                     High, Medium, Low and loG).  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_auto (const char *levels, const char *new_severity_sql)
{
  int count;
  GString *levels_sql;

  /* Generate SQL for constraints on message type, according to levels. */

  levels_sql = g_string_new ("");

  if (levels == NULL || strlen (levels) == 0)
    {
      g_string_append_printf (levels_sql,
                              " AND %s != " G_STRINGIFY (SEVERITY_ERROR),
                              new_severity_sql);
      return levels_sql;
    }

  count = 0;

  g_string_append_printf (levels_sql, " AND severity_in_levels (%s", new_severity_sql);

  if (strchr (levels, 'c'))
    {
      g_string_append (levels_sql, ", 'critical'");
      count++;
    }
  if (strchr (levels, 'h'))
    {
      g_string_append (levels_sql, ", 'high'");
      count++;
    }
  if (strchr (levels, 'm'))
    {
      g_string_append (levels_sql, ", 'medium'");
      count++;
    }
  if (strchr (levels, 'l'))
    {
      g_string_append (levels_sql, ", 'low'");
      count++;
    }
  if (strchr (levels, 'g'))
    {
      g_string_append (levels_sql, ", 'log'");
      count++;
    }
  if (strchr (levels, 'f'))
    {
      g_string_append (levels_sql, ", 'false'");
      count++;
    }

  if (count == 0)
    {
      g_string_free (levels_sql, TRUE);
      return NULL;
    }

  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 overridden 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 compliance levels.
 *
 * @param[in]  levels  String describing compliance levels to include in
 *                     report (for example, "yniu" for "yes, "no", "incomplete"
 *                     and "undefined").  All levels if NULL.
 *
 * @return WHERE clause for compliance levels if one is required, else NULL.
 */
static gchar*
where_compliance_levels (const char *levels)
{
  int count;
  GString *levels_sql;

  if (levels == NULL)
    return NULL;

  levels_sql = g_string_new ("");
  count = 0;

  g_string_append_printf (levels_sql,
    " AND coalesce("
    "  lower(substring(description, '^Compliant:[\\s]*([A-Z_]*)')),"
    "  'undefined') IN (");

  if (strchr (levels, 'y'))
    {
      g_string_append (levels_sql, "'yes'");
      count++;
    }
  if (strchr (levels, 'n'))
    {
      g_string_append (levels_sql, count ? ", 'no'" : "'no'");
      count++;
    }
  if (strchr (levels, 'i'))
    {
      g_string_append (levels_sql, count ? ", 'incomplete'" : "'incomplete'");
      count++;
    }
  if (strchr (levels, 'u'))
    {
      g_string_append (levels_sql, count ? ", 'undefined'" : "'undefined'");
      count++;
    }
  g_string_append (levels_sql, ")");

  if ((count == 4) || (count == 0))
    {
      /* All compliance levels or none selected, so no restriction is necessary. */
      g_string_free (levels_sql, TRUE);
      return NULL;
    }
  return g_string_free (levels_sql, FALSE);
}

/**
 * @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 an empty string.
 */
static gchar*
where_qod (int min_qod)
{
  gchar *qod_sql;

  if (min_qod <= 0)
    qod_sql = g_strdup ("");
  else
    qod_sql = g_strdup_printf (" AND (results.qod >= CAST (%d AS INTEGER))",
                               min_qod);

  return qod_sql;
}

/**
 * @brief Filter columns for result iterator.
 */
#define RESULT_ITERATOR_FILTER_COLUMNS                                        \
  { GET_ITERATOR_FILTER_COLUMNS, "host", "location", "nvt",                   \
    "type", "original_type",                                                  \
    "description", "task", "report", "cvss_base", "nvt_version",              \
    "severity", "original_severity", "vulnerability", "date", "report_id",    \
    "solution_type", "qod", "qod_type", "task_id", "cve", "hostname",         \
    "path", "compliant", "epss_score", "epss_percentile", "max_epss_score",   \
    "max_epss_percentile", NULL }

// TODO Combine with RESULT_ITERATOR_COLUMNS.
/**
 * @brief Result iterator filterable columns, for severity only version .
 */
#define BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE                      \
    { "results.id", "id", KEYWORD_TYPE_INTEGER },                             \
    { "results.uuid", "uuid", KEYWORD_TYPE_STRING },                          \
    { "(SELECT name FROM nvts WHERE nvts.oid =  nvt)",                        \
      "name",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "''", "comment", KEYWORD_TYPE_STRING },                                 \
    { "date",                                                                 \
      "creation_time",                                                        \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "date",                                                                 \
      "modification_time",                                                    \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "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 },                                  \
    /* Result specific columns. */                                            \
    { "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 },                                    \
    { "results.qod_type", "qod_type", KEYWORD_TYPE_STRING },                  \
    { "(CASE WHEN (hostname IS NULL) OR (hostname = '')"                      \
      " THEN (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)"                                                       \
      " ELSE hostname"                                                        \
      " END)",                                                                \
      "hostname",                                                             \
      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 }, \
    { "path",                                                                 \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT CASE WHEN host IS NULL"                                        \
      "             THEN NULL"                                                \
      "             ELSE (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 = results.report)"      \
      "                               AND value = results.host"               \
      "                               LIMIT 1))"                              \
      "             END)",                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM notes"                              \
      "                     WHERE (result = results.id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = results.task))"           \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM overrides"                          \
      "                     WHERE (result = results.id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = results.task))"           \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { TICKET_SQL_RESULT_MAY_HAVE_TICKETS("results.id"),                       \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "(SELECT name FROM tasks WHERE tasks.id = task)",                       \
      "task",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "coalesce(lower(substring(description, '^Compliant:[\\s]*([A-Z_]*)'))," \
      "         'undefined')",                                                \
      "compliant",                                                            \
      KEYWORD_TYPE_STRING },

/**
 * @brief Result iterator columns.
 */
#define RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE                           \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE                          \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Result iterator columns, when CERT db is not loaded.
 */
#define RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE_NO_CERT                   \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE                          \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief SQL for result iterator column.
 */
#define RESULT_HOSTNAME_SQL(hostname_col, host_col, report_col)               \
      "(CASE WHEN (" hostname_col " IS NULL) "                                \
      "           OR (" hostname_col " = '')"                                 \
      " THEN (SELECT value FROM report_host_details"                          \
      "       WHERE name = 'hostname'"                                        \
      "         AND report_host = (SELECT id FROM report_hosts "              \
      "                            WHERE report_hosts.host = " host_col       \
      "                            AND"                                       \
      "                            report_hosts.report = " report_col ")"     \
      "       LIMIT 1)"                                                       \
      " ELSE " hostname_col                                                   \
      " END)"

/**
 * @brief Result iterator columns.
 */
#define PRE_BASE_RESULT_ITERATOR_COLUMNS(new_severity_sql)                    \
    { "results.id", "id", KEYWORD_TYPE_INTEGER },                             \
    /* ^ 0 */                                                                 \
    { "results.uuid", "uuid", KEYWORD_TYPE_STRING },                          \
    { "nvts.name",                                                            \
      "name",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "''", "comment", KEYWORD_TYPE_STRING },                                 \
    { "date",                                                                 \
      "creation_time",                                                        \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "date",                                                                 \
      "modification_time",                                                    \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "date", "created", KEYWORD_TYPE_INTEGER },                              \
    { "date", "modified", KEYWORD_TYPE_INTEGER },                             \
    { "(SELECT name FROM users WHERE users.id = results.owner)",              \
      "_owner",                                                               \
      KEYWORD_TYPE_STRING },                                                  \
    { "results.owner", NULL, KEYWORD_TYPE_INTEGER },                          \
    /* ^ 9 */                                                                 \
    /* Result specific columns. */                                            \
    { "host", NULL, KEYWORD_TYPE_STRING },                                    \
    /* ^ 10 = 0 */                                                            \
    { "port", "location", KEYWORD_TYPE_STRING },                              \
    { "nvt", NULL, KEYWORD_TYPE_STRING },                                     \
    { "severity_to_type (results.severity)",                                  \
      "original_type",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "severity_to_type (" new_severity_sql ")",                              \
      "type",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "description", NULL, KEYWORD_TYPE_STRING },                             \
    { "task", NULL, KEYWORD_TYPE_INTEGER },                                   \
    { "report", "report_rowid", KEYWORD_TYPE_INTEGER },                       \
    { "nvts.cvss_base",                                                       \
      "cvss_base",                                                            \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "nvt_version", NULL, KEYWORD_TYPE_STRING },                             \
    { "results.severity", "original_severity", KEYWORD_TYPE_DOUBLE },         \
    /* ^ 20 = 10 */                                                           \
    { new_severity_sql,                                                       \
      "severity",                                                             \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "nvts.name",                                                            \
      "vulnerability",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "date" , NULL, KEYWORD_TYPE_INTEGER },                                  \
    { "(SELECT uuid FROM reports WHERE id = report)",                         \
      "report_id",                                                            \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.solution_type",                                                   \
      "solution_type",                                                        \
      KEYWORD_TYPE_STRING },                                                  \
    { "results.qod", "qod", KEYWORD_TYPE_INTEGER },                           \
    { "results.qod_type", NULL, KEYWORD_TYPE_STRING },                        \
    {  RESULT_HOSTNAME_SQL("hostname", "results.host", "results.report"),     \
       "hostname",                                                            \
       KEYWORD_TYPE_STRING },                                                 \
    { "(SELECT uuid FROM tasks WHERE id = task)",                             \
      "task_id",                                                              \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.cve", "cve", KEYWORD_TYPE_STRING },                               \
    /* ^ 30 = 20 */                                                           \
    { "path",                                                                 \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT CASE WHEN host IS NULL"                                        \
      "             THEN NULL"                                                \
      "             ELSE (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 = results.report)"      \
      "                               AND value = results.host"               \
      "                               LIMIT 1))"                              \
      "             END)",                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM notes"                              \
      "                     WHERE (result = results.id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = results.task))"           \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM overrides"                          \
      "                     WHERE (result = results.id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = results.task))"           \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { TICKET_SQL_RESULT_MAY_HAVE_TICKETS("results.id"),                       \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    /* ^ 35 = 25 */                                                           \
    { "(SELECT name FROM tasks WHERE tasks.id = task)",                       \
      "task",                                                                 \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.summary",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.insight",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.affected",                                                        \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.impact",                                                          \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    /* ^ 40 = 30 */                                                           \
    { "nvts.solution",                                                        \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.detection",                                                       \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.family",                                                          \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "nvts.tag",                                                             \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "coalesce(lower(substring(description, '^Compliant:[\\s]*([A-Z_]*)'))," \
      "         'undefined')",                                                \
      "compliant",                                                            \
      KEYWORD_TYPE_STRING },                                                  \
    /* ^ 45 = 35 */                                                           \
    { "coalesce (result_vt_epss.epss_score, 0.0)",                            \
      "epss_score",                                                           \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "coalesce (result_vt_epss.epss_percentile, 0.0)",                       \
      "epss_percentile",                                                      \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "result_vt_epss.epss_cve",                                              \
      "epss_cve",                                                             \
      KEYWORD_TYPE_STRING },                                                  \
    { "coalesce (result_vt_epss.epss_severity, 0.0)",                         \
      "epss_severity",                                                        \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "coalesce (result_vt_epss.max_epss_score, 0.0)",                        \
      "max_epss_score",                                                       \
      KEYWORD_TYPE_DOUBLE },                                                  \
    /* ^ 50 = 40 */                                                           \
    { "coalesce (result_vt_epss.max_epss_percentile, 0.0)",                   \
      "max_epss_percentile",                                                  \
      KEYWORD_TYPE_DOUBLE },                                                  \
    { "result_vt_epss.max_epss_cve",                                          \
      "max_epss_cve",                                                         \
      KEYWORD_TYPE_STRING },                                                  \
    { "coalesce (result_vt_epss.max_epss_severity, 0.0)",                     \
      "max_epss_severity",                                                    \
      KEYWORD_TYPE_DOUBLE },                                                  \

/**
 * @brief Result iterator columns.
 */
#define BASE_RESULT_ITERATOR_COLUMNS                                          \
  PRE_BASE_RESULT_ITERATOR_COLUMNS("lateral_new_severity.new_severity")

/**
 * @brief Result iterator columns.
 */
#define RESULT_ITERATOR_COLUMNS                                               \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS                                              \
    { SECINFO_SQL_RESULT_CERT_BUNDS,                                          \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { SECINFO_SQL_RESULT_DFN_CERTS,                                           \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Delta result iterator columns.
 */
#define DELTA_RESULT_COLUMNS                                                  \
    { "comparison.state", "delta_state", KEYWORD_TYPE_STRING },               \
    { "comparison.delta_description", NULL, KEYWORD_TYPE_STRING },            \
    { "comparison.delta_severity", NULL, KEYWORD_TYPE_DOUBLE },               \
    { "comparison.delta_qod", NULL, KEYWORD_TYPE_INTEGER },                   \
    { "comparison.delta_uuid", NULL, KEYWORD_TYPE_STRING },                   \
    { "delta_qod_type", NULL, KEYWORD_TYPE_STRING },                          \
    { "delta_date",                                                           \
      "delta_creation_time",                                                  \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "delta_date",                                                           \
      "delta_modification_time",                                              \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "delta_task", NULL, KEYWORD_TYPE_INTEGER },                             \
    { "delta_report", NULL, KEYWORD_TYPE_INTEGER },                           \
    { "(SELECT name FROM users WHERE users.id = results.owner)",              \
      "_owner",                                                               \
      KEYWORD_TYPE_STRING },                                                  \
    { "delta_path", NULL, KEYWORD_TYPE_STRING },                              \
    { "(SELECT CASE WHEN delta_host IS NULL"                                  \
      "             THEN NULL"                                                \
      "             ELSE (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 = results.report)"      \
      "                               AND value = delta_host"                 \
      "                               LIMIT 1))"                              \
      "             END)",                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_STRING },                                                  \
    { "delta_nvt_version", NULL, KEYWORD_TYPE_STRING },                       \
    { "result2_id", NULL, KEYWORD_TYPE_INTEGER },                             \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM notes"                              \
      "                     WHERE (result = result2_id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = delta_task))"             \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "(SELECT CASE"                                                          \
      "        WHEN EXISTS (SELECT * FROM overrides"                          \
      "                     WHERE (result = result2_id"                       \
      "                            OR (result = 0 AND nvt = results.nvt))"    \
      "                     AND (task = 0 OR task = delta_task))"             \
      "        THEN 1"                                                        \
      "        ELSE 0"                                                        \
      "        END)",                                                         \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { TICKET_SQL_RESULT_MAY_HAVE_TICKETS("result2_id"),                       \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "delta_hostname", NULL, KEYWORD_TYPE_STRING },                          \
    { "delta_new_severity", NULL, KEYWORD_TYPE_DOUBLE },                      \
    { "coalesce(lower(substring(comparison.delta_description,"                \
      "          '^Compliant:[\\s]*([A-Z_]*)')),"                             \
      "         'undefined')",                                                \
      "compliant",                                                            \
      KEYWORD_TYPE_STRING },

/**
 * @brief Delta result iterator columns.
 */
#define DELTA_RESULT_ITERATOR_COLUMNS                                         \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS                                              \
    { SECINFO_SQL_RESULT_CERT_BUNDS,                                          \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { SECINFO_SQL_RESULT_DFN_CERTS,                                           \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    DELTA_RESULT_COLUMNS                                                      \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Result iterator columns, when CERT db is not loaded.
 */
#define DELTA_RESULT_ITERATOR_COLUMNS_NO_CERT                                 \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS                                              \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
      DELTA_RESULT_COLUMNS                                                    \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Result iterator columns, when CERT db is not loaded.
 */
#define RESULT_ITERATOR_COLUMNS_NO_CERT                                       \
  {                                                                           \
    BASE_RESULT_ITERATOR_COLUMNS                                              \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { "0",                                                                    \
      NULL,                                                                   \
      KEYWORD_TYPE_INTEGER },                                                 \
    { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
  }

/**
 * @brief Generate the extra_tables string for a result iterator.
 *
 * @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 override, int dynamic)
{
  user_t user_id;
  gchar *user_zone, *quoted_user_zone, *ret;

  if (current_credentials.uuid)
    {
      user_id = sql_int64_0 ("SELECT id FROM users WHERE uuid = '%s';",
                             current_credentials.uuid);
      if (user_id > 0)
        user_zone = sql_string ("SELECT"
                                " coalesce ((SELECT current_setting"
                                "                    ('gvmd.tz_override')),"
                                "           (SELECT timezone FROM users"
                                "            WHERE id = %llu));",
                                user_id);
      else
        user_zone = g_strdup ("UTC");
    }
  else
    {
      user_id = 0;
      user_zone = sql_string ("SELECT"
                              " coalesce ((SELECT current_setting"
                              "                    ('gvmd.tz_override')),"
                              "           'UTC');");
    }

  quoted_user_zone = sql_quote ("user_zone");
  g_free (user_zone);

  ret = g_strdup_printf
         (", (SELECT"
          "   '%s'::text AS user_zone,"
          "   %llu AS user_id,"
          "   %d AS override,"
          "   %d AS dynamic) AS opts",
          quoted_user_zone,
          user_id,
          override,
          dynamic);

  g_free (quoted_user_zone);

  return ret;
}

/**
 * @brief Get new severity clause.
 *
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  dynamic_severity Whether to use dynamic severity.
 *
 * @return Newly allocated clause.
 */
gchar *
new_severity_clause (int apply_overrides, int dynamic_severity)
{
  if (apply_overrides)
    {
      if (dynamic_severity)
        /* Overrides, dynamic. */
        return g_strdup_printf ("(SELECT new_severity FROM result_new_severities_dynamic"
                                " WHERE result_new_severities_dynamic.result = results.id"
                                " AND result_new_severities_dynamic.user"
                                "     = (SELECT id FROM users WHERE uuid = '%s')"
                                " LIMIT 1)",
                                current_credentials.uuid);

      /* Overrides, no dynamic. */
      return g_strdup_printf ("(SELECT new_severity FROM result_new_severities_static"
                              " WHERE result_new_severities_static.result = results.id"
                              " AND result_new_severities_static.user"
                              "     = (SELECT id FROM users WHERE uuid = '%s')"
                              " LIMIT 1)",
                              current_credentials.uuid);
    }

  if (dynamic_severity)
    /* Dynamic, no overrides. */
    return g_strdup ("current_severity (results.severity,"
                     "                  results.nvt)");

  /* No dynamic, no overrides. */
  return g_strdup ("results.severity");
}

/**
 * @brief Get extra_where string for a result iterator or count.
 *
 * @param[in]  trash            Whether to get results from trashcan.
 * @param[in]  report           Report to restrict returned results to.
 * @param[in]  host             Host to restrict returned results to.
 * @param[in]  apply_overrides  Whether to apply overrides.
 * @param[in]  dynamic_severity Whether to use dynamic severity.
 * @param[in]  filter           Filter string.
 * @param[in]  given_new_severity_sql  SQL for new severity, or NULL.
 *
 * @return     Newly allocated extra_where string.
 */
static gchar*
results_extra_where (int trash, report_t report, const gchar* host,
                     int apply_overrides, int dynamic_severity,
                     const gchar *filter, const gchar *given_new_severity_sql)
{
  gchar *extra_where;
  int min_qod;
  gchar *levels, *compliance_levels;
  gchar *report_clause, *host_clause, *min_qod_clause;
  gchar *compliance_levels_clause;
  GString *levels_clause;
  gchar *new_severity_sql;

  // Get filter values
  min_qod = filter_term_min_qod (filter);
  levels = filter_term_value (filter, "levels");
  if (levels == NULL)
    levels = g_strdup ("chmlgdf");

  compliance_levels = filter_term_value (filter, "compliance_levels");

  // Build clause fragments

  if (given_new_severity_sql)
    new_severity_sql = NULL;
  else
    new_severity_sql = new_severity_clause (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);

  levels_clause = where_levels_auto (levels,
                                     given_new_severity_sql
                                      ? given_new_severity_sql
                                      : new_severity_sql);

  compliance_levels_clause = where_compliance_levels (compliance_levels);

  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 && levels_clause->str) ?
                                  levels_clause->str : "",
                                min_qod_clause ? min_qod_clause : "",
                                compliance_levels_clause ?: "");

  g_free (min_qod_clause);
  g_string_free (levels_clause, TRUE);
  g_free (report_clause);
  g_free (host_clause);
  g_free (compliance_levels_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, 2 failed to find filter (filt_id),
 *         -1 error.
 */
static 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[2];
  static column_t static_filterable_columns[]
    = RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE;
  static column_t static_filterable_columns_no_cert[]
    = RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE_NO_CERT;
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  column_t *filterable_columns;
  int ret;
  gchar *filter;
  int apply_overrides, dynamic_severity;
  gchar *extra_tables, *extra_where, *extra_where_single, *opts, *with_clause;
  const gchar *lateral;

  assert (report);

  dynamic_severity = setting_dynamic_severity_int ();

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  apply_overrides
    = filter_term_apply_overrides (filter ? filter : get->filter);

  if (manage_cert_loaded ())
    filterable_columns = column_array_copy (static_filterable_columns);
  else
    filterable_columns = column_array_copy (static_filterable_columns_no_cert);
  column_array_set
   (filterable_columns,
    "type",
    apply_overrides
     ? (dynamic_severity
        /* Overrides, dynamic. */
        ? g_strdup_printf ("severity_to_type"
                           " ((SELECT new_severity FROM result_new_severities_dynamic"
                           "   WHERE result_new_severities_dynamic.result = results.id"
                           "   AND result_new_severities_dynamic.user = opts.user_id"
                           "   LIMIT 1))")
        /* Overrides, no dynamic. */
        : g_strdup_printf ("severity_to_type"
                           " ((SELECT new_severity FROM result_new_severities_static"
                           "   WHERE result_new_severities_static.result = results.id"
                           "   AND result_new_severities_static.user = opts.user_id"
                           "   LIMIT 1))"))
     : (dynamic_severity
         /* Dynamic, no overrides. */
         ? g_strdup ("severity_to_type (current_severity (results.severity,"
                     "                                    results.nvt))")
         /* No dynamic, no overrides. */
         : g_strdup ("severity_to_type (results.severity)")));

  if (dynamic_severity)
    {
      if (apply_overrides)
        lateral
          = "coalesce ((SELECT new_severity FROM valid_overrides"
            "           WHERE valid_overrides.result_nvt"
            "                 = results.result_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 CAST (nvts.cvss_base"
            "                               AS double precision)"
            "                    ELSE results.severity"
            "                    END),"
            "                   results.severity),"
            "                 valid_overrides.severity)"
            "           LIMIT 1),"
            "          coalesce ((CASE WHEN results.severity"
            "                               > " G_STRINGIFY
                                                              (SEVERITY_LOG)
            "                     THEN CAST (nvts.cvss_base"
            "                                AS double precision)"
            "                     ELSE results.severity"
            "                     END),"
            "                    results.severity))";
      else
        lateral
          = "coalesce ((CASE WHEN results.severity"
            "                     > " G_STRINGIFY (SEVERITY_LOG)
            "                THEN CAST (nvts.cvss_base"
            "                           AS double precision)"
            "                ELSE results.severity"
            "                END),"
            "          results.severity)";
    }
  else
    {
      if (apply_overrides)
        lateral
          = "coalesce ((SELECT new_severity FROM valid_overrides"
            "           WHERE valid_overrides.result_nvt"
            "                 = results.result_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
        lateral
          /* coalesce because results.severity gives syntax error. */
          = "coalesce (results.severity, results.severity)";
    }

  columns[0].select = "lateral_severity";
  columns[0].filter = "severity";
  columns[0].type = KEYWORD_TYPE_DOUBLE;

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

  opts = result_iterator_opts_table (apply_overrides,
                                     dynamic_severity);
  if (dynamic_severity)
    extra_tables = g_strdup_printf (" LEFT OUTER JOIN nvts"
                                    " ON results.nvt = nvts.oid,"
                                    " LATERAL %s AS lateral_severity%s",
                                    lateral, opts);
  else
    extra_tables = g_strdup_printf (", LATERAL %s AS lateral_severity%s",
                                    lateral, opts);
  g_free (opts);

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

  extra_where_single = results_extra_where (get->trash, report, host,
                                            apply_overrides,
                                            dynamic_severity,
                                            "min_qod=0",
                                            "lateral_severity");

  free (filter);

  if (apply_overrides)
    {
      gchar *owned_clause, *overrides_with;
      char *user_id;

      user_id = sql_string ("SELECT id FROM users WHERE uuid = '%s';",
                            current_credentials.uuid);
      // Do not get ACL with_clause as it will be added by
      // init_get_iterator2_with.
      owned_clause = acl_where_owned_for_get ("override", user_id,
                                              "valid_overrides_",
                                              &overrides_with);
      free (user_id);

      with_clause = g_strdup_printf
                      (" %s,"
                       " valid_overrides"
                       " AS (SELECT result_nvt, hosts, new_severity, port,"
                       "            severity, result"
                       "     FROM overrides"
                       "     WHERE %s"
                       /*    Only use if override's NVT is in report. */
                       "     AND EXISTS (SELECT * FROM result_nvt_reports"
                       "                 WHERE report = %llu"
                       "                 AND result_nvt"
                       "                     = overrides.result_nvt)"
                       "     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)"
                       " ",
                       overrides_with + strlen ("WITH "),
                       owned_clause,
                       report,
                       report);
      g_free (overrides_with);
      g_free (owned_clause);
    }
  else
    with_clause = NULL;

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

/**
 * @brief SQL for getting current severity.
 */
#define CURRENT_SEVERITY_SQL                                            \
  "coalesce ((CASE WHEN %s.severity > " G_STRINGIFY (SEVERITY_LOG)      \
  "           THEN CAST (%s.cvss_base AS double precision)"             \
  "           ELSE %s.severity"                                         \
  "           END),"                                                    \
  "          %s.severity)"

/**
 * @brief Get LATERAL clause for result iterator.
 *
 * @param[in]  apply_overrides   Whether to apply overrides.
 * @param[in]  dynamic_severity  Whether to use dynamic severity.
 * @param[in]  nvts_table        NVTS table.
 * @param[in]  results_table     Results table.
 *
 * @return SQL clause for FROM.
 */
static gchar *
result_iterator_lateral (int apply_overrides,
                         int dynamic_severity,
                         const char *results_table,
                         const char *nvts_table)
{
  if (apply_overrides && dynamic_severity)
    /* Overrides, dynamic. */
    return g_strdup_printf(
      " (WITH curr AS (SELECT " CURRENT_SEVERITY_SQL " AS curr_severity)"
      " SELECT coalesce ((SELECT ov_new_severity FROM result_overrides"
      "                   WHERE result = %s.id"
      "                   AND result_overrides.user = opts.user_id"
      "                   AND severity_matches_ov"
      "                        ((SELECT curr_severity FROM curr LIMIT 1),"
      "                         ov_old_severity)"
      "                   LIMIT 1),"
      "                  (SELECT curr_severity FROM curr LIMIT 1))"
      " AS new_severity)",
      results_table,
      nvts_table,
      results_table,
      results_table,
      results_table);

  if (apply_overrides)
    /* Overrides, no dynamic. */
    return g_strdup_printf(
      "(SELECT new_severity"
      " FROM result_new_severities_static"
      " WHERE result_new_severities_static.result = %s.id"
      " AND result_new_severities_static.user = opts.user_id"
      " LIMIT 1)",
      results_table);

  if (dynamic_severity)
    /* No overrides, dynamic. */
    return g_strdup_printf("(SELECT " CURRENT_SEVERITY_SQL " AS new_severity)",
                           results_table,
                           nvts_table,
                           results_table,
                           results_table);
  /* No overrides, no dynamic.
   *
   * SELECT because results.severity gives syntax error. */
  return g_strdup_printf("(SELECT %s.severity AS new_severity)", results_table);
}

/**
 * @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, 2 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;
  static column_t columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT;
  int ret;
  gchar *filter, *extra_tables, *extra_where, *extra_where_single;
  gchar *opts_tables, *lateral_clause;
  int apply_overrides, dynamic_severity;
  column_t *actual_columns;

  g_debug ("%s", __func__);

  if (report == -1)
    {
      init_iterator (iterator, "SELECT NULL WHERE false;");
      return 0;
    }

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  apply_overrides
    = filter_term_apply_overrides (filter ? filter : get->filter);
  dynamic_severity = setting_dynamic_severity_int ();

  if (manage_cert_loaded ())
    actual_columns = columns;
  else
    actual_columns = columns_no_cert;

  opts_tables = result_iterator_opts_table (apply_overrides, dynamic_severity);

  lateral_clause = result_iterator_lateral (apply_overrides,
                                            dynamic_severity,
                                            "results",
                                            "nvts");

  extra_tables = g_strdup_printf (" LEFT OUTER JOIN result_vt_epss"
                                  " ON results.nvt = result_vt_epss.vt_id"
                                  " LEFT OUTER JOIN nvts"
                                  " ON results.nvt = nvts.oid %s,"
                                  " LATERAL %s AS lateral_new_severity",
                                  opts_tables,
                                  lateral_clause);
  g_free (opts_tables);
  g_free (lateral_clause);

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

  extra_where_single = results_extra_where (get->trash, report, host,
                                            apply_overrides,
                                            dynamic_severity,
                                            "min_qod=0",
                                            NULL);

  free (filter);

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

  g_debug ("%s: done", __func__);

  return ret;
}

/**
 * @brief Initialise a result iterator not limited to report or host.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find result, 2 failed to find filter (filt_id),
 *         -1 error.
 */
int
init_result_get_iterator_all (iterator_t* iterator, get_data_t *get)
{
  return init_result_get_iterator (iterator, get, 0, NULL, NULL);
}

/**
 * @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;
  static column_t columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT;
  int ret;
  gchar *filter, *extra_tables, *extra_where, *opts_tables, *lateral_clause;
  int apply_overrides, dynamic_severity;

  if (report == -1)
    return 0;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  apply_overrides
    = filter_term_apply_overrides (filter ? filter : get->filter);
  dynamic_severity = setting_dynamic_severity_int ();

  opts_tables = result_iterator_opts_table (apply_overrides, dynamic_severity);

  lateral_clause = result_iterator_lateral (apply_overrides,
                                            dynamic_severity,
                                            "results",
                                            "nvts");

  extra_tables = g_strdup_printf (" LEFT OUTER JOIN result_vt_epss"
                                  " ON results.nvt = result_vt_epss.vt_id"
                                  " LEFT OUTER JOIN nvts"
                                  " ON results.nvt = nvts.oid %s,"
                                  " LATERAL %s AS lateral_new_severity",
                                  opts_tables,
                                  lateral_clause);
  g_free (opts_tables);
  g_free (lateral_clause);

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

  ret = count ("result", get,
                manage_cert_loaded () ? columns : columns_no_cert,
                manage_cert_loaded () ? columns : columns_no_cert,
                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 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 + 5);

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

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

/**
 * @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.
 */
DEF_ACCESS (result_iterator_nvt_cvss_base, 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 + 9);
  return ret ? ret : "";
}

/**
 * @brief Get the original severity from a result iterator.
 *
 * This is the original severity without overrides.
 *
 * @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 + 10);
  return ret ? ret : "";
}

/**
 * @brief Get the original severity/threat level from a result iterator.
 *
 * This is the original level without overrides.
 *
 * @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 + 10))
    return NULL;

  /* severity */
  severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 10);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}

/**
 * @brief Get the severity from a result iterator.
 *
 * This is the the 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;

  /* new_severity */
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11);
  return ret ? ret : "";
}

/**
 * @brief Get the severity from a result iterator as double.
 *
 * This is the the 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;

  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 11);
}

/**
 * @brief Get the severity/threat level from a result iterator.
 *
 * This is the the 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 "";

  /* new_severity */
  if (iterator_null (iterator, GET_ITERATOR_COLUMN_COUNT + 11))
    return "";

  severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 11);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}

/**
 * @brief Get the solution type from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The solution type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_solution_type, GET_ITERATOR_COLUMN_COUNT + 15);

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

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

/**
 * @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_hostname, GET_ITERATOR_COLUMN_COUNT + 18);

/**
 * @brief Get the path from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The path of the result.  Caller must only use before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (result_iterator_path, GET_ITERATOR_COLUMN_COUNT + 21);

/**
 * @brief Get the asset host ID from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The ID of the asset host.  Caller must only use before
 *         calling cleanup_iterator.
 */
DEF_ACCESS (result_iterator_asset_host_id, GET_ITERATOR_COLUMN_COUNT + 22);

/**
 * @brief Get whether notes may exist from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if notes may exist, else 0.
 */
int
result_iterator_may_have_notes (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 23);
}

/**
 * @brief Get whether overrides may exist from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if overrides may exist, else 0.
 */
int
result_iterator_may_have_overrides (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 24);
}

/**
 * @brief Get whether tickets may exist from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if notes may exist, else 0.
 */
int
result_iterator_may_have_tickets (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 25);
}

/**
 * @brief Get the NVT summary from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The summary of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_summary, GET_ITERATOR_COLUMN_COUNT + 27);

/**
 * @brief Get the NVT insight from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The insight of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_insight, GET_ITERATOR_COLUMN_COUNT + 28);

/**
 * @brief Get the NVT affected from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The affected of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_affected, GET_ITERATOR_COLUMN_COUNT + 29);

/**
 * @brief Get the NVT impact from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Impact text of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_impact, GET_ITERATOR_COLUMN_COUNT + 30);

/**
 * @brief Get the NVT solution from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The solution of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_solution, GET_ITERATOR_COLUMN_COUNT + 31);

/**
 * @brief Get the NVT detection from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The detection of the NVT that produced the result, or NULL on error.
 */
DEF_ACCESS (result_iterator_nvt_detection, GET_ITERATOR_COLUMN_COUNT + 32);

/**
 * @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.
 */
DEF_ACCESS (result_iterator_nvt_family, GET_ITERATOR_COLUMN_COUNT + 33);

/**
 * @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.
 */
DEF_ACCESS (result_iterator_nvt_tag, GET_ITERATOR_COLUMN_COUNT + 34);

/**
 * @brief Get compliance status from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The compliance status (yes, no, incomplete or undefined).
 */
const char *
result_iterator_compliance (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 35);
}

/**
 * @brief Get EPSS score of highest severity CVE from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return EPSS score of the highest severity CVE.
 */
double
result_iterator_epss_score (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 36);
}

/**
 * @brief Get EPSS percentile of highest severity CVE from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return EPSS percentile of the highest severity CVE.
 */
double
result_iterator_epss_percentile (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 37);
}

/**
 * @brief Get highest severity CVE with EPSS score from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Highest severity CVE with EPSS score.
 */
const gchar *
result_iterator_epss_cve (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 38);
}

/**
 * @brief Get the highest severity of EPSS CVEs from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Highest severity of referenced CVEs with EPSS.
 */
double
result_iterator_epss_severity (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 39);
}

/**
 * @brief Get maximum EPSS score of referenced CVEs from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Maximum EPSS score.
 */
double
result_iterator_max_epss_score (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 40);
}

/**
 * @brief Get maximum EPSS percentile of referenced CVEs from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Maximum EPSS percentile.
 */
double
result_iterator_max_epss_percentile (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 41);
}

/**
 * @brief Get the CVE with the maximum EPSS score from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return CVE with maximum EPSS score.
 */
const gchar *
result_iterator_max_epss_cve (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 42);
}

/**
 * @brief Get severity of CVE with maximum EPSS score from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Severity of CVE with maximum EPSS score.
 */
double
result_iterator_max_epss_severity (iterator_t* iterator)
{
  if (iterator->done) return 0.0;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 43);
}

/**
 * @brief Get CERT-BUNDs from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return CERT-BUND names if any, else NULL.
 */
gchar **
result_iterator_cert_bunds (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_array (iterator, GET_ITERATOR_COLUMN_COUNT + 44);
}

/**
 * @brief Get DFN-CERTs from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return DFN-CERT names if any, else NULL.
 */
gchar **
result_iterator_dfn_certs (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_array (iterator, GET_ITERATOR_COLUMN_COUNT + 45);
}

/**
 * @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)
{
  return get_iterator_name (iterator);
}

/**
 * @brief Get the NVT solution_type from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The solution_type of the NVT that produced the result,
 *         or NULL on error.
 */
const char*
result_iterator_nvt_solution_type (iterator_t *iterator)
{
  return result_iterator_solution_type (iterator);
}

/**
 * @brief Get the NVT solution_method from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The solution_method of the NVT that produced the result,
 *         or NULL on error.
 */
const char*
result_iterator_nvt_solution_method (iterator_t *iterator)
{
  /* When we used a cache this was never added to the cache. */
  return NULL;
}

/**
 * @brief Get delta reports state from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta reports state if any, else NULL.
 */
const char *
result_iterator_delta_state (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET);
}

/**
 * @brief Get delta description from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta description if any, else NULL.
 */
const char *
result_iterator_delta_description (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 1);
}

/**
 * @brief Get delta severity from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta severity if any, else NULL.
 */
const char *
result_iterator_delta_original_severity (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 2);
}

/**
 * @brief Get delta severity (double) from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta severity (double) if any, else 0.
 */
double
result_iterator_delta_original_severity_double (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_double (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 2);
}

/**
 * @brief Get the severity/threat level from a delta result iterator.
 *
 * This is the the original level.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The threat level of the delta result.  Caller must only use before
 *         calling cleanup_iterator.
 */
const char*
result_iterator_delta_original_level (iterator_t* iterator)
{
  double severity;
  const char* ret;

  if (iterator->done)
    return "";

  /* new_severity */
  if (iterator_null (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 2))
    return "";

  severity = iterator_double (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 2);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}

/**
 * @brief Get delta qod from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta qod if any, else NULL.
 */
const char *
result_iterator_delta_qod (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 3);
}

/**
 * @brief Get delta uuid from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta uuid if any, else NULL.
 */
const char *
result_iterator_delta_uuid (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 4);
}


/**
 * @brief Get delta qod type from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta qod type if any, else NULL.
 */
const char *
result_iterator_delta_qod_type (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 5);
}

/**
 * @brief Get delta creation time from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time, or 0 if iteration is complete.
 */
time_t
result_iterator_delta_creation_time (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 6);
}

/**
 * @brief Get delta modification time from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Time, or 0 if iteration is complete.
 */
time_t
result_iterator_delta_modification_time (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 7);
}

/**
 * @brief Get delta task from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta task if any, else 0.
 */
task_t
result_iterator_delta_task (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 8);
}

/**
 * @brief Get delta report from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta report if any, else 0.
 */
report_t
result_iterator_delta_report (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 9);
}

/**
 * @brief Get delta owner from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta owner if any, else NULL.
 */
const char *
result_iterator_delta_owner_name (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 10);
}

/**
 * @brief Get delta path from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta path if any, else NULL.
 */
const char *
result_iterator_delta_path (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 11);
}

/**
 * @brief Get delta host asset id from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta host asset id if any, else NULL.
 */
const char *
result_iterator_delta_host_asset_id (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 12);
}

/**
 * @brief Get delta nvt version from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta nvt version if any, else NULL.
 */
const char *
result_iterator_delta_nvt_version (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 13);
}

/**
 * @brief Get delta result from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta result if any, else 0.
 */
result_t
result_iterator_delta_result (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 14);
}

/**
 * @brief Get whether there are notes for the delta result from the iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return whether there are notes.
 */
int
result_iterator_delta_may_have_notes (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 15);
}

/**
 * @brief Get whether there are overrides for the delta result from the iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return whether there are overrides.
 */
int
result_iterator_delta_may_have_overrides (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 16);
}

/**
 * @brief Get whether there are tickets for the delta result from the iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return whether there are tickets.
 */
int
result_iterator_delta_may_have_tickets (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 17);
}

/**
 * @brief Get delta hostname from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta hostname if any, else NULL.
 */
const char *
result_iterator_delta_hostname (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 18);
}


/**
 * @brief Get delta severity from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta severity if any, else NULL.
 */
const char *
result_iterator_delta_severity (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 19);
}

/**
 * @brief Get delta severity (double) from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta severity (double) if any, else 0.
 */
double
result_iterator_delta_severity_double (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_double (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 19);
}

/**
 * @brief Get delta compliance from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return delta compliance if any, else NULL.
 */
const char *
result_iterator_delta_compliance (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_string (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 20);
}

/**
 * @brief Get the severity/threat level from a delta result iterator.
 *
 * This is the the overridden level.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The threat level of the delta result.  Caller must only use before
 *         calling cleanup_iterator.
 */
const char*
result_iterator_delta_level (iterator_t* iterator)
{
  double severity;
  const char* ret;

  if (iterator->done)
    return "";

  /* new_severity */
  if (iterator_null (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 19))
    return "";

  severity = iterator_double (iterator, RESULT_ITERATOR_DELTA_COLUMN_OFFSET + 19);

  ret = severity_to_level (severity, 0);
  return ret ? ret : "";
}


/**
 * @brief Append an NVT's references to an XML string buffer.
 *
 * @param[in]  xml       The buffer where to append to.
 * @param[in]  oid       The oid of the nvti object from where to collect the refs.
 * @param[in]  first     Marker for first element.
 */
void
xml_append_nvt_refs (GString *xml, const char *oid, int *first)
{
  nvti_t *nvti = lookup_nvti (oid);
  int i;

  if (!nvti)
    return;

  for (i = 0; i < nvti_vtref_len (nvti); i++)
    {
      vtref_t *ref;

      if (first && *first)
        {
          xml_string_append (xml, "<refs>");
          *first = 0;
        }

      ref = nvti_vtref (nvti, i);
      xml_string_append (xml, "<ref type=\"%s\" id=\"%s\"/>", vtref_type (ref), vtref_id (ref));
    }
}

/**
 * @brief Check if the result_nvts are assigned to result
 *
 * @return 0 success, -1 error
 */
int
cleanup_result_nvts ()
{
  iterator_t affected_iter;
  GArray *affected;
  int index;

  g_debug ("%s: Cleaning up results with wrong nvt ids", __func__);
  sql ("UPDATE results"
       " SET nvt = (SELECT oid FROM nvts WHERE name = nvt),"
       "     result_nvt = NULL"
       " WHERE nvt IN (SELECT name FROM nvts WHERE name != oid);");

  g_debug ("%s: Cleaning up result_nvts entries with wrong nvt ids",
           __func__);
  sql ("DELETE FROM result_nvts"
       " WHERE nvt IN (SELECT name FROM nvts WHERE name != oid);");

  g_debug ("%s: Creating missing result_nvts entries", __func__);
  sql ("INSERT INTO result_nvts (nvt)"
       " SELECT DISTINCT nvt FROM results ON CONFLICT (nvt) DO NOTHING;");

  // Get affected reports with overrides
  affected = g_array_new (TRUE, TRUE, sizeof (report_t));
  init_iterator (&affected_iter,
                 "SELECT DISTINCT report FROM results"
                 " WHERE (result_nvt IS NULL"
                 "        OR report NOT IN"
                 "           (SELECT report FROM result_nvt_reports"
                 "             WHERE result_nvt IS NOT NULL))"
                 "   AND nvt IN (SELECT nvt FROM overrides);");
  while (next (&affected_iter))
    {
      report_t report;
      report = iterator_int64 (&affected_iter, 0);
      g_array_append_val (affected, report);
    }
  cleanup_iterator(&affected_iter);

  g_debug ("%s: Adding missing result_nvt values to results", __func__);
  sql ("UPDATE results"
       " SET result_nvt"
       "       = (SELECT id FROM result_nvts"
       "           WHERE result_nvts.nvt = results.nvt)"
       " WHERE result_nvt IS NULL");

  g_debug ("%s: Cleaning up NULL result_nvt_reports entries", __func__);
  sql ("DELETE FROM result_nvt_reports WHERE result_nvt IS NULL;");

  g_debug ("%s: Adding missing result_nvt_reports entries", __func__);
  sql ("INSERT INTO result_nvt_reports (report, result_nvt)"
       " SELECT DISTINCT report, result_nvts.id FROM results"
       "   JOIN result_nvts ON result_nvts.nvt = results.nvt"
       "  WHERE report NOT IN (SELECT report FROM result_nvt_reports"
       "                       WHERE result_nvt IS NOT NULL)");

  // Re-cache affected reports with overrides
  for (index = 0; index < affected->len; index++)
    {
      report_t report;
      report = g_array_index (affected, report_t, index);
      g_debug ("%s: Updating cache of affected report %llu",
               __func__, report);
      report_cache_counts (report, 0, 1, NULL);
    }
  g_array_free (affected, TRUE);

  return 0;
}

/**
 * @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)
    {
      init_ps_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 ($1 = 0 OR id = $1)"
                        "   AND report = $2"
                        "   AND ($3::text IS NULL OR host = $3)"
                        " ORDER BY order_inet (host);",
                        SQL_RESOURCE_PARAM (report_host),
                        SQL_RESOURCE_PARAM (report),
                        host ? SQL_STR_PARAM (host) : SQL_NULL_PARAM,
                        NULL);
    }
  else
    {
      init_ps_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 ($1 = 0 OR id = $1)"
                        "   AND ($2::text IS NULL OR host = $2)"
                        " ORDER BY order_inet (host);",
                        SQL_RESOURCE_PARAM (report_host),
                        host ? SQL_STR_PARAM (host) : SQL_NULL_PARAM,
                        NULL);
    }
}


/**
 * @brief Get the report host from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Report host.
 */
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 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.
 */
static
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.severity = %0.1f"
                   "  AND results.report = %llu",
                   SEVERITY_ERROR, 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.
 */
static
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.
 */
static
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.
 */
static
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.
 */
static
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.
 */
static
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.
 */
static
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.
 */
static
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.
 */
static
DEF_ACCESS (report_errors_iterator_severity, 7);

/**
 * @brief Get the result from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Result.
 */
static 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.
 */
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')"
                 " AND NOT name LIKE 'detected_by@%%'"
                 " UNION SELECT 0, 'Closed CVE', cve, 'gvmd', oid,"
                 "              nvts.name, cvss_base"
                 "       FROM nvts, report_host_details"
                 "       WHERE cve != ''"
                 "       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.
 */
static
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.
 */
static
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.
 */
static
DEF_ACCESS (report_host_details_iterator_extra, 6);

/**
 * @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.
 */
static 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. 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 UTC ctime format.
 */
void
set_scan_start_time_ctime (report_t report, const char* timestamp)
{
  sql ("UPDATE reports SET start_time = %i WHERE id = %llu;",
       parse_utc_ctime (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.
 */
static 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.  In UTC ctime format.  If NULL, clear end
 *                        time.
 */
void
set_scan_end_time_ctime (report_t report, const char* timestamp)
{
  if (timestamp)
    sql ("UPDATE reports SET end_time = %i WHERE id = %llu;",
         parse_utc_ctime (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.
 *
 * @return End time.
 */
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.  In UTC ctime format.
 */
void
set_scan_host_end_time_ctime (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_utc_ctime (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, 0, parse_utc_ctime (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.  In UTC ctime format.
 */
void
set_scan_host_start_time_ctime (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_utc_ctime (timestamp), report, quoted_host);
  else
    manage_report_host_add (report, host, parse_utc_ctime (timestamp), 0);
  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. In ISO format.
 */
void
set_scan_host_end_time_isotime (report_t report, const char* host,
                                const char* timestamp)
{
  gchar *quoted_host;
  int end_time;

  if (host == NULL || timestamp == NULL)
    return;

  end_time = parse_iso_time (timestamp);
  if (end_time <= 0)
    return;

  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';",
         end_time, report, quoted_host);
  else
    manage_report_host_add (report, host, 0, end_time);
  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. In ISO format.
 */
void
set_scan_host_start_time_isotime (report_t report, const char* host,
                                  const char* timestamp)
{
  gchar *quoted_host;
  int start_time;

  if (host == NULL || timestamp == NULL)
    return;

  start_time = parse_iso_time (timestamp);
  if (start_time <= 0)
    return;

  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';",
         start_time, report, quoted_host);
  else
    manage_report_host_add (report, host, start_time, 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 creation_time FROM reports where uuid = '%s';",
                         report_id);
  stamp = iso_time (&time);
  if (stamp == NULL) return -1;
  *timestamp = g_strdup (stamp);
  return 0;
}

/**
 * @brief Get the creation time of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Time in ISO format.
 */
gchar *
report_creation_time (report_t report)
{
  return sql_string ("SELECT iso_time (creation_time)"
                     " FROM reports"
                     " WHERE id = %llu",
                     report);
}


/**
 * @brief Get the modification time of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Time in ISO format.
 */
gchar *
report_modification_time (report_t report)
{
  return sql_string ("SELECT iso_time (modification_time)"
                     " FROM reports"
                     " WHERE id = %llu",
                     report);
}

/**
 * @brief Get the start time of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Time in ISO format.
 */
gchar *
report_start_time (report_t report)
{
  return sql_string ("SELECT iso_time (start_time)"
                     " FROM reports"
                     " WHERE id = %llu",
                     report);
}

/**
 * @brief Get the end time of a report.
 *
 * @param[in]  report  Report.
 *
 * @return Time in ISO format.
 */
gchar *
report_end_time (report_t report)
{
  return sql_string ("SELECT iso_time (end_time)"
                     " FROM reports"
                     " WHERE id = %llu",
                     report);
}

/**
 * @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.
 */
static int
report_scan_run_status (report_t report, task_status_t* 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,"
       " modification_time = m_now() WHERE id = %llu;",
       status,
       report);
  if (setting_auto_cache_rebuild_int ())
    report_cache_counts (report, 0, 0, NULL);
  return 0;
}

/**
 * @brief Update modification_time of a report to current time.
 *
 * @param[in]   report  Report.
 *
 * @return 0.
 */
int
update_report_modification_time (report_t report)
{
  sql("UPDATE reports SET modification_time = m_now() WHERE id = %llu;",
      report);

  return 0;
}

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

  if (report == 0)
    return;

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

  apply_overrides
    = filter_term_apply_overrides (filter ? filter : get->filter);

  if (severity_data)
    {
      get_data_t *get_all;

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

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

          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;
      get_filtered.ignore_max_rows_per_page = 1;

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

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

          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]  criticals    Number of critical 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]   min_qod      Min QOD.
 *
 * @return 0 on success, -1 on error.
 */
int
report_counts (const char* report_id,
               int* criticals,
               int* holes,
               int* infos,
               int* logs,
               int* warnings,
               int* false_positives,
               double* severity,
               int override, 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, min_qod);
  ret = report_counts_id (report, criticals, 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)
{
  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.
 *
 * @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 i, ret;
  double severity;
  int end_time;

  /* Try cache results. */

  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)
    {
      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)
        {
          return ret;
        }
    }
  else
    {
      GString *insert;
      int first;

      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);
      insert = g_string_new ("INSERT INTO report_counts"
                             " (report, \"user\", override, min_qod,"
                             "  severity, count, end_time)"
                             " VALUES");
      first = 1;
      while (severity <= (data->max + (1.0
                                       / SEVERITY_SUBDIVISIONS
                                       / SEVERITY_SUBDIVISIONS))
             && severity != SEVERITY_MISSING)
        {
          if (data->counts[i] > 0)
            {
              g_string_append_printf (insert,
                                      "%s (%llu,"
                                      "    (SELECT id FROM users"
                                      "     WHERE users.uuid = '%s'),"
                                      "    %d, %d, %1.1f, %d, %d)",
                                      first == 1 ? "" : ",",
                                      report, current_credentials.uuid,
                                      override, min_qod, severity,
                                      data->counts[i], end_time);
              first = 0;
            }
          i++;
          severity = severity_data_value (i);
        }

      if (i)
        {
          g_string_append_printf (insert, ";");
          ret = sql_giveup ("%s", insert->str);
          if (ret)
            {
              g_string_free (insert, TRUE);
              return ret;
            }
        }
      g_string_free (insert, TRUE);
    }
  return 0;
}

/**
 * @brief Get the message counts for a report.
 *
 * @param[in]   report    Report.
 * @param[out]  criticals Number of critical 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_criticals Number of critical 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* criticals,
                       int* holes,
                       int* infos,
                       int* logs,
                       int* warnings,
                       int* false_positives,
                       double* severity,
                       const get_data_t* get,
                       const char* host,
                       int* filtered_criticals,
                       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;
  int override, min_qod_int;
  severity_data_t severity_data, filtered_severity_data;

  unfiltered_requested = (criticals || holes || warnings || infos || logs || false_positives
                          || severity);
  filtered_requested = (filtered_criticals || filtered_holes || filtered_warnings
                        || filtered_infos  || filtered_logs
                        || filtered_false_positives || filtered_severity);

  if (current_credentials.uuid == NULL
      || strcmp (current_credentials.uuid, "") == 0)
    g_warning ("%s: called by NULL or dummy user", __func__);

  if (get->filt_id && strlen (get->filt_id)
      && strcmp (get->filt_id, FILT_ID_NONE))
    {
      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
        {
          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);

  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,
                              NULL, false_positives,
                              logs, infos, warnings, holes, criticals);
  severity_data_level_counts (&filtered_severity_data,
                              NULL, filtered_false_positives,
                              filtered_logs, filtered_infos,
                              filtered_warnings, filtered_holes, filtered_criticals);

  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);
      if (filtered_requested)
        cache_report_counts (report, override, min_qod_int,
                             &filtered_severity_data);
    }

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

  return 0;
}

/**
 * @brief Get the compliance state from compliance counts.
 *
 * @param[in]  yes_count         Compliant results count.
 * @param[in]  no_count          Incompliant results count.
 * @param[in]  incomplete_count  Incomplete results count.
 * @param[in]  undefined_count   Undefined results count.
 *
 * @return 0 on success, -1 on error.
 */
const char *
report_compliance_from_counts (const int* yes_count,
                               const int* no_count,
                               const int* incomplete_count,
                               const int* undefined_count)
{
  if (no_count && *no_count > 0)
    {
      return "no";
    }
  else if (incomplete_count && *incomplete_count > 0)
    {
      return "incomplete";
    }
  else if (yes_count && *yes_count > 0)
    {
      return "yes";
    }

  return "undefined";
}


/**
 * @brief Get the compliance filtered counts for a report.
 *
 * @param[in]   report               Report.
 * @param[in]   get                  Get data.
 * @param[out]  f_compliance_yes     Compliant results count after filtering.
 * @param[out]  f_compliance_no      Incompliant results count after filtering.
 * @param[out]  f_compliance_incomplete  Incomplete results count
 *                                       after filtering.
 * @param[out]  f_compliance_undefined   Undefined results count
 *                                       after filtering.
 *
 * @return 0 on success, -1 on error.
 */
static int
report_compliance_f_counts (report_t report,
                            const get_data_t* get,
                            int* f_compliance_yes,
                            int* f_compliance_no,
                            int* f_compliance_incomplete,
                            int* f_compliance_undefined)
{
  if (report == 0)
    return -1;

  get_data_t get_filtered;
  iterator_t results;
  int yes_count, no_count, incomplete_count, undefined_count;

  yes_count = no_count = incomplete_count = undefined_count = 0;

  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;
  get_filtered.ignore_max_rows_per_page = 1;

  init_result_get_iterator (&results, &get_filtered, report, NULL,
                            NULL);
  while (next (&results))
    {
      const char* compliance;

      compliance = result_iterator_compliance (&results);

      if (strcasecmp (compliance, "yes") == 0)
        {
          yes_count++;
        }
      else if (strcasecmp (compliance, "no") == 0)
        {
          no_count++;
        }
      else if (strcasecmp (compliance, "incomplete") == 0)
        {
          incomplete_count++;
        }
      else if (strcasecmp (compliance, "undefined") == 0)
        {
          undefined_count++;
        }

    }

  if (f_compliance_yes)
      *f_compliance_yes = yes_count;
  if (f_compliance_no)
      *f_compliance_no = no_count;
  if (f_compliance_incomplete)
      *f_compliance_incomplete = incomplete_count;
  if (f_compliance_undefined)
      *f_compliance_undefined = undefined_count;

  cleanup_iterator (&results);

  return 0;
}

/**
 * @brief Get the compliance counts for a report.
 *
 * @param[in]   report    Report.
 * @param[in]   get       Get data.
 * @param[out]  compliance_yes          Compliant results count.
 * @param[out]  compliance_no           Incompliant results count.
 * @param[out]  compliance_incomplete   Incomplete results count.
 * @param[out]  compliance_undefined    Undefined results count.
 *
 * @return 0 on success, -1 on error.
 */
static int
report_compliance_counts (report_t report,
                          const get_data_t* get,
                          int* compliance_yes,
                          int* compliance_no,
                          int* compliance_incomplete,
                          int* compliance_undefined)
{
  if (report == 0)
    return -1;

  report_compliance_by_uuid (report_uuid(report),
                             compliance_yes,
                             compliance_no,
                             compliance_incomplete,
                             compliance_undefined);

  return 0;
}

/**
 * @brief Get only the filtered message counts for a report.
 *
 * @param[in]   report    Report.
 * @param[out]  criticals Number of critical 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* criticals,
                  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, criticals, 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;

  if (report == 0)
    return SEVERITY_MISSING;

  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", __func__,
               iterator_string (&iterator, 0));
      severity = iterator_double (&iterator, 0);
    }
  else
    {
      g_debug ("%s: could not get max from cache", __func__);
      get_data_t *get = report_results_get_data (1, -1, overrides, 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, 2 report is in use, -1 error.
 */
int
delete_report_internal (report_t report)
{
  task_t task;

  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_QUEUED,
               TASK_STATUS_REQUESTED,
               TASK_STATUS_DELETE_REQUESTED,
               TASK_STATUS_DELETE_ULTIMATE_REQUESTED,
               TASK_STATUS_STOP_REQUESTED,
               TASK_STATUS_STOP_WAITING))
    return 2;

  /* This needs to have exclusive access to reports because otherwise at this
   * point another process (like a RESUME_TASK handler) could store the report
   * ID and then start trying to access that report after we've deleted it. */

  if (report_task (report, &task))
    return -1;

  /* 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 ("DELETE FROM tag_resources"
       " WHERE resource_type = 'result'"
       "   AND resource IN"
       "         (SELECT id FROM results WHERE report = %llu);",
       report);
  sql ("DELETE FROM tag_resources_trash"
       " WHERE resource_type = 'result'"
       "   AND resource IN"
       "         (SELECT id FROM results WHERE report = %llu);",
       report);
  sql ("DELETE FROM results WHERE report = %llu;", report);
  sql ("DELETE FROM results_trash WHERE report = %llu;", report);

  sql ("DELETE FROM tag_resources"
       " WHERE resource_type = 'report'"
       "   AND resource = %llu;",
       report);
  sql ("DELETE FROM tag_resources_trash"
       " WHERE resource_type = 'report'"
       "   AND resource = %llu;",
       report);
  sql ("DELETE FROM report_counts WHERE report = %llu;", report);
  sql ("DELETE FROM result_nvt_reports WHERE report = %llu;", report);
  sql ("DELETE FROM reports WHERE id = %llu;", report);

  /* Adjust permissions. */

  permissions_set_orphans ("report", report, LOCATION_TABLE);
  tags_remove_resource ("report", report, LOCATION_TABLE);
  tickets_remove_report (report);

  /* Update the task state. */

  switch (sql_int64 (&report,
                     "SELECT max (id) FROM reports WHERE task = %llu",
                     task))
    {
      case 0:
        if (report)
          {
            task_status_t 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 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, 3 reports table is locked,
 *         99 permission denied, -1 error.
 */
int
delete_report (const char *report_id, int dummy)
{
  report_t report;
  int ret, lock_ret, lock_retries;

  sql_begin_immediate ();

  /* This prevents other processes (in particular a RESUME_TASK) from getting
   * a reference to the report ID, and then using that reference to try access
   * the deleted report.
   *
   * If the report is running already then delete_report_internal will
   * ROLLBACK. */
  lock_retries = LOCK_RETRIES;
  lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
  while ((lock_ret == 0) && (lock_retries > 0))
    {
      lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
      lock_retries--;
    }
  if (lock_ret == 0)
    {
      sql_rollback ();
      return 3;
    }

  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)
    {
      if (find_trash_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.
 */
static 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 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 gmp.c! */
void buffer_results_xml (GString *, iterator_t *, task_t, int, int, int,
                         int, int, int, int, const char *, iterator_t *,
                         int, int, int, int);

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

/**
 * @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", __func__,
          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);
    }
  else
    {
      g_free (severity);
    }
}

/**
 * @brief Print delta host ports.
 *
 * @param[in]  key     Port.
 * @param[in]  value   Threat.
 * @param[in]  data    Host and stream.
 *
 * @return Always FALSE.
 */
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", __func__,
          (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.
 *
 * @return Always FALSE.
 */
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", __func__, (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.
 *
 * @return Always FALSE.
 */
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.
 *
 * @return Always FALSE.
 */
static gboolean
print_host_ports_desc (gpointer key, gpointer value, gpointer stream)
{
  guint index;
  array_t *ports;

  g_debug ("   delta: %s: host %s", __func__, (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.
 *
 * @return 1 one greater, -1 two greater, 0 equal.
 */
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.
 *
 * @return 1 one less, -1 two less, 0 equal.
 */
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.
 *
 * @return Always FALSE.
 */
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", __func__, (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.
 *
 * @return Always FALSE.
 */
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.
 *
 * @return Always FALSE.
 */
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.
 *
 * @return Always FALSE.
 */
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.
 *
 * @return N'th last report_host.
 */
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 > 0"
                     " 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 a report's total number of hosts.
 *
 * @param[in]  report  Report.
 *
 * @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.
 *
 * Ignores port entries in "general/..." form.
 *
 * @param[in]  report  Report.
 *
 * @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 report's total number of closed cves.
 *
 * @param[in]  report  Report.
 *
 * @return Closed CVE count.
 */
static int
report_closed_cve_count (report_t report)
{
  return sql_int (" SELECT count(id) FROM nvts"
                  " WHERE cve != ''"
                  " 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.
 *
 * @param[in]  report  Report.
 *
 * @return Vulnerabilities count.
 */
static int
report_vuln_count (report_t report)
{
  return sql_int ("SELECT count (DISTINCT nvt) FROM results"
                  " WHERE report = %llu"
                  " AND severity != " G_STRINGIFY (SEVERITY_ERROR) ";",
                  report);
}

/**
 * @brief Count a report's total number of detected Operating Systems.
 *
 * @param[in]  report  Report.
 *
 * @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.
 *
 * @param[in]  report  Report.
 *
 * @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.
 *
 * @param[in]  report  Report.
 *
 * @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.
 *
 * @param[in]  report  Report.
 *
 * @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 Get a list string of finished hosts in a report.
 *
 * @param[in]  report  The report to get the finished hosts from.
 *
 * @return String containing finished hosts as comma separated list.
 */
char *
report_finished_hosts_str (report_t report)
{
  char *ret;

  ret = sql_string ("SELECT string_agg (host, ',' ORDER BY host)"
                    " FROM report_hosts"
                    " WHERE report = %llu"
                    "   AND end_time != 0;",
                    report);

  return ret;
}

/**
 * @brief Get a list string of finished OCI container images in a report.
 *
 * @param[in]  report  The report to get the finished images from.
 *
 * @return String containing finished images as comma separated list.
 */
char *
report_finished_container_images_str (report_t report)
{
  char *ret;

  ret = sql_string ("SELECT string_agg (DISTINCT r.hostname, ',' ORDER BY r.hostname)"
                    " FROM results r"
                    " JOIN report_hosts rh"
                    " ON r.report = rh.report AND r.host = rh.host"
                    " WHERE rh.report = %llu AND rh.end_time != 0;",
                    report);

  return ret;
}

/**
 * @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.
 * @param[in]   lean      Whether to return reduced info.
 *
 * @return 0 success, -1 error.
 */
static int
print_report_host_detail (FILE *stream, iterator_t *details, int lean)
{
  const char *name, *value;

  name = report_host_details_iterator_name (details);
  value = report_host_details_iterator_value (details);

  if (lean)
    {
      /* Skip certain host details. */

      if (strcmp (name, "EXIT_CODE") == 0
          && strcmp (value, "EXIT_NOTVULN") == 0)
        return 0;

      if (strcmp (name, "scanned_with_scanner") == 0)
        return 0;

      if (strcmp (name, "scanned_with_feedtype") == 0)
        return 0;

      if (strcmp (name, "scanned_with_feedversion") == 0)
        return 0;

      if (strcmp (name, "OS") == 0)
        return 0;

      if (strcmp (name, "traceroute") == 0)
        return 0;
    }

  PRINT (stream,
        "<detail>"
        "<name>%s</name>"
        "<value>%s</value>"
        "<source>",
        name,
        value);

  if (lean == 0)
    PRINT (stream,
           "<type>%s</type>",
           report_host_details_iterator_source_type (details));

  PRINT (stream,
        "<name>%s</name>",
        report_host_details_iterator_source_name (details));

  if (report_host_details_iterator_source_desc (details)
      && strlen (report_host_details_iterator_source_desc (details)))
    PRINT (stream,
           "<description>%s</description>",
           report_host_details_iterator_source_desc (details));
  else if (lean == 0)
    PRINT (stream,
           "<description></description>");

  PRINT (stream,
        "</source>");

  if (report_host_details_iterator_extra (details)
      && strlen (report_host_details_iterator_extra (details)))
    PRINT (stream,
           "<extra>%s</extra>",
           report_host_details_iterator_extra (details));
  else if (lean == 0)
    PRINT (stream,
           "<extra></extra>");

  PRINT (stream,
        "</detail>");

  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.
 * @param[in]  lean         Report host details iterator.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_host_details_xml (report_host_t report_host, FILE *stream,
                               int lean)
{
  iterator_t details;

  init_report_host_details_iterator
   (&details, report_host);
  while (next (&details))
    if (print_report_host_detail (stream, &details, lean))
      return -1;
  cleanup_iterator (&details);

  return 0;
}

/**
 * @brief Print the XML for a report host's TLS certificates to a file stream.
 * @param[in]  report_host  The report host to get certificates from.
 * @param[in]  host_ip      The IP address of the report host.
 * @param[in]  stream       File stream to write to.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_host_tls_certificates_xml (report_host_t report_host,
                                        const char *host_ip,
                                        FILE *stream)
{

  iterator_t tls_certs;
  time_t activation_time, expiration_time;
  gchar *md5_fingerprint, *sha256_fingerprint, *subject, *issuer, *serial;
  gnutls_x509_crt_fmt_t certificate_format;

  if (report_host == 0
      || strcmp (host_ip, "") == 0)
    return -1;

  init_iterator (&tls_certs,
                 "SELECT rhd.value, rhd.name, rhd.source_name"
                 " FROM report_host_details AS rhd"
                 " WHERE rhd.report_host = %llu"
                 "   AND (source_description = 'SSL/TLS Certificate'"
                 "        OR source_description = 'SSL Certificate')",
                 report_host);

  while (next (&tls_certs))
    {
      const char *certificate_prefixed, *certificate_b64;
      gsize certificate_size;
      unsigned char *certificate;
      const char *scanner_fpr_prefixed, *scanner_fpr;
      gchar *quoted_scanner_fpr;
      char *ssldetails;
      iterator_t ports;
      gboolean valid;
      time_t now;

      certificate_prefixed = iterator_string (&tls_certs, 0);
      certificate_b64 = g_strrstr (certificate_prefixed, ":") + 1;

      certificate = g_base64_decode (certificate_b64, &certificate_size);

      scanner_fpr_prefixed = iterator_string (&tls_certs, 1);
      scanner_fpr = g_strrstr (scanner_fpr_prefixed, ":") + 1;

      quoted_scanner_fpr = sql_quote (scanner_fpr);

      activation_time = -1;
      expiration_time = -1;
      md5_fingerprint = NULL;
      sha256_fingerprint = NULL;
      subject = NULL;
      issuer = NULL;
      serial = NULL;
      certificate_format = 0;

      get_certificate_info ((gchar*)certificate,
                            certificate_size,
                            TRUE,
                            &activation_time,
                            &expiration_time,
                            &md5_fingerprint,
                            &sha256_fingerprint,
                            &subject,
                            &issuer,
                            &serial,
                            &certificate_format);

      if (sha256_fingerprint == NULL)
        sha256_fingerprint = g_strdup (scanner_fpr);

      ssldetails
        = sql_string ("SELECT rhd.value"
                      " FROM report_host_details AS rhd"
                      " WHERE report_host = %llu"
                      "   AND name = 'SSLDetails:%s'"
                      " LIMIT 1;",
                      report_host,
                      quoted_scanner_fpr);

      if (ssldetails)
        parse_ssldetails (ssldetails,
                          &activation_time,
                          &expiration_time,
                          &issuer,
                          &serial);
      else
        g_warning ("%s: No SSLDetails found for fingerprint %s",
                   __func__,
                   scanner_fpr);

      free (ssldetails);

      now = time (NULL);

      if((expiration_time >= now || expiration_time == -1)
         && (activation_time <= now || activation_time == -1))
        {
          valid = TRUE;
        }
      else
        {
          valid = FALSE;
        }
      char *hostname = sql_string ("SELECT value FROM report_host_details"
                                   " WHERE report_host = %llu"
                                   "   AND name = 'hostname'",
                                   report_host);

      PRINT (stream,
        "<tls_certificate>"
        "<name>%s</name>"
        "<certificate format=\"%s\">%s</certificate>"
        "<sha256_fingerprint>%s</sha256_fingerprint>"
        "<md5_fingerprint>%s</md5_fingerprint>"
        "<valid>%d</valid>"
        "<activation_time>%s</activation_time>"
        "<expiration_time>%s</expiration_time>"
        "<subject_dn>%s</subject_dn>"
        "<issuer_dn>%s</issuer_dn>"
        "<serial>%s</serial>"
        "<host><ip>%s</ip><hostname>%s</hostname></host>",
        scanner_fpr,
        tls_certificate_format_str (certificate_format),
        certificate_b64,
        sha256_fingerprint,
        md5_fingerprint,
        valid,
        certificate_iso_time (activation_time),
        certificate_iso_time (expiration_time),
        subject,
        issuer,
        serial,
        host_ip,
        hostname ? hostname : "");


      g_free (certificate);
      g_free (md5_fingerprint);
      g_free (sha256_fingerprint);
      g_free (subject);
      g_free (issuer);
      g_free (serial);

      free (hostname);

      init_iterator (&ports,
                     "SELECT value FROM report_host_details"
                     " WHERE report_host = %llu"
                     "   AND name = 'SSLInfo'"
                     "   AND value LIKE '%%:%%:%s'",
                     report_host,
                     quoted_scanner_fpr);

      PRINT (stream, "<ports>");

      while (next (&ports))
        {
          const char *value;
          gchar *port;

          value = iterator_string (&ports, 0);
          port = g_strndup (value, g_strrstr (value, ":") - value - 1);

          PRINT (stream, "<port>%s</port>", port);

          g_free (port);
        }

      PRINT (stream, "</ports>");

      PRINT (stream, "</tls_certificate>");

      g_free (quoted_scanner_fpr);
      cleanup_iterator (&ports);

    }
    cleanup_iterator (&tls_certs);

  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 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.
 * @param[in,out] results       Result iterator.  For caller to reuse.
 *
 * @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>");

  return 0;
}

/**
 * @brief Calculate the progress of a report.
 *
 * @param[in]  report     Report.
 *
 * @return Progress.
 */
int
report_progress (report_t report)
{
  if (report == 0)
    return -1;

  return report_slave_progress (report);
}

/**
 * @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", __func__);
              return -1;
            }
        }
      else
        unsetenv ("TZ");

      quoted_old_tz_override = sql_insert (old_tz_override);
      sql ("SET SESSION \"gvmd.tz_override\" = %s;",
           quoted_old_tz_override);
      g_free (quoted_old_tz_override);
    }
  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 !defined(__GLIBC__)
            if (strptime (start_iso, "%Y-%m-%dT%H:%M:%S", &start_tm) == NULL)
          #else
            if (strptime (start_iso, "%FT%H:%M:%S", &start_tm) == NULL)
          #endif
            {
              g_warning ("%s: Failed to parse start", __func__);
              return;
            }

          if (strftime (start, 200, "%b %d, %H:%M:%S", &start_tm) == 0)
            {
              g_warning ("%s: Failed to format start", __func__);
              return;
            }
        }
      else
        strcpy (start, "(not started)");

      if (end_iso)
        {
          struct tm end_tm;

          memset (&end_tm, 0, sizeof (struct tm));
          #if !defined(__GLIBC__)
            if (strptime (end_iso, "%Y-%m-%dT%H:%M:%S", &end_tm) == NULL)
          #else
            if (strptime (end_iso, "%FT%H:%M:%S", &end_tm) == NULL)
          #endif
            {
              g_warning ("%s: Failed to parse end", __func__);
              return;
            }

          if (strftime (end, 200, "%b %d, %H:%M:%S", &end_tm) == 0)
            {
              g_warning ("%s: Failed to format end", __func__);
              return;
            }
        }
      else
        strcpy (end, "(not finished)");

      g_string_append_printf (host_summary_buffer,
                              "   %-15s   %-16s   %s\n",
                              host,
                              start,
                              end);
    }
}

/**
 * @brief Print the XML for a report's host to a file stream.
 * @param[in]  stream                   File stream to write to.
 * @param[in]  hosts                    Host iterator.
 * @param[in]  host                     Single host to iterate over.
 *                                        All hosts if NULL.
 * @param[in]  usage_type               Report usage type.
 * @param[in]  lean                     Whether to return lean report.
 * @param[in]  host_summary_buffer      Host sumary buffer.
 * @param[in]  f_host_ports             Hashtable for host ports.
 * @param[in]  f_host_criticals         Hashtable for host criticals.
 * @param[in]  f_host_holes             Hashtable for host holes.
 * @param[in]  f_host_warnings          Hashtable for host host warnings.
 * @param[in]  f_host_infos             Hashtable for host infos.
 * @param[in]  f_host_logs              Hashtable for host logs.
 * @param[in]  f_host_false_positives   Hashtable for host false positives.
 * @param[in]  f_host_compliant         Hashtable for host compliant results.
 * @param[in]  f_host_notcompliant      Hashtable for host non compliant results.
 * @param[in]  f_host_incomplete        Hashtable for host incomplete resuls.
 * @param[in]  f_host_undefined         Hashtable for host undefined results.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_host_xml (FILE *stream,
                       iterator_t *hosts,
                       const char *host,
                       gchar *usage_type,
                       int lean,
                       GString *host_summary_buffer,
                       GHashTable *f_host_ports,
                       GHashTable *f_host_criticals,
                       GHashTable *f_host_holes,
                       GHashTable *f_host_warnings,
                       GHashTable *f_host_infos,
                       GHashTable *f_host_logs,
                       GHashTable *f_host_false_positives,
                       GHashTable *f_host_compliant,
                       GHashTable *f_host_notcompliant,
                       GHashTable *f_host_incomplete,
                       GHashTable *f_host_undefined)
{
  const char *current_host;
  int ports_count;

  current_host = host_iterator_host (hosts);

  ports_count
    = GPOINTER_TO_INT
        (g_hash_table_lookup (f_host_ports, current_host));

  host_summary_append (host_summary_buffer,
                       host ? host : host_iterator_host (hosts),
                       host_iterator_start_time (hosts),
                       host_iterator_end_time (hosts));
  PRINT (stream,
          "<host>"
          "<ip>%s</ip>",
          host ? host : host_iterator_host (hosts));

  if (host_iterator_asset_uuid (hosts)
      && strlen (host_iterator_asset_uuid (hosts)))
    PRINT (stream,
            "<asset asset_id=\"%s\"/>",
            host_iterator_asset_uuid (hosts));
  else if (lean == 0)
    PRINT (stream,
           "<asset asset_id=\"\"/>");

  if (strcmp (usage_type, "audit") == 0)
    {
      int yes_count, no_count, incomplete_count, undefined_count;

      yes_count
        = GPOINTER_TO_INT
            (g_hash_table_lookup (f_host_compliant, current_host));
      no_count
        = GPOINTER_TO_INT
            (g_hash_table_lookup (f_host_notcompliant, current_host));
      incomplete_count
        = GPOINTER_TO_INT
            (g_hash_table_lookup (f_host_incomplete, current_host));
      undefined_count
        = GPOINTER_TO_INT
            (g_hash_table_lookup (f_host_undefined, current_host));

      PRINT (stream,
            "<start>%s</start>"
            "<end>%s</end>"
            "<port_count><page>%d</page></port_count>"
            "<compliance_count>"
            "<page>%d</page>"
            "<yes><page>%d</page></yes>"
            "<no><page>%d</page></no>"
            "<incomplete><page>%d</page></incomplete>"
            "<undefined><page>%d</page></undefined>"
            "</compliance_count>"
            "<host_compliance>%s</host_compliance>",
            host_iterator_start_time (hosts),
            host_iterator_end_time (hosts)
              ? host_iterator_end_time (hosts)
              : "",
            ports_count,
            (yes_count + no_count + incomplete_count + undefined_count),
            yes_count,
            no_count,
            incomplete_count,
            undefined_count,
            report_compliance_from_counts (&yes_count,
                                            &no_count,
                                            &incomplete_count,
                                            &undefined_count));
    }
  else
    {
      int holes_count, warnings_count, infos_count;
      int logs_count, false_positives_count;
      int criticals_count = 0;

      criticals_count
        = GPOINTER_TO_INT
            (g_hash_table_lookup ( f_host_criticals, 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));

      PRINT (stream,
            "<start>%s</start>"
            "<end>%s</end>"
            "<port_count><page>%d</page></port_count>"
            "<result_count>"
            "<page>%d</page>"
            "<critical><page>%d</page></critical>"
            "<hole deprecated='1'><page>%d</page></hole>"
            "<high><page>%d</page></high>"
            "<warning deprecated='1'><page>%d</page></warning>"
            "<medium><page>%d</page></medium>"
            "<info deprecated='1'><page>%d</page></info>"
            "<low><page>%d</page></low>"
            "<log><page>%d</page></log>"
            "<false_positive><page>%d</page></false_positive>"
            "</result_count>",
            host_iterator_start_time (hosts),
            host_iterator_end_time (hosts)
              ? host_iterator_end_time (hosts)
              : "",
            ports_count,
            (criticals_count + holes_count + warnings_count + infos_count
              + logs_count + false_positives_count),
            criticals_count,
            holes_count,
            holes_count,
            warnings_count,
            warnings_count,
            infos_count,
            infos_count,
            logs_count,
            false_positives_count);
    }

  if (print_report_host_details_xml
        (host_iterator_report_host (hosts), stream, lean))
    {
      return -1;
    }

  PRINT (stream,
          "</host>");

  return 0;
}

/**
 * @brief Init delta iterator for print_report_xml.
 *
 * @param[in]  report         The report.
 * @param[in]  results        Report result iterator.
 * @param[in]  delta          Delta report.
 * @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_iterator (report_t report, iterator_t *results, report_t delta,
                     const get_data_t *get, const char *term,
                     const char *sort_field)
{
  int ret;
  static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS;
  static column_t columns_no_cert[] = DELTA_RESULT_ITERATOR_COLUMNS_NO_CERT;
  static column_t columns[] = DELTA_RESULT_ITERATOR_COLUMNS;


  gchar *filter, *extra_tables, *extra_where, *extra_where_single;
  gchar *opts_tables, *extra_with, *lateral_clause, *with_lateral;
  int apply_overrides, dynamic_severity;
  column_t *actual_columns;

  g_debug ("%s", __func__);

  if (report == -1)
    {
      init_iterator (results, "SELECT NULL WHERE false;");
      return 0;
    }

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        return 2;
    }
  else
    filter = NULL;

  apply_overrides
    = filter_term_apply_overrides (filter ? filter : get->filter);
  dynamic_severity = setting_dynamic_severity_int ();

  if (manage_cert_loaded ())
    actual_columns = columns;
  else
    actual_columns = columns_no_cert;

  opts_tables = result_iterator_opts_table (apply_overrides, dynamic_severity);

  lateral_clause = result_iterator_lateral (apply_overrides,
                                            dynamic_severity,
                                            "results",
                                            "nvts");

  extra_tables = g_strdup_printf (" JOIN comparison "
                                  " ON results.id = COALESCE (result1_id,"
                                  "                           result2_id)"
                                  " LEFT OUTER JOIN result_vt_epss"
                                  " ON results.nvt = result_vt_epss.vt_id"
                                  " LEFT OUTER JOIN nvts"
                                  " ON results.nvt = nvts.oid %s,"
                                  " LATERAL %s AS lateral_new_severity",
                                  opts_tables,
                                  lateral_clause);

  g_free (lateral_clause);

  extra_where = results_extra_where (get->trash, 0, NULL,
                                     apply_overrides, dynamic_severity,
                                     filter ? filter : get->filter,
                                     NULL);

  extra_where_single = results_extra_where (get->trash, 0, NULL,
                                            apply_overrides,
                                            dynamic_severity,
                                            "min_qod=0",
                                            NULL);

  free (filter);

  with_lateral = result_iterator_lateral (apply_overrides,
                                          dynamic_severity,
                                          "results",
                                          "nvts_cols");

  extra_with = g_strdup_printf(" comparison AS ("
    " WITH r1a as (SELECT results.id, description, host, report, port,"
    "              severity, nvt, results.qod, results.uuid, hostname,"
    "              path, r1_lateral.new_severity as new_severity "
    "       FROM results "
    "       LEFT JOIN (SELECT cvss_base, oid AS nvts_oid FROM nvts)"
    "       AS nvts_cols"
    "       ON nvts_cols.nvts_oid = results.nvt"
    "       %s, LATERAL %s AS r1_lateral"
    "       WHERE report = %llu),"
    " r2a as (SELECT results.*, r2_lateral.new_severity AS new_severity"
    "        FROM results"
    "        LEFT JOIN (SELECT cvss_base, oid AS nvts_oid FROM nvts)"
    "        AS nvts_cols"
    "        ON nvts_cols.nvts_oid = results.nvt"
    "        %s, LATERAL %s AS r2_lateral"
    "        WHERE report = %llu),"
    " r1 as (SELECT DISTINCT ON (r1a.id) r1a.*, r2a.id as r2id, row_number() over w1 as r1_rank"
    "        FROM r1a LEFT JOIN r2a ON r1a.host = r2a.host"
    "        AND normalize_port(r1a.port) = normalize_port(r2a.port)"
    "        AND r1a.nvt = r2a.nvt "
    "        AND (r1a.new_severity = 0) = (r2a.new_severity = 0)"
    "        AND (r1a.description = r2a.description)"
    "        WINDOW w1 AS (PARTITION BY r1a.host, normalize_port(r1a.port),"
    "                      r1a.nvt, r1a.new_severity = 0, r2a.id is null ORDER BY r1a.description, r2a.id)"
    "        ORDER BY r1a.id),"
    " r2 as (SELECT DISTINCT ON (r2a.id) r2a.*, r1a.id as r1id, row_number() over w2 as r2_rank"
    "        FROM r2a LEFT JOIN r1a ON r2a.host = r1a.host"
    "        AND normalize_port(r2a.port) = normalize_port(r1a.port)"
    "        AND r2a.nvt = r1a.nvt "
    "        AND (r2a.new_severity = 0) = (r1a.new_severity = 0)"
    "        AND (r2a.description = r1a.description)"
    "        WINDOW w2 AS (PARTITION BY r2a.host, normalize_port(r2a.port),"
    "                      r2a.nvt, r2a.new_severity = 0, r1a.id is null ORDER BY r2a.description, r1a.id)"
    "        ORDER BY r2a.id)"
    " (SELECT r1.id AS result1_id,"
    " r2.id AS result2_id,"
    " compare_results("
    "  r1.description,"
    "  r2.description,"
    "  r1.new_severity::double precision,"
    "  r2.new_severity::double precision,"
    "  r1.qod::integer,"
    "  r2.qod::integer,"
       RESULT_HOSTNAME_SQL("r1.hostname", "r1.host", "r1.report")","
       RESULT_HOSTNAME_SQL("r2.hostname", "r2.host", "r2.report")","
    "  r1.path,"
    "  r2.path) AS state,"
    " r2.description AS delta_description,"
    " r2.new_severity AS delta_new_severity,"
    " r2.severity AS delta_severity,"
    " r2.qod AS delta_qod,"
    " r2.qod_type AS delta_qod_type,"
    " r2.uuid AS delta_uuid,"
    " r2.date AS delta_date,"
    " r2.task AS delta_task,"
    " r2.report AS delta_report,"
    " r2.owner AS delta_owner,"
    " r2.path AS delta_path,"
    " r2.host AS delta_host,"
      RESULT_HOSTNAME_SQL("r2.hostname", "r2.host", "r2.report")
    "   AS delta_hostname,"
    " r2.nvt_version AS delta_nvt_version"
    " FROM r1"
    " FULL OUTER JOIN r2"
    " ON r1.host = r2.host"
    " AND normalize_port(r1.port) = normalize_port(r2.port)"
    " AND r1.nvt = r2.nvt "
    " AND (r1.new_severity = 0) = (r2.new_severity = 0)"
    " AND ((r1id IS NULL AND r2id IS NULL) OR"
    "      r2id = r2.id OR r1id = r1.id)"
    " AND r1_rank = r2_rank"
    " ) ) ",
    opts_tables,
    with_lateral,
    report,
    opts_tables,
    with_lateral,
    delta);

  ret = init_get_iterator2_with (results,
                                "result",
                                get,
                                /* SELECT columns. */
                                actual_columns,
                                NULL,
                                /* Filterable columns not in SELECT columns. */
                                NULL,
                                NULL,
                                filter_columns,
                                0,
                                extra_tables,
                                extra_where,
                                extra_where_single,
                                TRUE,
                                report ? TRUE : FALSE,
                                NULL,
                                extra_with,
                                0,
                                0);
  g_free (extra_tables);
  g_free (extra_where);
  g_free (extra_where_single);
  g_free (with_lateral);
  g_free (opts_tables);

  g_debug ("%s: done", __func__);

  return ret;
}

/**
 * @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_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_criticals            Result count.
 * @param[in]  f_criticals                 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]  f_compliance_yes            filtered compliant count.
 * @param[in]  f_compliance_no             filtered incompliant count.
 * @param[in]  f_compliance_incomplete     filtered incomplete count.
 * @param[in]  f_compliance_undefined      filtered undefined count.
 * @param[in]  f_compliance_count          total filtered compliance count.
 * @param[in]  result_hosts                Result hosts.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_delta_xml (FILE *out, iterator_t *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_criticals, int *f_criticals,
                        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,
                        int *f_compliance_yes, int *f_compliance_no,
                        int *f_compliance_incomplete,
                        int *f_compliance_undefined, int *f_compliance_count,
                        array_t *result_hosts)
{
  GString *buffer = g_string_new ("");
  GTree *ports;
  *orig_f_holes = *f_holes;
  *orig_f_criticals = *f_criticals;
  *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;
  gchar *usage_type = NULL;

  if (task && task_usage_type(task, &usage_type))
    return -1;

  ports = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free,
                           (GDestroyNotify) free_host_ports);

  while (next (results)) {

    const char *state = result_iterator_delta_state (results);

    if (strchr (delta_states, state[0]) == NULL) continue;

    if (strcmp (usage_type, "audit") == 0)
      {
          const char* compliance;
          compliance = result_iterator_compliance (results);
          (*f_compliance_count)++;
          if (strcasecmp (compliance, "yes") == 0)
            {
                (*f_compliance_yes)++;
            }
          else if (strcasecmp (compliance, "no") == 0)
            {
                (*f_compliance_no)++;
            }
          else if (strcasecmp (compliance, "incomplete") == 0)
            {
                (*f_compliance_incomplete)++;
            }
          else if (strcasecmp (compliance, "undefined") == 0)
            {
                (*f_compliance_undefined)++;
            }
      }
    else
      {
        const char *level;
        /* Increase the result count. */
        level = result_iterator_level (results);
        (*orig_filtered_result_count)++;
        (*filtered_result_count)++;
        if (strcmp (level, "Critical") == 0)
          {
            (*orig_f_criticals)++;
            (*f_criticals)++;
          }
        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)++;
          }
      }

    buffer_results_xml (buffer,
                        results,
                        task,
                        notes,
                        notes_details,
                        overrides,
                        overrides_details,
                        0,
                        0,
                        0,
                        state,
                        NULL,
                        (strcmp (state, "changed") == 0),
                        -1,
                        0,  /* Lean. */
                        0); /* Delta fields. */

    if (fprintf (out, "%s", buffer->str) < 0)
      {
        g_string_free (buffer, TRUE);
        g_tree_destroy (ports);
        return -1;
      }
    if (result_hosts_only)
      array_add_new_string (result_hosts,
                            result_iterator_host (results));
    add_port (ports, results);
    g_string_truncate (buffer, 0);
  }
  g_string_free (buffer, TRUE);
  g_free (usage_type);

  if (fprintf (out, "</results>") < 0)
    {
      g_tree_destroy (ports);
      return -1;
    }

  gchar *msg;
  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_tree_destroy (ports);
      g_free (msg);
      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);
  if (fprintf (out, "</ports>") < 0)
    {
      return -1;
    }

  return 0;
}

/**
 * @brief Ensure a filter string has min_qod and apply_overrides.
 *
 * @param[in]  clean  Filter string.
 *
 * @return clean, which may have been reallocated.
 */
static gchar *
ensure_term_has_qod_and_overrides (gchar *clean)
{
  gchar *term_value;

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

  return clean;
}

/**
 * @brief Clean a filter term string for print_report_xml_start.
 *
 * @param[in]  term  Pointer to filter term, which will be reallocated.
 * @param[in]  get   GET command data.
 */
static void
print_report_clean_filter (gchar **term, const get_data_t *get)
{
  gchar *clean;

  assert (term);

  clean = manage_clean_filter (*term
                                ? *term
                                : (get->filter ? get->filter : ""),
                               get->ignore_max_rows_per_page);

  g_free (*term);
  *term = ensure_term_has_qod_and_overrides (clean);
}

/**
 * @brief Context info for print_report_xml_start.
 */
struct print_report_context
{
  int count_filtered;         ///< Whether to count filtered results.
  report_t delta;             ///< Report to compare with.
  int filtered_result_count;  ///< Filtered result count.
  const get_data_t *get;      ///< GET command data.
  gchar *tz;                  ///< TZ.
  gchar *zone;                ///< Zone.
  char *old_tz_override;      ///< Old TZ.
  report_t report;            ///< Report.
  gchar *tsk_usage_type;      ///< Usage type of task, like "audit"
  // Counts.
  int criticals;              ///< Number of criticals.
  int holes;                  ///< Number of holes.
  int infos;                  ///< Number of infos.
  int logs;                   ///< Number of logs.
  int warnings;               ///< Number of warnings.
  int false_positives;        ///< Number of false positives.
  int total_result_count;     ///< Total number of results.
};

/**
 * @brief Context type for print_report_xml_start.
 */
typedef struct print_report_context print_report_context_t;

/**
 * @brief Free the members of a context.
 *
 * @param[in]  ctx  Printing context.
 */
static void
print_report_context_cleanup (print_report_context_t *ctx)
{
  g_free (ctx->tsk_usage_type);
  g_free (ctx->tz);
  g_free (ctx->zone);
  free (ctx->old_tz_override);
}

/**
 * @brief Init zone info for print_report_xml_start.
 *
 * @param[in]  ctx  Printing context.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_init_zone (print_report_context_t *ctx)
{
  if (ctx->zone && strlen (ctx->zone))
    {
      gchar *quoted_zone;
      /* Store current TZ. */
      ctx->tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

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

      ctx->old_tz_override = sql_string ("SELECT current_setting"
                                         "        ('gvmd.tz_override');");

      quoted_zone = sql_insert (ctx->zone);
      sql ("SET SESSION \"gvmd.tz_override\" = %s;", quoted_zone);
      g_free (quoted_zone);

      tzset ();
    }
  return 0;
}

/**
 * @brief Get result count totals for print_report_xml_start.
 *
 * @param[in]  ctx  Printing context.
 */
static void
print_report_get_totals (print_report_context_t *ctx)
{
  if (strcmp (ctx->tsk_usage_type, "audit"))
    {
      if (ctx->delta == 0)
        {
          int total_criticals = 0, 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);

          report_counts_id (ctx->report, &total_criticals, &total_holes,
                            &total_infos, &total_logs, &total_warnings,
                            &total_false_positives, NULL, all_results_get,
                            NULL);

          ctx->total_result_count = total_criticals + total_holes
                                    + total_infos + total_logs
                                    + total_warnings + total_false_positives;
          get_data_reset (all_results_get);
          free (all_results_get);
        }

      /* Get total counts of filtered results. */

      if (ctx->count_filtered)
        {
          /* We're getting all the filtered results, so we can count them as we
           * print them, to save time. */
          ctx->filtered_result_count = 0;
        }
      else
        {
          /* Beware, we're using the full variables temporarily here, but
           * report_counts_id counts the filtered results. */
          report_counts_id (ctx->report, &ctx->criticals, &ctx->holes, &ctx->infos, &ctx->logs, &ctx->warnings,
                            &ctx->false_positives, NULL, ctx->get, NULL);

          ctx->filtered_result_count = ctx->criticals + ctx->holes + ctx->infos + ctx->logs
                                       + ctx->warnings + ctx->false_positives;

        }
    }
}

/**
 * @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]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  result_tags        Whether to include tags in results.
 * @param[in]  ignore_pagination   Whether to ignore pagination data.
 * @param[in]  lean                Whether to return lean report.
 * @param[out] filter_term_return  Filter term used in report.
 * @param[out] zone_return         Actual timezone 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,
                        int notes_details, int overrides_details,
                        int result_tags, int ignore_pagination, int lean,
                        gchar **filter_term_return, gchar **zone_return,
                        gchar **host_summary)
{
  int result_hosts_only;
  int notes, overrides;

  int first_result, max_results, sort_order;

  FILE *out;
  gchar *term, *sort_field, *levels, *search_phrase;
  gchar *min_qod, *compliance_levels;
  gchar *delta_states, *timestamp;
  int min_qod_int;
  char *uuid, *tsk_uuid = NULL, *start_time, *end_time;
  array_t *result_hosts;
  int reuse_result_iterator;
  iterator_t results, delta_results;
  int f_criticals = 0, f_holes, f_infos, f_logs, f_warnings, f_false_positives;
  int orig_f_criticals, 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;
  GString *filters_buffer, *filters_extra_buffer, *host_summary_buffer;
  GHashTable *f_host_ports;
  GHashTable *f_host_holes, *f_host_warnings, *f_host_infos;
  GHashTable *f_host_logs, *f_host_false_positives;
  GHashTable *f_host_compliant, *f_host_notcompliant;
  GHashTable  *f_host_incomplete, *f_host_undefined;
  GHashTable *f_host_criticals = NULL;
  task_status_t run_status;
  int f_compliance_yes, f_compliance_no;
  int f_compliance_incomplete, f_compliance_undefined;
  int f_compliance_count;
  print_report_context_t ctx = {0};

  /* Init some vars to prevent warnings from older compilers. */
  max_results = -1;
  levels = NULL;
  compliance_levels = NULL;
  delta_states = NULL;
  min_qod = NULL;
  search_phrase = NULL;
  f_compliance_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_criticals = 0;
  host_summary_buffer = NULL;
  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;
  f_host_compliant = NULL;
  f_host_notcompliant = NULL;
  f_host_incomplete = NULL;
  f_host_undefined = NULL;

  ctx.delta = delta;
  ctx.get = get;
  ctx.report = report;

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

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

  out = fopen (xml_start, "w");

  if (out == NULL)
    {
      g_warning ("%s: fopen failed: %s",
                 __func__,
                 strerror (errno));
      return -1;
    }

  assert (get);

  if ((get->filt_id && strlen (get->filt_id)
       && strcmp (get->filt_id, FILT_ID_NONE))
      || (get->filter && strlen (get->filter)))
    {
      term = NULL;
      if (get->filt_id && strlen (get->filt_id)
          && strcmp (get->filt_id, FILT_ID_NONE))
        {
          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, &compliance_levels,
                                     &delta_states, &search_phrase,
                                     &search_phrase_exact, &notes,
                                     &overrides, &apply_overrides, &ctx.zone);
    }
  else
    {
      term = g_strdup ("");
      /* Set the filter parameters to defaults */
      manage_report_filter_controls (term,
                                     &first_result, &max_results, &sort_field,
                                     &sort_order, &result_hosts_only,
                                     &min_qod, &levels, &compliance_levels,
                                     &delta_states, &search_phrase,
                                     &search_phrase_exact, &notes, &overrides,
                                     &apply_overrides, &ctx.zone);
    }

  max_results = manage_max_rows (max_results, get->ignore_max_rows_per_page);

  levels = levels ? levels : g_strdup ("chmlgdf");

  if (task && (task_uuid (task, &tsk_uuid) || task_usage_type (task, &ctx.tsk_usage_type)))
    {
      fclose (out);
      g_free (term);
      g_free (levels);
      g_free (search_phrase);
      g_free (min_qod);
      g_free (delta_states);
      return -1;
    }

  if (print_report_init_zone (&ctx))
    {
      g_free (ctx.zone);
      return -1;
    }

  if (delta && report)
    {
      uuid = report_uuid (report);
      PRINT (out, "<report type=\"delta\" id=\"%s\">", uuid);
      free (uuid);
    }
  else
    {
      uuid = report_uuid (report);
      PRINT (out, "<report id=\"%s\">", uuid);
      free (uuid);
    }

  PRINT (out, "<gmp><version>%s</version></gmp>", GMP_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_INTERRUPTED));

      if (report_timestamp (uuid, &timestamp))
        {
          free (uuid);
          g_free (levels);
          g_free (search_phrase);
          g_free (min_qod);
          g_free (delta_states);
          goto fail;
        }
      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>");
    }

  ctx.count_filtered = (delta || (ignore_pagination && get->details));

  if (report)
    {
      /* Get total counts of full results. */
      print_report_get_totals (&ctx);

      /* Get report run status. */
      report_scan_run_status (report, &run_status);
    }

  print_report_clean_filter (&term, get);

  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 (strcmp (ctx.tsk_usage_type, "audit") == 0)
    {
      compliance_levels = compliance_levels ? compliance_levels : g_strdup ("yniu");

      if (strchr (compliance_levels, 'y'))
        g_string_append (filters_extra_buffer, "<filter>Yes</filter>");
      if (strchr (compliance_levels, 'n'))
        g_string_append (filters_extra_buffer, "<filter>No</filter>");
      if (strchr (compliance_levels, 'i'))
        g_string_append (filters_extra_buffer, "<filter>Incomplete</filter>");
      if (strchr (compliance_levels, 'u'))
        g_string_append (filters_extra_buffer, "<filter>Undefined</filter>");
    }
  else
    {
      if (strchr (levels, 'c'))
        g_string_append (filters_extra_buffer, "<filter>Critical</filter>");
      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, 'f'))
        g_string_append (filters_extra_buffer, "<filter>False Positive</filter>");
    }

  if (delta)
    {
      gchar *escaped_delta_states = g_markup_escape_text (delta_states, -1);
      g_string_append_printf (filters_extra_buffer,
                              "<delta>"
                              "%s"
                              "<changed>%i</changed>"
                              "<gone>%i</gone>"
                              "<new>%i</new>"
                              "<same>%i</same>"
                              "</delta>",
                              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_delta_states);
    }

  filters_buffer = g_string_new ("");
  buffer_get_filter_xml (filters_buffer, "result", get, term,
                         filters_extra_buffer->str);
  g_string_free (filters_extra_buffer, TRUE);

  PRINT_XML (out, filters_buffer->str);
  g_string_free (filters_buffer, TRUE);

  if (report)
    {
      const char *report_type = (strcmp (ctx.tsk_usage_type, "audit") == 0)
                                 ? "audit_report"
                                 : "report";
      int tag_count = resource_tag_count (report_type, report, 1);

      if (tag_count)
        {
          PRINT (out,
                 "<user_tags>"
                 "<count>%i</count>",
                 tag_count);

          if (get->details || get->id)
            {
              iterator_t tags;

              init_resource_tag_iterator (&tags, report_type, 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>");
        }
    }

  if (report)
    {
      PRINT
       (out,
        "<scan_run_status>%s</scan_run_status>",
        run_status_name (run_status
                          ? run_status
                          : TASK_STATUS_INTERRUPTED));

      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, *task_target_name;
      char *task_target_comment, *comment;
      target_t target;
      gchar *progress_xml;
      iterator_t tags;

      const char *task_type = (strcmp (ctx.tsk_usage_type, "audit") == 0)
                               ? "audit"
                               : "task";
      int task_tag_count = resource_tag_count (task_type, task, 1);

      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);
          task_target_name = trash_target_name (target);
          task_target_comment = trash_target_comment (target);
        }
      else
        {
          task_target_uuid = target_uuid (target);
          task_target_name = target_name (target);
          task_target_comment = target_comment (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);
          progress_xml = g_strdup_printf ("%i", progress);
        }

      PRINT (out,
             "<task id=\"%s\">"
             "<name>%s</name>"
             "<comment>%s</comment>"
             "<target id=\"%s\">"
             "<trash>%i</trash>"
             "<name>%s</name>"
             "<comment>%s</comment>"
             "</target>",
             tsk_uuid,
             tsk_name ? tsk_name : "",
             comment ? comment : "",
             task_target_uuid ? task_target_uuid : "",
             task_target_in_trash (task),
             task_target_name ? task_target_name : "",
             task_target_comment ? task_target_comment : "");

      free (comment);
      free (tsk_name);
      free (tsk_uuid);
      free (task_target_uuid);
      free (task_target_name);
      free (task_target_comment);

#if ENABLE_AGENTS
      agent_group_t agent_group = task_agent_group (task);
      if (agent_group)
        {
          char *ag_uuid, *ag_name, *ag_comment;
          int in_trash;

          in_trash = task_agent_group_in_trash (task);

          ag_uuid = in_trash
                      ? trash_agent_group_uuid (agent_group)
                      : agent_group_uuid (agent_group);
          ag_name = in_trash
                      ? trash_agent_group_name (agent_group)
                      : agent_group_name (agent_group);
          ag_comment = in_trash
                         ? trash_agent_group_comment (agent_group)
                         : agent_group_comment (agent_group);

          PRINT (out,
                 "<agent_group id=\"%s\">"
                 "<trash>%i</trash>"
                 "<name>%s</name>"
                 "<comment>%s</comment>"
                 "</agent_group>",
                 ag_uuid ? ag_uuid : "",
                 in_trash,
                 ag_name ? ag_name : "",
                 ag_comment ? ag_comment : "");

          g_free (ag_uuid);
          g_free (ag_name);
          g_free (ag_comment);

          g_free (progress_xml);
          progress_xml = g_strdup_printf ("%i", 100);
        }
#endif /* ENABLE_AGENTS */

#if ENABLE_CONTAINER_SCANNING
      oci_image_target_t oci_image_target = task_oci_image_target (task);
      if (oci_image_target)
        {
          char *oci_uuid, *oci_name, *oci_comment;
          int in_trash;

          in_trash = task_oci_image_target_in_trash (task);

          oci_uuid = in_trash
                       ? trash_oci_image_target_uuid (oci_image_target)
                       : oci_image_target_uuid (oci_image_target);
          oci_name = in_trash
                       ? trash_oci_image_target_name (oci_image_target)
                       : oci_image_target_name (oci_image_target);
          oci_comment = in_trash
                          ? trash_oci_image_target_comment (oci_image_target)
                          : oci_image_target_comment (oci_image_target);

          PRINT (out,
                 "<oci_image_target id=\"%s\">"
                 "<trash>%i</trash>"
                 "<name>%s</name>"
                 "<comment>%s</comment>"
                 "</oci_image_target>",
                 oci_uuid ? oci_uuid : "",
                 in_trash,
                 oci_name ? oci_name : "",
                 oci_comment ? oci_comment : "");

          g_free (oci_uuid);
          g_free (oci_name);
          g_free (oci_comment);

          g_free (progress_xml);
          progress_xml = g_strdup_printf ("%i", report_progress (report));
        }
#endif /* ENABLE_CONTAINER_SCANNING */
      PRINT (out, "<progress>%s</progress>", progress_xml);

      g_free (progress_xml);

      if (task_tag_count)
        {
          PRINT (out,
                 "<user_tags>"
                 "<count>%i</count>",
                 task_tag_count);

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

      PRINT (out,
             "</task>");

    }

  uuid = report_uuid (report);
  if (report_timestamp (uuid, &timestamp))
    {
      free (uuid);
      g_free (term);
      goto fail;
    }
  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 (ctx.zone && strlen (ctx.zone))
      report_zone = g_strdup (ctx.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);

  reuse_result_iterator = 0;
  if (get->details && (delta == 0))
    {
      reuse_result_iterator = 1;
      if (print_report_port_xml (report, out, get, first_result, max_results,
                                 sort_order, sort_field, f_host_ports, &results))
        {
          g_free (term);
          g_hash_table_destroy (f_host_ports);
          goto fail;
        }
    }

  /* Prepare result counts. */
  int compliance_yes = 0, compliance_no = 0;
  int compliance_incomplete = 0, compliance_undefined = 0;
  int total_compliance_count = 0;

  if (strcmp (ctx.tsk_usage_type, "audit") == 0)
    {
      report_compliance_counts (report, get, &compliance_yes, &compliance_no,
                                &compliance_incomplete, &compliance_undefined);

      total_compliance_count = compliance_yes
                              + compliance_no
                              + compliance_incomplete
                              + compliance_undefined;

      f_compliance_yes = f_compliance_no = 0;
      f_compliance_incomplete = f_compliance_undefined = 0;

       if (ctx.count_filtered == 0)
         {
           report_compliance_f_counts (report,
                                       get,
                                       &f_compliance_yes,
                                       &f_compliance_no,
                                       &f_compliance_incomplete,
                                       &f_compliance_undefined);

           f_compliance_count = f_compliance_yes
                               + f_compliance_no
                               + f_compliance_incomplete
                               + f_compliance_undefined;
         }
    }
  else
    {
      if (ctx.count_filtered)
        {
          /* We're getting all the filtered results, so we can count them as we
          * print them, to save time. */
          report_counts_id_full (report, &ctx.criticals, &ctx.holes, &ctx.infos, &ctx.logs,
                                 &ctx.warnings, &ctx.false_positives, &severity,
                                 get, NULL, NULL, NULL, NULL, NULL,
                                 NULL, NULL, NULL);

          f_criticals = f_holes = f_infos = f_logs = f_warnings = 0;
          f_false_positives = f_severity = 0;
        }
    else
      report_counts_id_full (report, &ctx.criticals, &ctx.holes, &ctx.infos, &ctx.logs,
                             &ctx.warnings, &ctx.false_positives, &severity,
                             get, NULL,
                             &f_criticals, &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_iterator (report, &results, delta,
                                  get, term, sort_field))
        {
          g_free (term);
          g_hash_table_destroy (f_host_ports);
          goto fail;
        }
    }
  else if (get->details)
    {
      int res;
      g_free (term);
      if (reuse_result_iterator)
        iterator_rewind (&results);
      else
        {
          res = init_result_get_iterator (&results, get, report, NULL, NULL);
          if (res)
            {
              g_hash_table_destroy (f_host_ports);
              goto fail;
            }
        }
    }
  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;

  if (strcmp (ctx.tsk_usage_type, "audit") == 0)
    {
      f_host_compliant = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                g_free, NULL);
      f_host_notcompliant = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                   g_free, NULL);
      f_host_incomplete = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                 g_free, NULL);
      f_host_undefined = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                g_free, NULL);
    }
  else
    {
      f_host_criticals = g_hash_table_new_full (g_str_hash, g_str_equal,
                                            g_free, 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_states,
                                  ignore_pagination ? 0 : first_result,
                                  ignore_pagination ? -1 : max_results,
                                  task, notes,
                                  notes_details, overrides,
                                  overrides_details, sort_order,
                                  sort_field, result_hosts_only,
                                  &orig_filtered_result_count,
                                  &ctx.filtered_result_count,
                                  &orig_f_criticals, &f_criticals,
                                  &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,
                                  &f_compliance_yes,
                                  &f_compliance_no,
                                  &f_compliance_incomplete,
                                  &f_compliance_undefined,
                                  &f_compliance_count,
                                  result_hosts))

        goto failed_delta_report;
    }
  else if (get->details)
    {
      int cert_loaded;

      cert_loaded = manage_cert_loaded ();
      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,
                              result_tags,
                              1,
                              0,
                              NULL,
                              NULL,
                              0,
                              cert_loaded,
                              lean,
                              0); /* Delta fields. */
          PRINT_XML (out, buffer->str);
          g_string_free (buffer, TRUE);
          if (result_hosts_only)
            array_add_new_string (result_hosts,
                                  result_iterator_host (&results));

          if (strcmp (ctx.tsk_usage_type, "audit") == 0)
            {
              const char* compliance;
              compliance = result_iterator_compliance (&results);

              if (strcasecmp (compliance, "yes") == 0)
                {
                  f_host_result_counts = f_host_compliant;
                  if (ctx.count_filtered)
                    f_compliance_yes++;
                }
              else if (strcasecmp (compliance, "no") == 0)
                {
                  f_host_result_counts = f_host_notcompliant;
                  if (ctx.count_filtered)
                    f_compliance_no++;
                }
              else if (strcasecmp (compliance, "incomplete") == 0)
                {
                  f_host_result_counts = f_host_incomplete;
                  if (ctx.count_filtered)
                    f_compliance_incomplete++;
                }
              else if (strcasecmp (compliance, "undefined") == 0)
                {
                  f_host_result_counts = f_host_undefined;
                  if (ctx.count_filtered)
                    f_compliance_undefined++;
                }
              else
                {
                  f_host_result_counts = NULL;
                }

              if (f_host_result_counts)
                {
                  const char *result_host = result_iterator_host (&results);
                  int result_count
                        = GPOINTER_TO_INT
                            (g_hash_table_lookup (f_host_result_counts,
                                                  result_host));

                  g_hash_table_replace (f_host_result_counts,
                                        g_strdup (result_host),
                                        GINT_TO_POINTER (result_count + 1));
              }
            }
          else
            {
              double result_severity;
              result_severity = result_iterator_severity_double (&results);
              if (result_severity > f_severity)
                f_severity = result_severity;

              level = result_iterator_level (&results);

              if (strcasecmp (level, "log") == 0)
                {
                  f_host_result_counts = f_host_logs;
                  if (ctx.count_filtered)
                    f_logs++;
                }
              else if (strcasecmp (level, "critical") == 0)
                {
                  f_host_result_counts = f_host_criticals;
                  if (ctx.count_filtered)
                    f_criticals++;
                }
              else if (strcasecmp (level, "high") == 0)
                {
                  f_host_result_counts = f_host_holes;
                  if (ctx.count_filtered)
                    f_holes++;
                }
              else if (strcasecmp (level, "medium") == 0)
                {
                  f_host_result_counts = f_host_warnings;
                  if (ctx.count_filtered)
                    f_warnings++;
                }
              else if (strcasecmp (level, "low") == 0)
                {
                  f_host_result_counts = f_host_infos;
                  if (ctx.count_filtered)
                    f_infos++;
                }
              else if (strcasecmp (level, "false positive") == 0)
                {
                  f_host_result_counts = f_host_false_positives;
                  if (ctx.count_filtered)
                    f_false_positives++;
                }
              else
                f_host_result_counts = NULL;

              if (f_host_result_counts)
                {
                  const char *result_host = result_iterator_host (&results);
                  int result_count
                        = GPOINTER_TO_INT
                            (g_hash_table_lookup (f_host_result_counts, result_host));

                  g_hash_table_replace (f_host_result_counts,
                                        g_strdup (result_host),
                                        GINT_TO_POINTER (result_count + 1));
                }
           }
        }
      PRINT (out, "</results>");
    }
  if (get->details)
    cleanup_iterator (&results);

  /* Print result counts and severity. */

  if (strcmp (ctx.tsk_usage_type, "audit") == 0)
    {
      if (delta)
        PRINT (out,
              "<compliance_count>"
              "<filtered>%i</filtered>"
              "<yes><filtered>%i</filtered></yes>"
              "<no><filtered>%i</filtered></no>"
              "<incomplete><filtered>%i</filtered></incomplete>"
              "<undefined><filtered>%i</filtered></undefined>"
              "</compliance_count>",
              f_compliance_count,
              (strchr (compliance_levels, 'y') ? f_compliance_yes : 0),
              (strchr (compliance_levels, 'n') ? f_compliance_no : 0),
              (strchr (compliance_levels, 'i') ? f_compliance_incomplete : 0),
              (strchr (compliance_levels, 'u') ? f_compliance_undefined : 0));
      else
        {
          if (ctx.count_filtered)
            f_compliance_count = f_compliance_yes
                                  + f_compliance_no
                                  + f_compliance_incomplete
                                  + f_compliance_undefined;
          PRINT (out,
              "<compliance_count>"
              "%i"
              "<full>%i</full>"
              "<filtered>%i</filtered>"
              "<yes><full>%i</full><filtered>%i</filtered></yes>"
              "<no><full>%i</full><filtered>%i</filtered></no>"
              "<incomplete><full>%i</full><filtered>%i</filtered></incomplete>"
              "<undefined><full>%i</full><filtered>%i</filtered></undefined>"
              "</compliance_count>",
              total_compliance_count,
              total_compliance_count,
              f_compliance_count,
              compliance_yes,
              (strchr (compliance_levels, 'y') ? f_compliance_yes : 0),
              compliance_no,
              (strchr (compliance_levels, 'n') ? f_compliance_no : 0),
              compliance_incomplete,
              (strchr (compliance_levels, 'i') ? f_compliance_incomplete : 0),
              compliance_undefined,
              (strchr (compliance_levels, 'i') ? f_compliance_undefined : 0));

          PRINT (out,
                "<compliance>"
                "<full>%s</full>"
                "<filtered>%s</filtered>"
                "</compliance>",
                report_compliance_from_counts (&compliance_yes,
                                                &compliance_no,
                                                &compliance_incomplete,
                                                &compliance_undefined),
                report_compliance_from_counts (&f_compliance_yes,
                                                &f_compliance_no,
                                                &f_compliance_incomplete,
                                                &f_compliance_undefined));
        }
    }
  else
    {
      if (delta)
        /** @todo The f_holes, etc. vars are setup to give the page count. */
        PRINT (out,
              "<result_count>"
              "<filtered>%i</filtered>"
              "<critical><filtered>%i</filtered></critical>"
              "<hole deprecated='1'><filtered>%i</filtered></hole>"
              "<high><filtered>%i</filtered></high>"
              "<info deprecated='1'><filtered>%i</filtered></info>"
              "<low><filtered>%i</filtered></low>"
              "<log><filtered>%i</filtered></log>"
              "<warning deprecated='1'><filtered>%i</filtered></warning>"
              "<medium><filtered>%i</filtered></medium>"
              "<false_positive>"
              "<filtered>%i</filtered>"
              "</false_positive>"
              "</result_count>",
              orig_filtered_result_count,
              (strchr (levels, 'c') ? orig_f_criticals : 0),
              (strchr (levels, 'h') ? orig_f_holes : 0),
              (strchr (levels, 'h') ? orig_f_holes : 0),
              (strchr (levels, 'l') ? orig_f_infos : 0),
              (strchr (levels, 'l') ? orig_f_infos : 0),
              (strchr (levels, 'g') ? orig_f_logs : 0),
              (strchr (levels, 'm') ? orig_f_warnings : 0),
              (strchr (levels, 'm') ? orig_f_warnings : 0),
              (strchr (levels, 'f') ? orig_f_false_positives : 0));
      else
        {
          if (ctx.count_filtered)
            ctx.filtered_result_count = f_criticals + f_holes + f_infos + f_logs
                                        + f_warnings + f_false_positives;

          PRINT (out,
                "<result_count>"
                "%i"
                "<full>%i</full>"
                "<filtered>%i</filtered>"
                "<critical>"
                "<full>%i</full>"
                "<filtered>%i</filtered>"
                "</critical>"
                "<hole deprecated='1'><full>%i</full><filtered>%i</filtered></hole>"
                "<high><full>%i</full><filtered>%i</filtered></high>"
                "<info deprecated='1'><full>%i</full><filtered>%i</filtered></info>"
                "<low><full>%i</full><filtered>%i</filtered></low>"
                "<log><full>%i</full><filtered>%i</filtered></log>"
                "<warning deprecated='1'><full>%i</full><filtered>%i</filtered></warning>"
                "<medium><full>%i</full><filtered>%i</filtered></medium>"
                "<false_positive>"
                "<full>%i</full>"
                "<filtered>%i</filtered>"
                "</false_positive>"
                "</result_count>",
                ctx.total_result_count,
                ctx.total_result_count,
                ctx.filtered_result_count,
                ctx.criticals,
                (strchr (levels, 'c') ? f_criticals : 0),
                ctx.holes,
                (strchr (levels, 'h') ? f_holes : 0),
                ctx.holes,
                (strchr (levels, 'h') ? f_holes : 0),
                ctx.infos,
                (strchr (levels, 'l') ? f_infos : 0),
                ctx.infos,
                (strchr (levels, 'l') ? f_infos : 0),
                ctx.logs,
                (strchr (levels, 'g') ? f_logs : 0),
                ctx.warnings,
                (strchr (levels, 'm') ? f_warnings : 0),
                ctx.warnings,
                (strchr (levels, 'm') ? f_warnings : 0),
                ctx.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 *result_host;
      int index = 0;
      array_terminate (result_hosts);
      while ((result_host = g_ptr_array_index (result_hosts, index++)))
        {
          gboolean present;
          iterator_t hosts;
          init_report_host_iterator (&hosts, report, result_host, 0);
          present = next (&hosts);
          if (present)
            {
              if (print_report_host_xml (out,
                                         &hosts,
                                         result_host,
                                         ctx.tsk_usage_type,
                                         lean,
                                         host_summary_buffer,
                                         f_host_ports,
                                         f_host_criticals,
                                         f_host_holes,
                                         f_host_warnings,
                                         f_host_infos,
                                         f_host_logs,
                                         f_host_false_positives,
                                         f_host_compliant,
                                         f_host_notcompliant,
                                         f_host_incomplete,
                                         f_host_undefined))
                {
                  goto failed_print_report_host;
                }
            }
          cleanup_iterator (&hosts);
        }
    }
  else if (get->details)
    {
      iterator_t hosts;
      init_report_host_iterator (&hosts, report, NULL, 0);
      while (next (&hosts))
        {
          if (print_report_host_xml (out,
                                     &hosts,
                                     NULL,
                                     ctx.tsk_usage_type,
                                     lean,
                                     host_summary_buffer,
                                     f_host_ports,
                                     f_host_criticals,
                                     f_host_holes,
                                     f_host_warnings,
                                     f_host_infos,
                                     f_host_logs,
                                     f_host_false_positives,
                                     f_host_compliant,
                                     f_host_notcompliant,
                                     f_host_incomplete,
                                     f_host_undefined))

            goto failed_print_report_host;
        }
      cleanup_iterator (&hosts);
    }
  if (strcmp (ctx.tsk_usage_type, "audit") == 0)
    {
      g_hash_table_destroy (f_host_compliant);
      g_hash_table_destroy (f_host_notcompliant);
      g_hash_table_destroy (f_host_incomplete);
      g_hash_table_destroy (f_host_undefined);
    }
  else
    {
      g_hash_table_destroy (f_host_criticals);
      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);
    }
  g_hash_table_destroy (f_host_ports);

  /* Print TLS certificates */

   if (get->details && result_hosts_only)
    {
      gchar *result_host;
      int index = 0;
      PRINT (out, "<tls_certificates>");
      while ((result_host = g_ptr_array_index (result_hosts, index++)))
        {
          gboolean present;
          iterator_t hosts;
          init_report_host_iterator (&hosts, report, result_host, 0);
          present = next (&hosts);
          if (present)
            {
              report_host_t report_host = host_iterator_report_host(&hosts);

              if (print_report_host_tls_certificates_xml(report_host,
                                                         result_host,
                                                         out))
                {
                  cleanup_iterator (&hosts);
                  goto fail;
                }
            }
          cleanup_iterator (&hosts);
        }
      PRINT (out, "</tls_certificates>");
      array_free (result_hosts);
    }
  else if (get->details)
    {
      const char *host;
      iterator_t hosts;
      init_report_host_iterator (&hosts, report, NULL, 0);

      PRINT (out, "<tls_certificates>");

      while (next (&hosts))
        {
          report_host_t report_host = host_iterator_report_host(&hosts);
          host = host_iterator_host (&hosts);

          if (print_report_host_tls_certificates_xml(report_host,
                                                     host,
                                                     out))
            {
              cleanup_iterator (&hosts);
              goto fail;
            }
        }
      cleanup_iterator (&hosts);
      PRINT (out, "</tls_certificates>");
    }

  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))
    {
      if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
      goto fail;
    }

  g_free (sort_field);
  g_free (levels);
  g_free (search_phrase);
  g_free (min_qod);
  g_free (delta_states);
  g_free (compliance_levels);

  if (host_summary && host_summary_buffer)
    *host_summary = g_string_free (host_summary_buffer, FALSE);

  print_report_context_cleanup (&ctx);

  if (fclose (out))
    {
      g_warning ("%s: fclose failed: %s",
                 __func__,
                 strerror (errno));
      return -1;
    }

  return 0;

  failed_delta_report:
    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);
  failed_print_report_host:
    if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
    g_hash_table_destroy (f_host_ports);

    g_free (compliance_levels);
    if (strcmp (ctx.tsk_usage_type, "audit") == 0)
      {
        g_hash_table_destroy (f_host_compliant);
        g_hash_table_destroy (f_host_notcompliant);
        g_hash_table_destroy (f_host_incomplete);
        g_hash_table_destroy (f_host_undefined);
      }
    else
      {
        g_hash_table_destroy (f_host_criticals);
        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);
      }
  fail:
    tz_revert (ctx.zone, ctx.tz, ctx.old_tz_override);
    print_report_context_cleanup (&ctx);
    fclose (out);
    return -1;
}

/**
 * @brief Generate a report.
 *
 * @param[in]  report             Report.
 * @param[in]  delta_report       Report to compare with.
 * @param[in]  get                GET data for report.
 * @param[in]  report_format      Report format.
 * @param[in]  report_config      Report config.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @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 timezone used in report.
 * @param[out] host_summary    Summary of results per host.
 *
 * @return Contents of report on success, NULL on error.
 */
gchar *
manage_report (report_t report, report_t delta_report, const get_data_t *get,
               const report_format_t report_format,
               const report_config_t report_config,
               int notes_details, int overrides_details,
               gsize *output_length, gchar **extension, gchar **content_type,
               gchar **filter_term_return, gchar **zone_return,
               gchar **host_summary)
{
  task_t task;
  gchar *xml_start, *xml_file, *output_file;
  char xml_dir[] = "/tmp/gvmd_XXXXXX";
  int ret;
  GList *used_rfps;
  GError *get_error;
  gchar *output;
  gsize output_len;

  used_rfps = 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", __func__);
      return NULL;
    }

  xml_start = g_strdup_printf ("%s/report-start.xml", xml_dir);
  ret = print_report_xml_start (report, delta_report, task, xml_start, get,
                                notes_details, overrides_details,
                                1 /* result_tags */,
                                0 /* ignore_pagination */,
                                0 /* lean */,
                                filter_term_return, zone_return, host_summary);
  if (ret)
    {
      g_free (xml_start);
      gvm_file_remove_recurse (xml_dir);
      return NULL;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);

  if (report_format > 0)
    {
      /* Apply report format(s) */
      gchar* report_format_id = report_format_uuid (report_format);

      output_file = apply_report_format (report_format_id, report_config,
                                         xml_start, xml_file, xml_dir,
                                         &used_rfps);
      g_free (report_format_id);
    }
  else
    {
      print_report_xml_end (xml_start, xml_file, -1, 0);
      output_file = g_strdup(xml_file);
    }

  if (output_file == NULL)
    {
      g_warning ("%s: No file returned for report format", __func__);
    }
  g_free (xml_file);
  g_free (xml_start);

  /* Read the script output from file. */
  if (output_file == NULL)
    {
      gvm_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_warning ("%s: Failed to get output: %s",
                  __func__,
                  get_error->message);
      g_error_free (get_error);
      if (extension) g_free (*extension);
      if (content_type) g_free (*content_type);
      gvm_file_remove_recurse (xml_dir);
      return NULL;
    }

  /* Remove the directory. */

  gvm_file_remove_recurse (xml_dir);

  /* Set convenience return parameters. */

  if ((report_format > 0) && (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_uuid(report_format);

      init_report_format_iterator (&formats, &report_format_get);
      if (next (&formats) == FALSE)
        {
          g_free (report_format_get.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_get.id);
    }


  /* Return the output. */

  if (output_length) *output_length = output_len;

  return output;
}

/**
 * @brief Size of base64 chunk in manage_send_report.
 */
#define MANAGE_SEND_REPORT_CHUNK64_SIZE 262144

/**
 * @brief Size of file chunk in manage_send_report.
 */
#define MANAGE_SEND_REPORT_CHUNK_SIZE (MANAGE_SEND_REPORT_CHUNK64_SIZE * 3 / 4)

/**
 * @brief Generate a report.
 *
 * @param[in]  report             Report.
 * @param[in]  delta_report       Report to compare with.
 * @param[in]  report_format      Report format.
 * @param[in]  report_config      Report config.
 * @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]  result_tags        Whether to include tags in results.
 * @param[in]  ignore_pagination  Whether to ignore pagination.
 * @param[in]  lean               Whether to send lean report.
 * @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 trigger with the report,
 *                                instead of getting report.  NULL to get
 *                                report.
 * @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,
                    report_config_t report_config,
                    const get_data_t *get,
                    int notes_details, int overrides_details, int result_tags,
                    int ignore_pagination, int lean, 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 gchar* prefix)
{
  task_t task;
  gchar *xml_start, *xml_file;
  char xml_dir[] = "/tmp/gvmd_XXXXXX";
  int ret;
  GList *used_rfps;
  gchar *output_file;
  char chunk[MANAGE_SEND_REPORT_CHUNK_SIZE + 1];
  FILE *stream;

  used_rfps = NULL;

  if (report_task (report, &task))
    return -1;

  /* Trigger alert 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 = trigger (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 > 0)
      && (report_format_predefined (report_format) == 0)
      && (report_format_trust (report_format) != TRUST_YES))
    return -1;

  if (report_config
      && report_config_report_format (report_config) != report_format)
    {
      g_warning ("%s: report config is not compatible with report_format",
                 __func__);
      return -1;
    }

  if (mkdtemp (xml_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed", __func__);
      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,
                                notes_details, overrides_details, result_tags,
                                ignore_pagination, lean, NULL, NULL, NULL);
  if (ret)
    {
      g_free (xml_start);
      gvm_file_remove_recurse (xml_dir);
      if (ret == 2)
        return 2;
      return -1;
    }

  xml_file = g_strdup_printf ("%s/report.xml", xml_dir);

  if (report_format > 0)
    {
      /* Apply report format(s). */
      gchar* report_format_id = report_format_uuid (report_format);

      output_file = apply_report_format (report_format_id, report_config,
                                         xml_start, xml_file, xml_dir,
                                         &used_rfps);
      g_free (report_format_id);
    }
  else
    {
      print_report_xml_end (xml_start, xml_file, -1, 0);
      output_file = g_strdup(xml_file);
    }

  if (output_file == NULL)
    {
      g_warning ("%s: No file returned for report format", __func__);
    }

  /* 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",
                  __func__,
                  strerror (errno));
      gvm_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", __func__);
      gvm_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)
        {
          ret = fread (dest, 1, left, stream);
          if (ferror (stream))
            {
              fclose (stream);
              g_warning ("%s: error after fread", __func__);
              gvm_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", __func__);
                  gvm_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", __func__);
                  gvm_file_remove_recurse (xml_dir);
                  return -1;
                }
            }
        }

      /* Check if there's more. */

      if (feof (stream))
        break;
    }

  fclose (stream);

  /* Remove the directory. */

  gvm_file_remove_recurse (xml_dir);

  /* Return the output. */

  return 0;
}

/**
 * @brief Generate the hash value for the entity of the result and
 * check if osp result for report already exists
 *
 * @param[in]  report      Report.
 * @param[in]  task        Task.
 * @param[in]  r_entity    entity of the result.
 * @param[out] entity_hash_value  The generated hash value of r_entity.
 * @param[out] hashed_osp_results  A GHashtable containing hashed osp results.
 *
 * @return     "1" if osp result already exists, else "0"
 */
static int
check_osp_result_exists (report_t report, task_t task,
                         entity_t r_entity, char **entity_hash_value,
                         GHashTable *hashed_osp_results)
{
  GString *entity_string;
  int return_value = 0;

  entity_string = g_string_new ("");
  print_entity_to_string (r_entity, entity_string);
  *entity_hash_value = get_md5_hash_from_string (entity_string->str);

  /* The hash map is used to to filter duplicates that may exist within
   * the same batch of results fetched from the scanner even before
   * insertion in the database while the SQL is used to check for duplicates
   * across batches. */

  if (g_hash_table_contains (hashed_osp_results, *entity_hash_value))
    {
      return_value = 1;
    }
  else
    {
      g_hash_table_insert (hashed_osp_results, g_strdup(*entity_hash_value),
                           GINT_TO_POINTER(1));
      if (sql_int ("SELECT EXISTS"
                  " (SELECT * FROM results"
                  "  WHERE report = %llu and hash_value = '%s');",
                  report, *entity_hash_value))
        {
          const char *desc, *type, *severity, *host;
          const char *hostname, *port, *qod, *path;
          gchar *quoted_desc, *quoted_type, *quoted_host;
          gchar *quoted_hostname, *quoted_port, *quoted_path;
          double severity_double = 0.0;
          int qod_int = QOD_DEFAULT;

          host = entity_attribute (r_entity, "host");
          hostname = entity_attribute (r_entity, "hostname");
          type = entity_attribute (r_entity, "type");
          desc = entity_text (r_entity);
          port = entity_attribute (r_entity, "port");
          severity = entity_attribute (r_entity, "severity");
          qod = entity_attribute (r_entity, "qod");
          path = entity_attribute (r_entity, "uri");

          if (!qod)
            {
              qod_int = QOD_DEFAULT;
            }
          else
            {
              qod_int = atoi (qod);
              if (qod_int <= 0 || qod_int > 100)
                qod_int = QOD_DEFAULT;
            }

          if (!severity || !strcmp (severity, ""))
            {
              if (!strcmp (type, severity_to_type (SEVERITY_ERROR)))
                severity_double = SEVERITY_ERROR;
              else
                {
                  g_debug ("%s: Result without severity", __func__);
                  return 0;
                }
            }
          else
            {
              severity_double = strtod (severity, NULL);
            }

          quoted_host = sql_quote (host ?: "");
          quoted_hostname = sql_quote (hostname ?: "");
          quoted_type = sql_quote (type ?: "");
          quoted_desc = sql_quote (desc ?: "");
          quoted_port = sql_quote (port ?: "");
          quoted_path = sql_quote (path ?: "");

          if (sql_int ("SELECT EXISTS"
                      " (SELECT * FROM results"
                      "   WHERE report = %llu and hash_value = '%s'"
                      "    and host = '%s' and hostname = '%s'"
                      "    and type = '%s' and description = '%s'"
                      "    and port = '%s' and severity = %1.1f"
                      "    and qod = %d and path = '%s'"
                      " );",
                      report, *entity_hash_value,
                      quoted_host, quoted_hostname,
                      quoted_type, quoted_desc,
                      quoted_port, severity_double,
                      qod_int, quoted_path))
            {
              return_value = 1;
            }

          g_free (quoted_host);
          g_free (quoted_hostname);
          g_free (quoted_type);
          g_free (quoted_desc);
          g_free (quoted_port);
          g_free (quoted_path);
      }
    }

  if (return_value)
    {
      g_debug ("Captured duplicate result, report: %llu hash_value: %s",
                report, *entity_hash_value);
      g_debug ("Entity string: %s", entity_string->str);
    }
  g_string_free(entity_string, TRUE);
  return return_value;
}

/**
 * @brief Check if host detail exists.
 *
 * @param[in]  report      Report.
 * @param[in]  host        Host.
 * @param[in]  s_type      Source type.
 * @param[in]  s_name      Source name.
 * @param[in]  s_desc      Source description.
 * @param[in]  name        Name.
 * @param[in]  value       Value.
 * @param[out] detail_hash_value  The generated hash value.
 * @param[out] hashed_host_details  A GHashtable containing hashed host details.
 *
 * @return     "1" if osp result already exists, else "0"
 */
int
check_host_detail_exists (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 **detail_hash_value,
                          GHashTable *hashed_host_details)
{
  char *hash_string;
  long long int report_host;
  int return_value = 0;

  hash_string = g_strdup_printf ("%llu-%s-%s-%s-%s-%s-%s", report, host, s_type,
                                 s_name, s_desc, name, value);
  *detail_hash_value = get_md5_hash_from_string (hash_string);

  /* The hash map is used to to filter duplicates that may exist within
   * the same batch of results fetched from the scanner even before
   * insertion in the database while the SQL is used to check for duplicates
   * across batches. */

  if (g_hash_table_contains (hashed_host_details, *detail_hash_value))
    {
      return_value = 1;
    }
  else
    {
        g_hash_table_insert (hashed_host_details, g_strdup(*detail_hash_value),
                        GINT_TO_POINTER(1));
        sql_int64 (&report_host, "SELECT id FROM report_hosts"
                                " WHERE report = %llu AND host = '%s';",
                  report, host);

        if (sql_int ("SELECT EXISTS"
                    " (SELECT * FROM report_host_details"
                    "  WHERE report_host = %llu and hash_value = '%s');",
                    report_host, *detail_hash_value))
          {
            gchar *quoted_host = sql_quote (host);
            gchar *quoted_s_type = sql_quote (s_type);
            gchar *quoted_s_name = sql_quote (s_name);
            gchar *quoted_s_desc = sql_quote (s_desc);
            gchar *quoted_name = sql_quote (name);
            gchar *quoted_value = sql_quote (value);

            if (sql_int ("SELECT EXISTS"
                        " (SELECT * FROM report_host_details"
                        "   WHERE report_host = %llu and hash_value = '%s'"
                        "   and source_type = '%s' and source_name = '%s'"
                        "   and source_description = '%s'"
                        "   and  name = '%s' and value = '%s'"
                        " );",
                        report_host, *detail_hash_value, quoted_s_type,
                        quoted_s_name, quoted_s_desc, quoted_name, quoted_value))
              {
                return_value = 1;
              }
            g_free (quoted_host);
            g_free (quoted_s_type);
            g_free (quoted_s_name);
            g_free (quoted_s_desc);
            g_free (quoted_name);
            g_free (quoted_value);
          }
    }

  if (return_value)
    {
      g_debug ("Captured duplicate report host detail, report: %llu hash_value: %s",
                report, *detail_hash_value);
      g_debug ("Hash string: %s", hash_string);
    }
  g_free (hash_string);
  return return_value;
}

/**
 * @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;
  gboolean has_results = FALSE;
  GArray *results_array;
  GHashTable *hashed_osp_results;
  GHashTable *hashed_host_details;

  assert (task);
  assert (report);
  assert (report_xml);

  if (parse_entity (report_xml, &entity))
    {
      g_warning ("Couldn't parse %s OSP scan report", report_xml);
      return;
    }

  hashed_osp_results = g_hash_table_new_full (g_str_hash,
                                              g_str_equal,
                                              g_free,
                                              NULL);

  hashed_host_details = g_hash_table_new_full (g_str_hash,
                                               g_str_equal,
                                               g_free,
                                               NULL);

  sql_begin_immediate ();
  /* Set the report's start and end times. */
  results_array = g_array_new (TRUE, TRUE, sizeof (result_t));
  start_time = 0;
  str = entity_attribute (entity, "start_time");
  if (str)
    {
      start_time = atoi (str);
      set_scan_start_time_epoch (report, start_time);
    }

  end_time = 0;
  str = entity_attribute (entity, "end_time");
  if (str)
    {
      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;
  if (results)
    has_results = TRUE;

  defs_file = task_definitions_file (task);
  while (results)
    {
      result_t result;
      const char *type, *name, *severity, *host, *hostname, *test_id, *port;
      const char *qod, *path;
      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");
      hostname = entity_attribute (r_entity, "hostname");
      port = entity_attribute (r_entity, "port") ?: "";
      qod = entity_attribute (r_entity, "qod") ?: "";
      path = entity_attribute (r_entity, "uri") ?: "";

      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"))
        {
          gchar *hash_value = NULL;
          if (!check_host_detail_exists (report, host, "osp", "",
                                         "OSP Host Detail", name,
                                         entity_text (r_entity), &hash_value,
                                         hashed_host_details))
            {
              insert_report_host_detail (report, host, "osp", "", "OSP Host Detail",
                                         name, entity_text (r_entity), hash_value);
            }
          g_free (hash_value);
          results = next_entities (results);
          continue;
        }
      else if (g_str_has_prefix (test_id, "1.3.6.1.4.1.25623.1."))
        {
          nvt_id = g_strdup (test_id);
          severity_str = nvt_severity (test_id, type);
          desc = g_strdup (entity_text (r_entity));
        }
      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;
      if (port && strcmp (port, "general/Host_Details") == 0)
        {
          /* TODO: This should probably be handled by the "Host Detail"
           *        result type with extra source info in OSP.
           */
          if (manage_report_host_detail (report, host, desc, hashed_host_details))
            g_warning ("%s: Failed to add report detail for host '%s': %s",
                       __func__, host, desc);
        }
      else if (host && nvt_id && desc && (strcmp (nvt_id, "HOST_START") == 0))
        {
          set_scan_host_start_time_ctime (report, host, desc);
        }
      else if (host && nvt_id && desc && (strcmp (nvt_id, "HOST_END") == 0))
        {
          set_scan_host_end_time_ctime (report, host, desc);
          add_assets_from_host_in_report (report, host);
        }
      else
        {
          char *hash_value;
          if (!check_osp_result_exists (report, task, r_entity, &hash_value,
                                        hashed_osp_results))
            {
              result = make_osp_result (task,
                                        host,
                                        hostname,
                                        nvt_id,
                                        type,
                                        desc,
                                        port ?: "",
                                        severity_str ?: severity,
                                        qod_int,
                                        path,
                                        hash_value);
              g_array_append_val (results_array, result);
            }
          g_free (hash_value);
        }
      g_free (nvt_id);
      g_free (desc);
      g_free (severity_str);
      results = next_entities (results);
    }

  if (has_results)
    {
      sql ("UPDATE reports SET modification_time = m_now() WHERE id = %llu;",
           report);
      report_add_results_array (report, results_array);
    }


 end_parse_osp_report:
  sql_commit ();
  g_array_free (results_array, TRUE);
  g_hash_table_destroy (hashed_osp_results);
  g_hash_table_destroy (hashed_host_details);
  g_free (defs_file);
  free_entity (entity);
}


/* More task stuff. */

/**
 * @brief Return the trend of a task, given counts.
 *
 * @param[in]  criticals_a  Number of criticals on earlier report.
 * @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]  criticals_b  Number of criticals on later 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 criticals_a, int holes_a, int warns_a, int infos_a,
                 double severity_a, int criticals_b, 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 (criticals_a > 0)
    threat_a = 5;
  else 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 (criticals_b > 0)
    threat_b = 5;
  else 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 (criticals_a)
    {
      if (criticals_a > criticals_b)
        return "more";
      if (criticals_a < criticals_b)
        return "less";
      return "same";
    }

  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]  criticals_a  Number of criticals on earlier report.
 * @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]  criticals_b  Number of criticals on later 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 criticals_a, int holes_a,
                            int warns_a, int infos_a, double severity_a,
                            int criticals_b, 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 (criticals_a, holes_a, warns_a, infos_a, severity_a,
                          criticals_b, holes_b, warns_b, infos_b, severity_b);
}

/**
 * @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 = gvm_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, target_location,"
       "  scanner_location, schedule_location, alterable,"
       "  creation_time, modification_time, usage_type)"
       " VALUES ((SELECT id FROM users WHERE users.uuid = '%s'),"
       "         '%s', '%s', 0, '%s', 0, 0, 0, 0, 0, 0, 0, 0, m_now (),"
       "         m_now (), 'scan');",
       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]  task     The task.
 */
void
make_task_complete (task_t task)
{
  assert (task);
  cache_permissions_for_resource ("task", task, NULL);

  event (EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_NEW, task, 0);
}

/**
 * @brief Set the name of a task.
 *
 * @param[in]  task  A task.
 * @param[in]  name  New name.
 */
void
set_task_name (task_t task, const char *name)
{
  gchar *quoted_name;

  quoted_name = sql_quote (name ? name : "");
  sql ("UPDATE tasks SET name = '%s', modification_time = m_now ()"
       " WHERE id = %llu;", quoted_name, task);
  g_free (quoted_name);
}

/**
 * @brief Set the comment of a task.
 *
 * @param[in]  task  A task.
 * @param[in]  comment  New comment.
 */
static void
set_task_comment (task_t task, const char *comment)
{
  gchar *quoted_comment;

  assert (comment);

  quoted_comment = sql_quote (comment ? comment : "");
  sql ("UPDATE tasks SET comment = '%s', modification_time = m_now ()"
       " WHERE id = %llu;", quoted_comment, task);
  g_free (quoted_comment);
}

/**
 * @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. < 0 to
 *                         to copy from existing.
 * @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 ();

  ret = copy_resource_lock ("task", name, comment, task_id,
                            "config, target, oci_image_target,"
                            " schedule, schedule_periods,"
                            " scanner, schedule_next_time,"
                            " config_location, target_location,"
                            " schedule_location, scanner_location,"
                            " oci_image_target_location, hosts_ordering,"
                            " usage_type, alterable",
                            1, &new, &old);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  if (alterable >= 0)
    sql ("UPDATE tasks SET alterable = %i, hidden = 0 WHERE id = %llu;",
         alterable,
         new);
  else
    sql ("UPDATE tasks SET hidden = 0 WHERE id = %llu;",
         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);

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

  cache_permissions_for_resource ("task", new, NULL);

  sql_commit ();
  if (new_task) *new_task = new;
  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, 3 if reports table is locked,
 *         -1 on error.
 */
static int
delete_task_lock (task_t task, int ultimate)
{
  int ret, lock_ret, lock_retries;

  g_debug ("   delete task %llu", task);

  sql_begin_immediate ();

  /* This prevents other processes (for example a START_TASK) from getting
   * a reference to a report ID or the task ID, and then using that
   * reference to try access the deleted report or task.
   *
   * If the task is already active then delete_report (via delete_task)
   * will fail and rollback. */
  lock_retries = LOCK_RETRIES;
  lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
  while ((lock_ret == 0) && (lock_retries > 0))
    {
      lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
      lock_retries--;
    }
  if (lock_ret == 0)
    {
      sql_rollback ();
      return 3;
    }

  if (sql_int ("SELECT hidden FROM tasks WHERE id = %llu;", task))
    {
      sql_rollback ();
      return 1;
    }

  ret = delete_task (task, ultimate);
  if (ret)
    sql_rollback ();
  else
    sql_commit ();
  return ret;
}

/**
 * @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 gmp.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,
 *         3 if reports table is locked,
 *         -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 %llu", task);

  hidden = sql_int ("SELECT hidden from tasks WHERE id = %llu;",
                    *task_pointer);

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

/**
 * @brief Request deletion of a task.
 *
 * Stop the task beforehand with \ref stop_task_internal, if it is running.
 *
 * This is only used for DELETE_TASK in gmp.c.
 *
 * @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, 4 reports table locked, 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;
  int lock_ret, lock_retries;

  /* 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", 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_remove_resource ("task", task, LOCATION_TRASH);
      tickets_remove_task (task);

      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 (current_credentials.uuid == NULL)
    {
      sql_rollback ();
      return -1;
    }

  switch (stop_task_internal (task))
    {
      case 0:    /* Stopped. */
        {
          int ret;

          if (ultimate)
            {
              /* This prevents other processes (for example a START_TASK) from
               * getting a reference to a report ID or the task ID, and then
               * using that reference to try access the deleted report or task.
               *
               * If the task is running already then delete_task will lead to
               * ROLLBACK. */
              lock_retries = LOCK_RETRIES;
              lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
              while ((lock_ret == 0) && (lock_retries > 0))
                {
                  lock_ret = sql_table_lock_wait ("reports", LOCK_TIMEOUT);
                  lock_retries--;
                }
              if (lock_ret == 0)
                {
                  sql_rollback ();
                  return 4;
                }
            }

          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 on error.
 */
int
delete_task (task_t task, int ultimate)
{
  g_debug ("   delete task %llu", task);

  assert (current_credentials.uuid);

  if (ultimate)
    {
      if (delete_reports (task))
        return -1;

      permissions_set_orphans ("task", task,
                               task_in_trash (task)
                                ? LOCATION_TRASH
                                : LOCATION_TABLE);
      tags_remove_resource ("task", task,
                            task_in_trash (task)
                              ? LOCATION_TRASH
                              : LOCATION_TABLE);
      tickets_remove_task (task);

      sql ("DELETE FROM results_trash WHERE task = %llu;", task);
      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 tag_resources"
           " 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 tag_resources"
           " SET resource_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT id FROM results"
           "                  WHERE results.task = %llu);",
           task);

      sql ("UPDATE tag_resources_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 tag_resources_trash"
           " SET resource_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE resource_type = 'result'"
           " AND resource IN (SELECT id FROM results"
           "                  WHERE results.task = %llu);",
           task);

      sql ("INSERT INTO results_trash"
           " (uuid, task, host, port, nvt, result_nvt, type, description,"
           "  report, nvt_version, severity, qod, qod_type, owner, date,"
           "  hostname, path)"
           " SELECT uuid, task, host, port, nvt, result_nvt, type,"
           "        description, report, nvt_version, severity, qod,"
           "         qod_type, owner, date, hostname, path"
           " FROM results"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           task);

      tickets_trash_task (task);

      sql ("DELETE FROM results"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           task);

      sql ("DELETE FROM report_counts"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           task);

      sql ("UPDATE tasks SET hidden = 2 WHERE id = %llu;", task);
    }

  delete_permissions_cache_for_resource ("task", task);

  return 0;
}

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

      tickets_remove_task (task);

      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.
 */
static int
cleanup_schedule_times ()
{
  sql ("UPDATE tasks"
        " SET schedule_next_time"
        "   = (SELECT next_time_ical (icalendar, m_now()::bigint, timezone)"
        "      FROM schedules"
        "      WHERE schedules.id = tasks.schedule)"
        " WHERE schedule_next_time != 0"
        "   AND schedule_next_time"
        "         = (SELECT next_time_ical (icalendar, m_now()::bigint,"
        "                                   timezone)"
        "              FROM schedules"
        "             WHERE schedules.id = tasks.schedule)"
        "   AND schedule_next_time"
        "         != (SELECT next_time_ical (icalendar, m_now()::bigint,"
        "                                    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 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 for a specific permission, given a UUID.
 *
 * @param[in]   uuid      UUID of task.
 * @param[out]  task      Task return, 0 if successfully 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 successfully 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 successfully 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 successfully 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 Find a report in the trashcan for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of report.
 * @param[out]  report      Report return, 0 if successfully failed to find
 *                          report.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find report), TRUE on error.
 */
static gboolean
find_trash_report_with_permission (const char *uuid, report_t *report,
                                   const char *permission)
{
  return find_resource_with_permission ("report", uuid, report, permission, 1);
}

/**
 * @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_id  Task.
 * @param[in]  name     Name of file.
 * @param[in]  content  Content for file in base64 encoding.
 *
 * @return 0 success, 1 failed to find task, -1 error.
 */
int
manage_task_update_file (const gchar *task_id, const char *name,
                         const void *content)
{
  gchar* quoted_name = sql_quote (name);
  gchar* quoted_content = sql_quote (content);
  task_t task;

  /** @todo Probably better to save ASCII instead of base64. */

  task = 0;
  if (find_task_with_permission (task_id, &task, "modify_task"))
    return -1;
  else if (task == 0)
    return 1;

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

  return 0;
}

/**
 * @brief Remove a file on a task.
 *
 * @param[in]  task_id  Task.
 * @param[in]  name     Name of file.
 *
 * @return 0 success, 1 failed to find task, -1 error.
 */
int
manage_task_remove_file (const gchar *task_id, const char *name)
{
  task_t task;

  task = 0;
  if (find_task_with_permission (task_id, &task, "modify_task"))
    return -1;
  else if (task == 0)
    return 1;

  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, "%s", 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);

/**
 * @brief Modify a task.
 *
 * @param[in]  task_id     Task.
 * @param[in]  name        Name of file.
 * @param[in]  comment     Comment.
 * @param[in]  scanner_id  Scanner.
 * @param[in]  target_id   Target.
 * @param[in]  config_id   Config.
 * @param[in]  observers   Observers.
 * @param[in]  alerts      Alerts.
 * @param[in]  alterable   Alterable.
 * @param[in]  groups      Groups.
 * @param[in]  schedule_id  Schedule.
 * @param[in]  schedule_periods  Period of schedule.
 * @param[in]  preferences       Preferences.
 * @param[in]  hosts_ordering    Host scan order.
 * @param[in]  agent_group_id    Agent group.
 * @param[in]  oci_image_target_id  OCI image target.
 * @param[out] fail_alert_id     Alert when failed to find alert.
 * @param[out] fail_group_id     Group when failed to find group.
 *
 * @return 0 success, 1 failed to find task, 2 status must be new to edit
 *         scanner, 3 failed to find scanner, 4 failed to find config, 5 status
 *         must be new to edit config, 6 user name validation failed, 7 failed
 *         to find user, 8 failed to find alert, 9 task must be new to modify
 *         alterable state, 10 failed to find group, 11 failed to find schedule,
 *         12 failed to find target, 13 invalid auto_delete value, 14 auto
 *         delete count out of range, 15 config and scanner types mismatch,
 *         16 status must be new to edit target, 17 for import tasks only
 *         certain fields may be edited, 18 failed to find agent group,
           19 failed to find OCI image target,
           20 cannot set asset preferences for container image task,
           -1 error.
 */
int
modify_task (const gchar *task_id, const gchar *name,
             const gchar *comment, const gchar *scanner_id,
             const gchar *target_id, const gchar *config_id,
             const gchar *observers, array_t *alerts,
             const gchar *alterable, array_t *groups,
             const gchar *schedule_id,
             const gchar *schedule_periods,
             array_t *preferences,
             const gchar *hosts_ordering,
             const gchar *agent_group_id,
             const gchar* oci_image_target_id,
             gchar **fail_alert_id,
             gchar **fail_group_id)
{
  task_t task;
  int type_of_scanner;
  scanner_t scanner;

  /* @todo Probably better to rollback on error. */

  task = 0;
  if (find_task_with_permission (task_id, &task, "modify_task"))
    return -1;
  if (task == 0)
    return 1;


  if ((task_target (task) == 0
       && (agent_group_id == NULL)
       && (oci_image_target_id == NULL))
      && (alerts->len || schedule_id))
    return 17;

  switch (modify_task_check_config_scanner (task, config_id, scanner_id))
    {
      case 0:
        break;
      case 1:
        return 15;
      case 2:
        return 4;
      case 3:
        return 3;
      default:
        assert (0);
        /* fallthrough */
      case -1:
        return -1;
    }

  if (name)
    set_task_name (task, name);

  if (comment)
    set_task_comment (task, comment);

  scanner = 0;
  if (scanner_id)
    {
      if (strcmp (scanner_id, "0") == 0)
        {
          /* Leave it as is. */
        }
      else if ((task_run_status (task) != TASK_STATUS_NEW)
               && (task_alterable (task) == 0))
        return 2;
      else if (find_scanner_with_permission (scanner_id,
                                             &scanner,
                                             "get_scanners"))
        return -1;
      else if (scanner == 0)
        return 3;
      else
        set_task_scanner (task, scanner);
    }

  if (scanner == 0)
    type_of_scanner = scanner_type (task_scanner (task));
  else
    type_of_scanner = scanner_type (scanner);

  if (config_id
      && (type_of_scanner != SCANNER_TYPE_CVE)
      && (type_of_scanner != SCANNER_TYPE_CONTAINER_IMAGE))
    {
      config_t config;

      config = 0;
      if (strcmp (config_id, "0") == 0)
        {
          /* Leave it as it is. */
        }
      else if ((task_run_status (task) != TASK_STATUS_NEW)
               && (task_alterable (task) == 0))
        return 5;
      else if (find_config_with_permission (config_id, &config, "get_configs"))
        return -1;
      else if (config == 0)
        return 4;
      else
       set_task_config (task, config);
    }

  if (observers)
    {
      switch (set_task_observers (task, observers))
        {
          case 0:
            break;
          case 1:
            return 6;
          case 2:
            return 7;
            break;
          case -1:
          default:
            return -1;
        }
    }

  if (alerts->len)
    {
      switch (set_task_alerts (task, alerts, fail_alert_id))
        {
          case 0:
            break;
          case 1:
            return 8;
          case -1:
          default:
            return -1;
        }
    }

  if (alterable && (task_alterable (task) != atoi (alterable)))
    {
      if (task_run_status (task) != TASK_STATUS_NEW)
        return 9;
      set_task_alterable (task, strcmp (alterable, "0"));
    }

  if (groups->len)
    {
      switch (set_task_groups (task, groups, fail_group_id))
        {
          case 0:
            break;
          case 1:
            return 10;
          case -1:
          default:
            return -1;
        }
    }

  if (schedule_id)
    {
      schedule_t schedule = 0;
      int periods;

      periods = schedule_periods ? atoi (schedule_periods) : 0;

      if (strcmp (schedule_id, "0") == 0)
        set_task_schedule (task, 0, periods);
      else if (find_schedule_with_permission (schedule_id,
                                              &schedule,
                                              "get_schedules"))
        return -1;
      else if (schedule == 0)
        return 11;
      else if (set_task_schedule (task, schedule, periods))
        return -1;
    }
  else if (schedule_periods && strlen (schedule_periods))
    set_task_schedule_periods (task_id,
                               atoi (schedule_periods));

  if (target_id)
    {
      target_t target;

      target = 0;
      if (strcmp (target_id, "0") == 0)
        {
          /* Leave it as it is. */
        }
      else if ((task_run_status (task) != TASK_STATUS_NEW)
               && (task_alterable (task) == 0))
        return 16;
      else if (find_target_with_permission (target_id,
                                            &target,
                                            "get_targets"))
        return -1;
      else if (target == 0)
        return 12;
      else
        set_task_target (task, target);
    }
#if ENABLE_AGENTS
  if (agent_group_id) {
    agent_group_t agent_group;

    agent_group = 0;
    if ((task_run_status(task) != TASK_STATUS_NEW)
        && (task_alterable(task) == 0))
      return 16;
    else if (find_agent_group_with_permission(agent_group_id,
                                              &agent_group,
                                              "get_agent_groups"))
      return -1;
    else if (agent_group == 0)
      return 18;
    else
      {
        set_task_agent_group_and_location (task, agent_group);
      }
  }
#endif

#if ENABLE_CONTAINER_SCANNING
  if (oci_image_target_id)
    {
      oci_image_target_t oci_image_target = 0;

      if ((task_run_status (task) != TASK_STATUS_NEW)
          && (task_alterable (task) == 0))
        return 16;
      else if (find_oci_image_target_with_permission (oci_image_target_id,
                                                      &oci_image_target,
                                                      "get_oci_image_targets"))
        return -1;
      else if (oci_image_target == 0)
        return 19;
      else
        set_task_oci_image_target (task, oci_image_target);
    }
#endif /* ENABLE_CONTAINER_SCANNING */

  if (preferences)
    switch (set_task_preferences (task, preferences))
      {
        case 0:
          break;
        case 1:
          return 13;
        case 2:
          return 14;
        case 3:
          return 20;
        default:
          return -1;
      }

  if (hosts_ordering)
    set_task_hosts_ordering (task, hosts_ordering);

  return 0;
}


/* 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 successfully 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, for use in override or note.
 *
 * @param[in]  port  A port.
 *
 * @return 0 success, 1 failed.
 */
static int
validate_results_port (const char *port)
{
  long int num;
  char *end;

  if (!port)
    return 1;

  if (strcmp (port, "package") == 0)
    return 0;

  /* "cpe:abc", "general/tcp", "20/udp"
   *
   * We keep the "general/tcp" case pretty open because it is not clearly
   * restricted anywhere, and is already used with non-alphanumerics in
   * "general/Host_Details".  We exclude whitespace, ',' and ';' to prevent
   * users from entering lists of ports.
   *
   * Similarly, the CPE case forbids whitespace, but allows ',' and ';' as
   * these may occur in valid CPEs. */
  if (g_regex_match_simple
       ("^(cpe:[^\\s]+|general/[^\\s,;]+|[0-9]+/[[:alnum:]]+)$",
        port, 0, 0)
      == FALSE)
    return 1;

  if (g_str_has_prefix (port, "cpe:")
      || g_str_has_prefix (port, "general/"))
    return 0;

  num = strtol (port, &end, 10);
  if (*end != '/')
    return 1;
  if (num > 0 && num <= 65535)
    return 0;
  return 1;
}

/**
 * @brief Convert alive test array to alive test bitfield.
 *
 * @param[in]  alive_tests NULL-terminated array of alive tests.
 *
 * @return Alive test bitfield, or -1 on error.
 */
static int
alive_test_from_array (GPtrArray *alive_tests)
{
  alive_test_t alive_test_bitfield = 0;

  if (alive_tests->len == 0)
    return 0;

  for (int i = 0; i < alive_tests->len; i++)
    {
      const char *item = g_ptr_array_index (alive_tests, i);
      if (strcasecmp (item, "Scan Config Default") == 0)
        {
          alive_test_bitfield = 0;
          break;
        }
      else if (strcasecmp (item, "Consider Alive") == 0)
        {
          alive_test_bitfield = ALIVE_TEST_CONSIDER_ALIVE;
          break;
        }
      else if (strcasecmp (item, "ARP") == 0
          || strcasecmp (item, "ARP Ping") == 0)
        alive_test_bitfield |= ALIVE_TEST_ARP;
      else if (strcmp (item, "ICMP") == 0
                || strcmp (item, "ICMP Ping") == 0)
        alive_test_bitfield |= ALIVE_TEST_ICMP;
      else if (strcmp (item, "TCP-ACK Service") == 0
                || strcmp (item, "TCP-ACK Service Ping") == 0)
        alive_test_bitfield |= ALIVE_TEST_TCP_ACK_SERVICE;
      else if (strcmp (item, "TCP-SYN Service") == 0
                || strcmp (item, "TCP-SYN Service Ping") == 0)
        alive_test_bitfield |= ALIVE_TEST_TCP_SYN_SERVICE;
      else
        {
          g_debug ("%s: Invalid alive_tests item: %s", __func__, item);
          alive_test_bitfield = -1;
          break;
        }
    }
  return alive_test_bitfield;
}

/**
 * @brief Convert legacy alive test name string 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 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 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_elevate_credential  SSH previlige escalation 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]   krb5_credential       Kerberos 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 array.
 * @param[in]   alive_test_str          Legacy alive tests string.
 * @param[in]   allow_simultaneous_ips  Scanner preference allow_simultaneous_ips.
 * @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 SSH elevate credential type,
 *         10 invalid SMB credential type, 11 invalid ESXi credential type,
 *         12 invalid SNMP credential type, 13 port range or port list required,
 *         14 SSH elevate credential without an SSH credential,
 *         15 elevate credential must be different from the SSH credential,
 *         16 invalid Kerberos 5 credential type,
 *         30 cannot use both alive_tests string and sub-elements,
 *         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,
               credential_t ssh_elevate_credential,
               const char* ssh_port, credential_t smb_credential,
               credential_t esxi_credential, credential_t snmp_credential,
               credential_t krb5_credential,
               const char *reverse_lookup_only,
               const char *reverse_lookup_unify,
               GPtrArray *alive_tests,
               const char *alive_test_str,
               const char *allow_simultaneous_ips,
               target_t* target)
{
  gchar *quoted_name, *quoted_hosts, *quoted_exclude_hosts, *quoted_comment;
  gchar *port_list_comment, *quoted_ssh_port, *clean, *clean_exclude;
  gchar *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;

  if (alive_tests && alive_tests->len
      && alive_test_str && strlen (alive_test_str))
    return 30;
  else if (alive_tests && alive_tests->len)
    alive_test = alive_test_from_array (alive_tests);
  else if (alive_test_str && strlen (alive_test_str))
    alive_test = alive_test_from_string (alive_test_str);
  else
    alive_test = 0;
  if (alive_test <= -1)
    return 7;

  if (ssh_elevate_credential && (!ssh_credential))
    return 14;

  if (ssh_credential && (ssh_elevate_credential == ssh_credential))
    return 15;

  sql_begin_immediate ();

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

  if (resource_with_name_exists (name, "target", 0))
    {
      sql_rollback ();
      return 1;
    }

  if (port_list_id)
    {
      if (find_port_list_with_permission (port_list_id, &port_list,
                                          "get_port_lists")
          || (port_list == 0))
        {
          sql_rollback ();
          return 6;
        }
    }
  else if (port_range == NULL)
    {
      sql_rollback ();
      return 13;
    }
  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)
        {
          sql_rollback ();
          return ret;
        }
    }

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

      g_debug ("asset chosen_hosts: %s", chosen_hosts);
    }
  else
    {
      chosen_hosts = g_strdup (hosts);
      g_debug ("manual chosen_hosts: %s", chosen_hosts);
    }


  clean = clean_hosts (chosen_hosts, &max);
  g_free (chosen_hosts);
  if (exclude_hosts)
    clean_exclude = clean_hosts (exclude_hosts, NULL);
  else
    clean_exclude = g_strdup ("");

  max = manage_count_hosts (clean, clean_exclude);
  if (max <= 0)
    {
      g_free (clean);
      g_free (clean_exclude);
      sql_rollback ();
      return 2;
    }
  if (max > max_hosts)
    {
      g_free (clean);
      g_free (clean_exclude);
      sql_rollback ();
      return 3;
    }
  quoted_hosts = sql_quote (clean);
  quoted_exclude_hosts = sql_quote (clean_exclude);
  g_free (clean);
  g_free (clean_exclude);

  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 (allow_simultaneous_ips
      && strcmp (allow_simultaneous_ips, "0") == 0)
    allow_simultaneous_ips = "0";
  else
    allow_simultaneous_ips = "1";

  quoted_name = sql_quote (name ?: "");

  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,"
       "  allow_simultaneous_ips,"
       "  creation_time, modification_time)"
       " VALUES (make_uuid (), '%s',"
       " (SELECT id FROM users WHERE users.uuid = '%s'),"
       " '%s', '%s', '%s', %llu, '%s', '%s', %i,"
       " %s,"
       " 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,
        allow_simultaneous_ips);

  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")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_usk") && strcmp (type, "cs_up")
#endif
          )
        {
          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 (ssh_elevate_credential)
    {
      gchar *type = credential_type (ssh_elevate_credential);
      if (strcmp (type, "up")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_up")
#endif
         )
        {
          sql_rollback ();
          return 9;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'elevate', %llu, %s);",
           new_target, ssh_elevate_credential, "0");
    }

  if (smb_credential)
    {
      gchar *type = credential_type (smb_credential);
      if (strcmp (type, "up")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_up")
#endif
         )
        {
          sql_rollback ();
          return 10;
        }
      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")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_up")
#endif
        )
        {
          sql_rollback ();
          return 11;
        }
      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")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_snmp")
#endif
         )
        {
          sql_rollback ();
          return 12;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'snmp', %llu, %s);",
           new_target, snmp_credential, "0");
    }

  if (krb5_credential)
    {
      gchar *type = credential_type (krb5_credential);
      if (strcmp (type, "krb5")
#if ENABLE_CREDENTIAL_STORES
          && strcmp (type, "cs_krb5")
#endif
         )
        {
          sql_rollback ();
          g_free (type);
          return 16;
        }
      g_free (type);

      sql ("INSERT INTO targets_login_data"
           " (target, type, credential, port)"
           " VALUES (%llu, 'krb5', %llu, %s);",
           new_target, krb5_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 old_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,"
                       " allow_simultaneous_ips",
                       1, new_target, &old_target);
  if (ret)
    return ret;

  sql ("INSERT INTO targets_login_data (target, type, credential, port)"
       " SELECT %llu, type, credential, port"
       "   FROM targets_login_data"
       "  WHERE target = %llu;",
       *new_target, old_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, 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_remove_resource ("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;",
                   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,"
           "  allow_simultaneous_ips,"
           "  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,"
           "        allow_simultaneous_ips,"
           "        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_remove_resource ("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_elevate_credential_id  SSH previlige escalation 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]   krb5_credential_id  Kerberos 5 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 array.
 * @param[in]   alive_test_str         Alive test string.
 * @param[in]   allow_simultaneous_ips Scanner preference allow_simultaneous_ips.
 *
 * @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,
 *         22 failed to find SSH elevate cred, 23 invalid SSH elevate
 *         credential type, 24 SSH elevate credential without SSH credential,
 *         25 SSH elevate credential equals SSH credential,
 *         26 failed to find Kerberos 5 credential,
 *         27 invalid Kerberos 5 credential type,
 *         28 cannot use both SMB and Kerberos 5 credential,
 *         30 cannot use both alive_tests string and sub-elements,
 *         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_elevate_credential_id,
               const char *ssh_port, const char *smb_credential_id,
               const char *esxi_credential_id, const char* snmp_credential_id,
               const char *krb5_credential_id,
               const char *reverse_lookup_only,
               const char *reverse_lookup_unify,
               GPtrArray *alive_tests,
               const char *alive_test_str,
               const char *allow_simultaneous_ips)
{
  target_t target;
  credential_t ssh_credential = 0;
  credential_t ssh_elevate_credential = 0;
  credential_t smb_credential;
  credential_t krb5_credential;

  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 (allow_simultaneous_ips)
    {
      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      sql ("UPDATE targets SET"
           " allow_simultaneous_ips = '%i',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           strcmp (allow_simultaneous_ips, "0") ? 1 : 0,
           target);
    }

  if (alive_tests && alive_tests->len
      && alive_test_str && strlen (alive_test_str))
    return 30;
  else if (alive_tests && alive_tests->len)
    {
      int alive_test;

      alive_test = alive_test_from_array (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);
    }
  else if (alive_test_str && strlen (alive_test_str))
    {
      int alive_test;

      alive_test = alive_test_from_string (alive_test_str);
      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)
    {
      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")
#if ENABLE_CREDENTIAL_STORES
              && strcmp (type, "cs_up") && strcmp (type, "cs_usk")
#endif
          )
            {
              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 (ssh_elevate_credential_id)
    {
      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      ssh_elevate_credential = 0;
      if (strcmp (ssh_elevate_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (ssh_elevate_credential_id,
                                               &ssh_elevate_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (ssh_elevate_credential == 0)
            {
              sql_rollback ();
              return 22;
            }

          type = credential_type (ssh_elevate_credential);
          if (strcmp (type, "up")
#if ENABLE_CREDENTIAL_STORES
              && strcmp (type, "cs_up")
#endif
            )
            {
              sql_rollback ();
              return 23;
            }
          g_free (type);

          set_target_login_data (target, "elevate", ssh_elevate_credential, 0);
        }
      else
        set_target_login_data (target, "elevate", 0, 0);
    }

  if (smb_credential_id)
    {
      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")
#if ENABLE_CREDENTIAL_STORES
              && strcmp (type, "cs_up")
#endif
             )
            {
              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);
    }
  else
    smb_credential = target_smb_credential (target);

  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")
#if ENABLE_CREDENTIAL_STORES
              && strcmp (type, "cs_up")
#endif
             )
            {
              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")
#if ENABLE_CREDENTIAL_STORES
              && strcmp (type, "cs_snmp")
#endif
             )
            {
              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 (ssh_credential_id || ssh_elevate_credential_id)
    {
      if (!ssh_credential_id)
        ssh_credential = target_ssh_credential (target);
      if (!ssh_elevate_credential_id)
        ssh_elevate_credential = target_ssh_elevate_credential (target);

      if (ssh_elevate_credential && !ssh_credential)
        {
          sql_rollback ();
          return 24;
        }
      if (ssh_credential && (ssh_credential == ssh_elevate_credential))
        {
          sql_rollback ();
          return 25;
        }
    }

  if (krb5_credential_id)
    {
      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      krb5_credential = 0;
      if (strcmp (krb5_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (krb5_credential_id,
                                               &krb5_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (krb5_credential == 0)
            {
              sql_rollback ();
              return 26;
            }

          type = credential_type (krb5_credential);
          if (strcmp (type, "krb5"))
            {
              sql_rollback ();
              g_free (type);
              return 27;
            }
          g_free (type);

          set_target_login_data (target, "krb5", krb5_credential, 0);
        }
      else
        set_target_login_data (target, "krb5", 0, 0);
    }
  else
    krb5_credential = target_krb5_credential (target);

  if (smb_credential && krb5_credential)
    {
      sql_rollback ();
      return 28;
    }

  if (exclude_hosts)
    {
      gchar *quoted_exclude_hosts, *quoted_hosts, *clean, *clean_exclude;
      int max;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      if (hosts == NULL)
        {
          sql_rollback ();
          return 12;
        }

      if (strlen (hosts) == 0)
        {
          sql_rollback ();
          return 14;
        }

      clean = clean_hosts (hosts, &max);
      clean_exclude = clean_hosts (exclude_hosts, NULL);

      max = manage_count_hosts (clean, clean_exclude);
      if (max <= 0)
        {
          g_free (clean);
          g_free (clean_exclude);
          sql_rollback ();
          return 2;
        }

      if (max > max_hosts)
        {
          g_free (clean);
          g_free (clean_exclude);
          sql_rollback ();
          return 3;
        }
      quoted_hosts = sql_quote (clean);
      quoted_exclude_hosts = sql_quote (clean_exclude);
      g_free (clean);
      g_free (clean_exclude);

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

/**
 * @brief Target iterator columns.
 */
#define TARGET_ITERATOR_COLUMNS                                \
 {                                                             \
   GET_ITERATOR_COLUMNS (targets),                             \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                     \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = CAST ('ssh' AS text))",                      \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   { "target_login_port (id, 0, CAST ('ssh' AS text))",        \
     "ssh_port",                                               \
     KEYWORD_TYPE_INTEGER },                                   \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = 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 },               \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = CAST ('esxi' AS text))",                     \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = CAST ('snmp' AS text))",                     \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = CAST ('elevate' AS text))",                  \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "(SELECT credential FROM targets_login_data"              \
     " WHERE target = targets.id"                              \
     " AND type = CAST ('krb5' AS text))",                     \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "allow_simultaneous_ips",                                 \
     NULL,                                                     \
     KEYWORD_TYPE_INTEGER },                                   \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('ssh' AS text)))",            \
     "ssh_credential",                                         \
     KEYWORD_TYPE_STRING                                       \
   },                                                          \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('smb' AS text)))",            \
     "smb_credential",                                         \
     KEYWORD_TYPE_STRING                                       \
   },                                                          \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('esxi' AS text)))",           \
     "esxi_credential",                                        \
     KEYWORD_TYPE_STRING                                       \
   },                                                          \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('snmp' AS text)))",           \
     "snmp_credential",                                        \
     KEYWORD_TYPE_STRING                                       \
   },                                                          \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('elevate' AS text)))",        \
     "ssh_elevate_credential",                                 \
     KEYWORD_TYPE_STRING                                       \
   },                                                          \
   {                                                           \
     "(SELECT name FROM credentials"                           \
     " WHERE credentials.id"                                   \
     "       = (SELECT credential FROM targets_login_data"     \
     "          WHERE target = targets.id"                     \
     "          AND type = CAST ('krb5' AS text)))",           \
     "krb5_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 },                                            \
   { "target_credential (id, 1, CAST ('elevate' AS text))",             \
     NULL,                                                              \
     KEYWORD_TYPE_INTEGER },                                            \
   { "trash_target_credential_location (id, CAST ('elevate' AS text))", \
     NULL,                                                              \
     KEYWORD_TYPE_INTEGER },                                            \
   { "target_credential (id, 1, CAST ('krb5' AS text))",                \
     NULL,                                                              \
     KEYWORD_TYPE_INTEGER },                                            \
   { "trash_target_credential_location (id, CAST ('krb5' AS text))",    \
     NULL,                                                              \
     KEYWORD_TYPE_INTEGER },                                            \
   { "allow_simultaneous_ips",                                          \
     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, 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";

  /* We could pass the return up to the caller, but we don't pass in
   * a filter id and the callers are all in situations where the
   * target cannot disappear, so it's safe to ignore the return. */
  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, 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_tests value from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Alive_tests of the target or -1 if iteration is complete.
 */
int
target_iterator_alive_tests (iterator_t* iterator)
{
  if (iterator->done)
    return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 13);
}

/**
 * @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 Get the ELEVATE LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ELEVATE LSC credential.
 */
int
target_iterator_ssh_elevate_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 18);
  return ret;
}

/**
 * @brief Get the ELEVATE LSC credential location from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ELEVATE LSC credential.
 */
int
target_iterator_ssh_elevate_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 19);
  return ret;
}

/**
 * @brief Get the Kerberos 5 LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Kerberos 5 LSC credential.
 */
int
target_iterator_krb5_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 20);
  return ret;
}

/**
 * @brief Get the Kerberos 5 LSC credential location from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Kerberos 5 LSC credential.
 */
int
target_iterator_krb5_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 21);
  return ret;
}

/**
 * @brief Get the allow_simultaneous_ips value from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return allow_simult_ips_same_host or NULL if iteration is complete.
 */
DEF_ACCESS (target_iterator_allow_simultaneous_ips,
            GET_ITERATOR_COLUMN_COUNT + 22);

/**
 * @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 the comment of a target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated name if available, else NULL.
 */
static char*
target_comment (target_t target)
{
  return sql_string ("SELECT comment FROM targets WHERE id = %llu;",
                     target);
}

/**
 * @brief Return the comment of a trashcan target.
 *
 * @param[in]  target  Target.
 *
 * @return Newly allocated name if available, else NULL.
 */
static char*
trash_target_comment (target_t target)
{
  return sql_string ("SELECT comment 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 allow_simultaneous_ips value of a target.
 *
 * @param[in]  target  Target.
 *
 * @return The allow_simultaneous_ips value if available, else NULL.
 */
char*
target_allow_simultaneous_ips (target_t target)
{
  return sql_string ("SELECT allow_simultaneous_ips FROM targets"
                     " 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 ELEVATE credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return ELEVATE credential if any, else 0.
 */
credential_t
target_ssh_elevate_credential (target_t target)
{
  return target_credential (target, "elevate");
}

/**
 * @brief Return the Kerberos 5 credential associated with a target, if any.
 *
 * @param[in]  target  Target.
 *
 * @return Kerberos 5 credential if any, else 0.
 */
credential_t
target_krb5_credential (target_t target)
{
  return target_credential (target, "krb5");
}

/**
 * @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 GMP port range list 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;",
                    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, *with_clause;
  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, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT name, uuid, %s FROM tasks"
                 " WHERE target = %llu"
                 " AND hidden = 0"
                 " ORDER BY name ASC;",
                 with_clause ? with_clause : "",
                 available,
                 target);

  g_free (with_clause);
  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);
}


/* 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 a GMP 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;

  char *encryption_key_uid = current_encryption_key_uid (TRUE);
  crypt_ctx = lsc_crypt_new (encryption_key_uid);
  free (encryption_key_uid);
  /* 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 Check that a string represents a valid Private Key.
 *
 * @param[in]  key_str      Private Key string.
 * @param[in]  key_phrase   Private Key passphrase.
 *
 * @return 0 if valid, 1 otherwise.
 */
int
check_private_key (const char *key_str, const char *key_phrase)
{
  gnutls_x509_privkey_t key;
  gnutls_datum_t data;
  int ret;

  assert (key_str);
  if (gnutls_x509_privkey_init (&key))
    return 1;
  data.size = strlen (key_str);
  data.data = (void *) g_strdup (key_str);
  ret = gnutls_x509_privkey_import2 (key, &data, GNUTLS_X509_FMT_PEM,
                                     key_phrase, 0);
  if (ret)
    {
      gchar *public_key;
      public_key = gvm_ssh_public_from_private (key_str, key_phrase);

      if (public_key == NULL)
        {
          gnutls_x509_privkey_deinit (key);
          g_free (data.data);
          g_message ("%s: import failed: %s",
                     __func__, gnutls_strerror (ret));
          return 1;
        }
      g_free (public_key);
    }
  g_free (data.data);
  gnutls_x509_privkey_deinit (key);
  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 successfully 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.
 */
static 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 Test if a username is valid to use in a credential.
 *
 * Valid usernames may only contain alphanumeric characters and a few
 *  special ones to avoid problems with installer package generation.
 *
 * @param[in]  username  The username string to test.
 *
 * @return Whether the username is valid.
 */
static int
validate_credential_username (const gchar *username)
{
  const char *s;
  s = username;
  while (*s)
    if (isalnum (*s)
        || strchr ("-_\\.@", *s))
      s++;
    else
      return 0;

  return 1;
}

/**
 * @brief Test if a username is valid for a credential export format.
 *
 * @param[in]  username  The username string to test.
 * @param[in]  format    The credential format to validate for.
 *
 * @return Whether the username is valid.
 */
static gboolean
validate_credential_username_for_format (const gchar *username,
                                         credential_format_t format)
{
  const char *s, *name_characters;

  name_characters = NULL;
  switch (format)
    {
      case CREDENTIAL_FORMAT_NONE:
      case CREDENTIAL_FORMAT_KEY:
      case CREDENTIAL_FORMAT_PEM:
        // No validation required
        break;
      case CREDENTIAL_FORMAT_RPM:
      case CREDENTIAL_FORMAT_DEB:
        name_characters = "-_";
        break;
      case CREDENTIAL_FORMAT_EXE:
        name_characters = "-_\\.@";
        break;
      case CREDENTIAL_FORMAT_ERROR:
        return 0;
    }

  if (name_characters)
    {
      s = username;
      while (*s)
        if (isalnum (*s)
            || strchr (name_characters, *s))
          s++;
        else
          return FALSE;
    }

  return TRUE;
}

/**
 * @brief Validate the format and resolvability of a Kerberos KDC input string.
 *
 * @param[in] kdc_input  A comma or newline-separated list of KDC hostnames or IPs.
 *
 * @return TRUE if all KDC entries are valid hosts, FALSE otherwise.
 */
static gboolean
validate_credential_kdc_format (const char *kdc_input)
{
  if (!kdc_input || !*kdc_input)
    return FALSE;

  gchar *input_copy = g_strdup (kdc_input);
  for (gchar *p = input_copy; *p; ++p)
    {
      if (*p == '\n')
        *p = ',';
    }

  gchar **parts = g_strsplit (input_copy, ",", 0);
  g_free (input_copy);

  for (gchar **ptr = parts; *ptr != NULL; ptr++)
    {
      const gchar *entry = *ptr;

      // reject empty strings
      if (!*entry)
        {
          g_strfreev (parts);
          return FALSE;
        }

      // validate whitespace in the KDC entry
      for (const gchar *c = entry; *c; ++c)
        {
          if (g_ascii_isspace (*c))
            {
              g_strfreev (parts);
              return FALSE;
            }
        }

      // validate host or IP
      if (gvm_get_host_type (entry) == -1)
        {
          g_strfreev (parts);
          return FALSE;
        }
    }

  g_strfreev (parts);
  return TRUE;
}

/**
 * @brief Validate the format and resolvability of a list of Kerberos KDCs.
 *
 * This function checks that each entry in the provided kdcs array:
 * - does not contain any whitespace,
 * - can be resolved via gvm_get_host_type(),
 * and if all entries are valid, it joins them into a single comma-separated string.
 *
 * @param[in]  kdcs        A pointer to an array of KDC strings (hostnames or IPs).
 * @param[out] joined_out  A location to store the resulting joined string
 *                         if validation succeeds.
 *                         The caller is responsible for freeing
 *                         the returned string using g_free().
 *
 * @return TRUE if all KDC entries are valid and @p joined_out is set; FALSE otherwise.
 */
static gboolean
validate_credential_kdcs_format (array_t *kdcs, gchar **joined_out)
{
  if (!kdcs || kdcs->len == 0 || !joined_out)
    return FALSE;

  GString *joined = g_string_new ("");

  for (size_t i = 0; i < kdcs->len; ++i)
    {
      const char *kdc_val = g_ptr_array_index (kdcs, i);

      // reject whitespace
      for (const char *c = kdc_val; *c; ++c)
        {
          if (g_ascii_isspace (*c))
            {
              g_string_free (joined, TRUE);
              return FALSE;
            }
        }

      // reject unresolvable host
      if (gvm_get_host_type (kdc_val) == -1)
        {
          g_string_free (joined, TRUE);
          return FALSE;
        }

      if (i > 0)
        g_string_append_c (joined, ',');

      g_string_append (joined, kdc_val);
    }

  *joined_out = g_string_free (joined, FALSE);
  return TRUE;
}

/**
 * @brief Validate the format of a Kerberos realm string.
 *
 * This function checks whether the given @p realm is non-empty and does not
 * contain any whitespace characters.
 *
 * @param[in] realm  A string representing the Kerberos realm.
 *
 * @return TRUE if the realm is valid; FALSE otherwise.
 */
gboolean
validate_credential_realm_format (const char *realm)
{
  if (!realm || !*realm)
    return FALSE;

  for (const char *c = realm; *c; ++c)
    {
      if (g_ascii_isspace (*c))
        return FALSE;
    }

  return TRUE;
}

/**
 * @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]  key_public      Public 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]  kdc             Kerberos KDC (key distribution centers).
 * @param[in]  kdcs            List of Kerberos KDCs.
 * @param[in]  realm           Kerberos realm.
 * @param[in]  credential_store_id Credential store ID, or NULL
 *                                 (if ENABLE_CREDENTIAL_STORES).
 * @param[in]  vault_id         Vault ID, or NULL.
 *                              (if ENABLE_CREDENTIAL_STORES).
 * @param[in]  host_identifier  Host identifier, or NULL.
 *                              (if ENABLE_CREDENTIAL_STORES).
 * @param[in]  privacy_host_identifier  SNMP Privacy host identifier, or NULL.
 *                              (if ENABLE_CREDENTIAL_STORES).
 * @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 invalid username,
 *         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,
 *         9 public key missing, 10 autogenerate not supported,
 *         11 community missing, 12 auth algorithm missing,
 *         14 privacy algorithm missing,
 *         15 invalid auth algorithm, 16 invalid privacy algorithm,
 *         17 invalid certificate, 18 cannot determine type,
 *         19 key distribution center missing, 20 realm missing,
 *         21 invalid key distribution center,
 *         22 invalid kerberos realm,
 *         23 credential store ID missing and no default store available,
 *            (if ENABLE_CREDENTIAL_STORES),
 *         24 credential store cannot be found (if ENABLE_CREDENTIAL_STORES),
 *         25 vault ID missing (if ENABLE_CREDENTIAL_STORES),
 *         26 host identifier missing (if ENABLE_CREDENTIAL_STORES),
 *         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* key_public,
                   const char* certificate, const char* community,
                   const char* auth_algorithm, const char* privacy_password,
                   const char* privacy_algorithm,
                   const char* kdc, array_t* kdcs, const char *realm,
                   const char* credential_store_id, const char* vault_id,
                   const char* host_identifier,
                   const char* privacy_host_identifier,
                   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 using_snmp_v3;
  int ret;
  gchar *kdc_value = NULL;

  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, "pgp")
          && strcmp (given_type, "pw")
          && strcmp (given_type, "snmp")
          && strcmp (given_type, "smime")
          && strcmp (given_type, "up")
          && strcmp (given_type, "usk")
          && strcmp (given_type, "krb5")
          && strcmp (given_type, "cs_pgp")
          && strcmp (given_type, "cs_pw")
          && strcmp (given_type, "cs_snmp")
          && strcmp (given_type, "cs_smime")
          && strcmp (given_type, "cs_up")
          && strcmp (given_type, "cs_usk")
          && strcmp (given_type, "cs_krb5")
      )
        {
          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 && (realm || kdc))
    quoted_type = g_strdup ("krb5");
  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", __func__);
      sql_rollback ();
      return 18;
    }

  /* Validate credential data */
  auto_generate = ((given_password == NULL) && (key_private == NULL)
                   && (key_public == NULL) && (certificate == NULL)
                   && (community == NULL)
                   && !g_str_has_prefix (quoted_type, "cs_"));
  ret = 0;

  if (auto_generate
      && (strcmp (quoted_type, "cc") == 0
          || strcmp (quoted_type, "pgp") == 0
          || strcmp (quoted_type, "smime") == 0
          || strcmp (quoted_type, "snmp") == 0
          || strcmp (quoted_type, "krb5") == 0))
    ret = 10; // Type does not support autogenerate

  using_snmp_v3 = 0;

  if (login == NULL
      && strcmp (quoted_type, "cc")
      && strcmp (quoted_type, "pgp")
      && strcmp (quoted_type, "pw")
      && strcmp (quoted_type, "smime")
      && strcmp (quoted_type, "snmp")
      && g_str_has_prefix (quoted_type, "cs_") == 0
    )
    ret = 5;
  else if (given_password == NULL && auto_generate == 0
           && (strcmp (quoted_type, "up") == 0
               || strcmp (quoted_type, "pw") == 0
               || strcmp (quoted_type, "krb5") == 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
               || strcmp (quoted_type, "smime") == 0))
    ret = 8;
  else if (key_public == NULL && auto_generate == 0
           && strcmp (quoted_type, "pgp") == 0)
    ret = 9;
  else if ((strcmp (quoted_type, "krb5") == 0)
           || (feature_enabled (FEATURE_ID_CREDENTIAL_STORES)
               && strcmp (quoted_type, "cs_krb5") == 0))
    {
      if (kdcs && kdcs->len > 0)
        {
          if (!validate_credential_kdcs_format (kdcs, &kdc_value))
            {
              ret = 21;
            }
        }
      else if (kdc)
        {
          if (!validate_credential_kdc_format (kdc))
            {
              ret = 21;
            }
          else
            {
              kdc_value = g_strdup (kdc);
            }
        }
      else
        {
          // both kdcs and kdc missing
          ret = 19;
        }
      if (ret == 0)
        {
          if (realm == NULL && auto_generate == 0)
            ret = 20;
          else if (!validate_credential_realm_format (realm))
            ret = 22;
        }
    }
  else if (strcmp (quoted_type, "snmp") == 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_algorithm == NULL
                || strcmp (privacy_algorithm, "") == 0)
               && privacy_password
               && strcmp (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, "")
               && strcmp (privacy_algorithm, "aes")
               && strcmp (privacy_algorithm, "des"))
        ret = 16;
    }
  else if (strcmp (quoted_type, "cs_snmp") == 0
           && feature_enabled (FEATURE_ID_CREDENTIAL_STORES))
    {
      if (auth_algorithm == NULL)
        ret = 12;
      else if ((privacy_algorithm == NULL
                || strcmp (privacy_algorithm, "") == 0)
               && privacy_host_identifier
               && strcmp (privacy_host_identifier, ""))
        ret = 14;
      else if (auth_algorithm
               && strcmp (auth_algorithm, "md5")
               && strcmp (auth_algorithm, "sha1"))
        ret = 15;
      else if (privacy_algorithm
               && strcmp (privacy_algorithm, "")
               && strcmp (privacy_algorithm, "aes")
               && strcmp (privacy_algorithm, "des"))
        ret = 16;
    }

  if (g_str_has_prefix (quoted_type, "cs_") && ret == 0)
    {
      credential_store_t store;
      if (!feature_enabled (FEATURE_ID_CREDENTIAL_STORES))
        ret = 4;
      else if (credential_store_id == NULL
               && get_default_credential_store_id () == NULL)
        ret = 23;
      else if (credential_store_id
               && (find_credential_store_no_acl (credential_store_id, &store)
                   || store == 0))
        ret = 24;
      else if (vault_id == NULL)
        ret = 25;
      else if (host_identifier == NULL)
        ret = 26;
    }

  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)
    {
      /*
       * Ensure the login does not contain characters that cause problems
       *  with package generation.
       */
      if (validate_credential_username (login) == 0)
        {
          sql_rollback ();
          return 2;
        }

      set_credential_data (new_credential,
                           "username", login);
    }

  if (kdc_value)
    {
      set_credential_data (new_credential, "kdc", kdc_value);
      g_free (kdc_value);
    }
  if (key_public)
    set_credential_data (new_credential, "public_key", key_public);
  if (certificate)
    {
      gchar *certificate_truncated;
      certificate_truncated = truncate_certificate (certificate);
      if (certificate_truncated)
        set_credential_data (new_credential,
                             "certificate", certificate_truncated);
      else
        {
          sql_rollback();
          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);
  if (realm)
    set_credential_data (new_credential, "realm", realm);

  if (g_str_has_prefix (quoted_type, "cs_"))
    {
      if (credential_store_id)
        set_credential_data (new_credential,
                             "credential_store_id", credential_store_id);
      else
        set_credential_data (new_credential,
                             "credential_store_id", get_default_credential_store_id ());
      if (vault_id)
        set_credential_data (new_credential,
                             "vault_id", vault_id);
      if (host_identifier)
        set_credential_data (new_credential,
                             "host_identifier", host_identifier);
      if (privacy_host_identifier)
        set_credential_data (new_credential,
                             "privacy_host_identifier", privacy_host_identifier);
      if (credential)
        *credential = new_credential;

      g_free (quoted_type);
      sql_commit ();
      return 0;
    }

  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, *generated_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
        {
          sql_rollback ();
          return 3;
        }

      generated_key_public = gvm_ssh_public_from_private
                                (key_private_truncated
                                    ? key_private_truncated
                                    : key_private,
                                 given_password);
      if (generated_key_public == NULL)
        {
          g_free (key_private_truncated);
          sql_rollback ();
          return 3;
        }
      g_free (generated_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;
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
          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 || using_snmp_v3)
    {
      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;
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
          secret = lsc_crypt_encrypt (crypt_ctx,
                                      "community", community,
                                      "password", given_password,
                                      "privacy_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, "privacy_password",
                               privacy_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)
        {
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
          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
   */

  /* 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;
        char *encryption_key_uid = current_encryption_key_uid (TRUE);
        crypt_ctx = lsc_crypt_new (encryption_key_uid);
        free (encryption_key_uid);
        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, &credential);
  if (ret)
    return ret;

  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]   key_public          Public 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]   kdc                 Kerberos KDC (key distribution centers).
 * @param[in]   kdcs                List of Kerberos KDCs.
 * @param[in]   realm               Kerberos realm.
 * @param[in]   credential_store_id Credential store ID, or NULL
 *                                   (if ENABLE_CREDENTIAL_STORES).
 * @param[in]   vault_id            Vault ID, or NULL.
 *                                  (if ENABLE_CREDENTIAL_STORES).
 * @param[in]   host_identifier     Host identifier, or NULL.
 *                                  (if ENABLE_CREDENTIAL_STORES).
 * @param[in]   privacy_host_identifier  SNMP Privacy host identifier, or NULL.
 *                                  (if ENABLE_CREDENTIAL_STORES).
 * @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,
 *         9 invalid public key,
 *         10 privacy password must be empty if algorithm is empty
 *         11 invalid or empty key distribution center,
 *         12 invalid or empty kerberos realm,
 *         13 credential store cannot be found,
 *            (if ENABLE_CREDENTIAL_STORES),
 *         14 vault ID is required (if ENABLE_CREDENTIAL_STORES),
 *         15 host identifier is required (if ENABLE_CREDENTIAL_STORES),
  *         17 value cannot be modified for credential store type,
 *         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* key_public,
                   const char* certificate,
                   const char* community, const char* auth_algorithm,
                   const char* privacy_password, const char* privacy_algorithm,
                   const char* kdc, array_t* kdcs,
                   const char* realm,
                   const char* credential_store_id, const char* vault_id,
                   const char* host_identifier,
                   const char* privacy_host_identifier,
                   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 ((credential_store_id || vault_id || host_identifier
      || privacy_host_identifier)
      && (login || password || privacy_password || key_private
          || key_public ||certificate || community))
    {
      sql_rollback ();
      return 17;
    }

  if (login && ret == 0)
    {
      /*
       * Ensure the login is not empty and does not contain characters that
       *  cause problems with package generation.
       */
      if (strcmp (login, "") == 0
          || validate_credential_username (login) == 0)
        ret = 4;

      if (ret == 0)
        set_credential_login (credential, login);
    }

  if (certificate && ret == 0)
    {
      if (g_str_equal (certificate, ""))
        {
          set_credential_certificate (credential, NULL);
        }
      else
        {
          // Truncate certificate which also validates it.
          gchar *certificate_truncated;
          certificate_truncated = truncate_certificate (certificate);
          if (certificate_truncated)
            {
              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 (privacy_algorithm, "aes")
          && strcmp (privacy_algorithm, "des")
          && strcmp (privacy_algorithm, ""))
        ret = 7;
      else
        {
          iterator_t credential_iterator;
          const char *used_password;

          init_credential_iterator_one (&credential_iterator, credential);

          if (privacy_password)
            used_password = privacy_password;
          else
            {
              next (&credential_iterator);
              used_password
                = credential_iterator_privacy_password (&credential_iterator);
              if (used_password == NULL)
                used_password = "";
            }

          // Privacy password must be empty if algorithm is empty
          if (strcmp (privacy_algorithm, "") == 0
              && strcmp (used_password, ""))
            ret = 10;
          else
            set_credential_privacy_algorithm (credential, privacy_algorithm);

          cleanup_iterator (&credential_iterator);
        }
    }

  if (key_public && ret == 0)
    {
      set_credential_public_key (credential, key_public);
    }

  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 (strcmp (type, "cc") && strcmp (type, "up")
          && strcmp (type, "pw") && strcmp (type, "usk")
          && strcmp (type, "snmp") && strcmp (type, "pgp")
          && strcmp (type, "smime") && strcmp (type, "krb5")
          && !g_str_has_prefix (type, "cs_"))
        {
          g_warning ("%s: Unknown credential type: %s", __func__, type);
          sql_rollback ();
          cleanup_iterator (&iterator);
          return -1;
        }

      if (g_str_has_prefix (type, "cs_")
          && !feature_enabled (FEATURE_ID_CREDENTIAL_STORES))
        {
          sql_rollback ();
          cleanup_iterator (&iterator);
          g_debug ("%s: Credential stores feature not enabled",
                    __func__);
          return -1;
        }

      if (key_private)
        {
          if (!g_str_equal (key_private, ""))
            {
              char *generated_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;

              generated_key_public = gvm_ssh_public_from_private (
                key_private_to_use,
                password ? password : credential_iterator_password (&iterator));

              if (generated_key_public == NULL)
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  g_free (generated_key_public);
                  return 8;
                }
              g_free (generated_key_public);
            }
          else
            key_private_to_use = key_private;
        }
      else
        key_private_to_use = NULL;

      if (strcmp (type, "up") == 0
               || strcmp (type, "pw") == 0)
        {
          if (password)
            set_credential_password (credential, password);
        }
      else if (g_str_equal (type, "usk") || g_str_equal (type, "cc"))
        {
          if (key_private_to_use || password)
            {
              if (g_str_equal (type, "usk"))
                {
                  if (check_private_key (
                        key_private_to_use
                          ? key_private_to_use
                          : credential_iterator_private_key (&iterator),
                        password ? password
                                 : credential_iterator_password (&iterator)))
                    {
                      sql_rollback ();
                      cleanup_iterator (&iterator);
                      return 8;
                    }
                }

              set_credential_private_key (
                credential,
                key_private_to_use
                  ? key_private_to_use
                  : credential_iterator_private_key (&iterator),
                password ? password : credential_iterator_password (&iterator));
            }
        }
      else if ((strcmp (type, "snmp") == 0)
               || (strcmp (type, "cs_snmp") == 0))
        {
          if (privacy_password || privacy_host_identifier)
            {
              const char *used_algorithm;

              // Privacy_algorithm should be set above if given
              used_algorithm
                = credential_iterator_privacy_algorithm (&iterator);
              if (used_algorithm == NULL)
                used_algorithm = "";

              /* privacy password and host identifier
               * must be empty if algorithm is empty */
              if (strcmp (used_algorithm, "") == 0
                  && (strcmp (privacy_password, "")
                      || strcmp (privacy_host_identifier, "")))
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  return 10;
                }
            }

          if (strcmp (type, "snmp") == 0
              && (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 if (strcmp (type, "pgp") == 0
               || strcmp (type, "smime") == 0)
        {
          set_credential_data (credential, "secret", "");
        }
      else if ((strcmp (type, "krb5") == 0)
               || (strcmp (type, "cs_krb5") == 0))
        {
          if (password && strcmp (type, "krb5") == 0)
            set_credential_password (credential, password);
          if (kdcs)
            {
              gchar *joined_kdcs = NULL;

              if (!validate_credential_kdcs_format (kdcs, &joined_kdcs))
                {
                  sql_rollback ();
                  return 11;
                }

              set_credential_data (credential, "kdc", joined_kdcs);
              g_free (joined_kdcs);
            }
          else if (kdc)
            {
              if (!validate_credential_kdc_format (kdc))
                {
                  sql_rollback ();
                  return 11;
                }

              set_credential_data (credential, "kdc", kdc);
            }

          if (realm)
            {
              if (!validate_credential_realm_format (realm))
                {
                  sql_rollback ();
                  return 12;
                }

              set_credential_data (credential, "realm", realm);
            }
        }

      if (g_str_has_prefix (type, "cs_"))
        {
          if (credential_store_id)
            {
              credential_store_t store;
              if (find_credential_store_no_acl (credential_store_id, &store)
                  || store == 0)
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  return 13;
                }

              set_credential_data (credential,
                                   "credential_store_id",
                                   credential_store_id);
            }
          if (vault_id)
            {
              if (!*vault_id)
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  return 14;
                }
              set_credential_data (credential,
                                   "vault_id",
                                   vault_id);
            }
          if (host_identifier)
            {
              if (!*host_identifier)
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  return 15;
                }
              set_credential_data (credential,
                                   "host_identifier",
                                   host_identifier);
            }
          if (privacy_host_identifier)
            {
              if (g_str_equal (privacy_host_identifier, ""))
                set_credential_data (credential,
                                    "privacy_host_identifier",
                                    NULL);
              else
                set_credential_data (credential,
                                    "privacy_host_identifier",
                                    privacy_host_identifier);
            }
        }
      g_free (key_private_truncated);
    }
  else
    {
      g_warning ("%s: credential iterator next() failed", __func__);
      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_remove_resource ("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);
#if ENABLE_CONTAINER_SCANNING
      sql ("UPDATE oci_image_targets_trash"
           " SET credential_location = " G_STRINGIFY (LOCATION_TRASH) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           "   AND credential_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_credential,
           credential);
#endif /* ENABLE_CONTAINER_SCANNING */

      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_remove_resource ("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.
 */
#if ENABLE_CREDENTIAL_STORES
#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 },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'public_key')",           \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'kdc')"       ,           \
     "kdc",                                                                   \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'realm')",                \
     "realm",                                                                 \
     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 },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'credential_store_id')",  \
     "credential_store_id",                                                   \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'vault_id')",             \
     "vault_id",                                                              \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'host_identifier')",      \
     "host_identifier",                                                       \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id"                                     \
     " AND type = 'privacy_host_identifier')",                                \
     "privacy_host_identifier",                                               \
     KEYWORD_TYPE_STRING },                                                   \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }
#else
#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 },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'public_key')",           \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'kdc')"       ,           \
     "kdc",                                                                   \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'realm')",                \
     "realm",                                                                 \
     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 }                                       \
 }
#endif

/**
 * @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 },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials_trash.id AND type = 'public_key')",     \
     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)
#if ENABLE_CONTAINER_SCANNING
           || sql_int ("SELECT count (*) FROM oci_image_targets"
                       " WHERE credential = %llu;",
                       credential)
#endif /* ENABLE_CONTAINER_SCANNING */
           || sql_int ("SELECT count (*) FROM scanners"
                       " WHERE credential = %llu;",
                       credential)
           || sql_int ("SELECT count (*) FROM alert_method_data"
                       " WHERE (name = 'recipient_credential'"
                       "        OR name = 'scp_credential'"
                       "        OR name = 'smb_credential'"
                       "        OR name = 'tp_sms_credential'"
                       "        OR name = 'verinice_server_credential'"
                       "        OR name = 'pkcs12_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)
#if ENABLE_CONTAINER_SCANNING
           || sql_int ("SELECT count (*) FROM oci_image_targets_trash"
                       " WHERE credential = %llu"
                       " AND credential_location"
                       "      = " G_STRINGIFY (LOCATION_TRASH) ";",
                       credential)
#endif
           || sql_int ("SELECT count (*) FROM alert_method_data_trash"
                       " WHERE (name = 'recipient_credential'"
                       "        OR name = 'scp_credential'"
                       "        OR name = 'smb_credential'"
                       "        OR name = 'tp_sms_credential'"
                       "        OR name = 'verinice_server_credential'"
                       "        OR name = 'pkcs12_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.
 *
 * @return 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.
 *
 * @return 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;
      char *encryption_key_uid = current_encryption_key_uid (TRUE);
      crypt_ctx = lsc_crypt_new (encryption_key_uid);
      free (encryption_key_uid);

      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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static void
set_credential_password (credential_t credential, const char *password)
{
  lsc_crypt_ctx_t crypt_ctx;

  if (!disable_encrypted_credentials)
    {
      gchar *encrypted_blob;
      char *encryption_key_uid = current_encryption_key_uid (TRUE);
      crypt_ctx = lsc_crypt_new (encryption_key_uid);
      free (encryption_key_uid);
      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.
 */
static 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)
    {
      if (!g_str_equal (private_key, ""))
        {
          gchar *encrypted_blob;
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
          encrypted_blob = lsc_crypt_encrypt (crypt_ctx, "password", passphrase,
                                              "private_key", private_key, NULL);
          if (!encrypted_blob)
            {
              g_critical ("%s: encryption failed", G_STRFUNC);
              lsc_crypt_release (crypt_ctx);
              return;
            }
          set_credential_data (credential, "secret", encrypted_blob);

          g_free (encrypted_blob);
          lsc_crypt_release (crypt_ctx);
        }
      else
        {
          set_credential_data (credential, "secret", NULL);
        }

      set_credential_data (credential, "password", NULL);
      set_credential_data (credential, "private_key", NULL);
    }
  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);
}

/**
 * @brief Set the public key of a Credential.
 *
 * @param[in]  credential      The Credential.
 * @param[in]  public_key      Public key.
 */
void
set_credential_public_key (credential_t credential, const char *public_key)
{
  set_credential_data (credential, "public_key", public_key);
  sql ("UPDATE credentials SET"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
}

/**
 * @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.
 */
static 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;
      char *encryption_key_uid = current_encryption_key_uid (TRUE);
      crypt_ctx = lsc_crypt_new (encryption_key_uid);
      free (encryption_key_uid);
      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";

  /* We could pass the return up to the caller, but we don't pass in
   * a filter id and the callers are all in situations where the
   * credential cannot disappear, so it's safe to ignore the return. */
  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, 2 failed to find
 *         filter (filt_id), -1 error.
 */
int
init_credential_iterator (iterator_t* iterator, 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);
}

/**
 * @brief Get possibly encrypted data from credentials.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  type      Type of data.
 *
 * @return Data.
 */
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 + 9);
  if (type == NULL)
    {
      g_warning ("%s: NULL data type given", __func__);
      return NULL;
    }
  else if (strcmp (type, "password") == 0)
    unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10);
  else if (strcmp (type, "private_key") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11);
  else if (strcmp (type, "community") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 12);
  else if (strcmp (type, "privacy_password") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 13);
  else
    {
      g_warning ("%s: unknown data type \"%s\"", __func__, 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)
        {
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          iterator->crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
        }

      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 public key from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Public key, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_public_key,
            GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the key distribution center from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Key distribution center, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_kdc,
            GET_ITERATOR_COLUMN_COUNT + 7);

/**
 * @brief Get the realm from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Realm, or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_realm,
            GET_ITERATOR_COLUMN_COUNT + 8);

#if ENABLE_CREDENTIAL_STORES

/**
 * @brief Get the credential store UUID from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Credential store UUID, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_credential_store_uuid,
            GET_ITERATOR_COLUMN_COUNT + 14);

/**
 * @brief Get the credential store vault ID from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Vault ID, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_vault_id,
            GET_ITERATOR_COLUMN_COUNT + 15);

/**
 * @brief Get the host identifier from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Host identifier, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (credential_iterator_host_identifier,
            GET_ITERATOR_COLUMN_COUNT + 16);

/**
 * @brief Get the SNMP privacy host identifier from an LSC credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return SNMP privacy host identifier, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */

DEF_ACCESS (credential_iterator_privacy_host_identifier,
            GET_ITERATOR_COLUMN_COUNT + 17);

#endif

/**
 * @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 = gvm_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = credential_iterator_login (iterator);
  if (credential_iterator_format_available
          (iterator, CREDENTIAL_FORMAT_RPM) == FALSE)
    {
      g_free (public_key);
      return NULL;
    }
  else if (lsc_user_rpm_recreate (login, public_key, &rpm, &rpm_size))
    {
      g_warning ("%s: Failed to create RPM", __func__);
      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, *maintainer;
  void *deb;
  gsize deb_size;
  gchar *deb64;

  if (iterator->done) return NULL;

  maintainer = NULL;
  setting_value (SETTING_UUID_LSC_DEB_MAINTAINER, &maintainer);
  private_key = credential_iterator_private_key (iterator);
  pass = credential_iterator_password (iterator);
  public_key = gvm_ssh_public_from_private (private_key, pass);
  if (!public_key)
    return NULL;
  login = credential_iterator_login (iterator);
  if (credential_iterator_format_available
          (iterator, CREDENTIAL_FORMAT_DEB) == FALSE)
    {
      g_free (public_key);
      free (maintainer);
      return NULL;
    }
  else if (lsc_user_deb_recreate (login, public_key,
                                  maintainer ? maintainer : "",
                                  &deb, &deb_size))
    {
      g_warning ("%s: Failed to create DEB", __func__);
      g_free (public_key);
      free (maintainer);
      return NULL;
    }
  g_free (public_key);
  free (maintainer);

  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 (credential_iterator_format_available
          (iterator, CREDENTIAL_FORMAT_EXE) == FALSE)
    return NULL;
  else if (lsc_user_exe_recreate (login, password, &exe, &exe_size))
    {
      g_warning ("%s: Failed to create EXE", __func__);
      return NULL;
    }
  exe64 = (exe && exe_size)
          ? g_base64_encode (exe, exe_size)
          : g_strdup ("");
  free (exe);
  return exe64;
}

/**
 * @brief  Test if a credential format is available for an iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  format    The format to test availability of.
 *
 * @return  Whether format is available for the current credential of iterator.
 */
gboolean
credential_iterator_format_available (iterator_t* iterator,
                                      credential_format_t format)
{
  const char *type, *login, *private_key;

  type = credential_iterator_type (iterator);
  login = credential_iterator_login (iterator);
  private_key = credential_iterator_private_key (iterator);

  switch (format)
    {
      case CREDENTIAL_FORMAT_NONE:
        return TRUE;
      case CREDENTIAL_FORMAT_KEY:
      case CREDENTIAL_FORMAT_RPM:
      case CREDENTIAL_FORMAT_DEB:
        if (strcasecmp (type, "usk") == 0 && private_key)
          return validate_credential_username_for_format (login, format);
        else
          return FALSE;
      case CREDENTIAL_FORMAT_EXE:
        if (strcasecmp (type, "up") == 0)
          return validate_credential_username_for_format (login, format);
        else
          return FALSE;
      case CREDENTIAL_FORMAT_PEM:
        if (strcasecmp (type, "cc") == 0)
          return validate_credential_username_for_format (login, format);
        else
          return FALSE;
      case CREDENTIAL_FORMAT_ERROR:
        return FALSE;
    }
  return FALSE;
}

/**
 * @brief  Get XML of available formats for a credential iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return  Newly allocated XML string.
 */
gchar *
credential_iterator_formats_xml (iterator_t* iterator)
{
  GString *xml;

  xml = g_string_new ("<formats>");

  if (credential_iterator_format_available (iterator,
                                            CREDENTIAL_FORMAT_KEY))
    g_string_append (xml, "<format>key</format>");

  if (credential_iterator_format_available (iterator,
                                            CREDENTIAL_FORMAT_RPM))
    g_string_append (xml, "<format>rpm</format>");

  if (credential_iterator_format_available (iterator,
                                            CREDENTIAL_FORMAT_DEB))
    g_string_append (xml, "<format>deb</format>");

  if (credential_iterator_format_available (iterator,
                                            CREDENTIAL_FORMAT_EXE))
    g_string_append (xml, "<format>exe</format>");

  if (credential_iterator_format_available (iterator,
                                            CREDENTIAL_FORMAT_PEM))
    g_string_append (xml, "<format>pem</format>");

  g_string_append (xml, "</formats>");

  return g_string_free (xml, FALSE);
}

/**
 * @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, *with_clause;
  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, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT uuid, name, %s FROM targets"
                 " WHERE id IN"
                 "   (SELECT target FROM targets_login_data"
                 "    WHERE credential = %llu)"
                 " ORDER BY name %s;",
                 with_clause ? with_clause : "",
                 available,
                 credential,
                 ascending ? "ASC" : "DESC");

  g_free (with_clause);
  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);
}

#if ENABLE_CONTAINER_SCANNING
/**
 * @brief Initialise a Credential OCI image target iterator.
 *
 * Iterates over all OCI image targets that use the credential.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  credential      Credential.
 * @param[in]  ascending       Whether to sort ascending or descending.
 */
void
init_credential_oci_image_target_iterator (iterator_t* iterator,
                                           credential_t credential,
                                           int ascending)
{
  gchar *available, *with_clause;
  get_data_t get;
  array_t *permissions;

  assert (credential);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_oci_image_targets"));
  available = acl_where_owned ("oci_image_target", &get, 1, "any",
                               0, permissions, 0, &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT uuid, name, %s FROM oci_image_targets"
                 " WHERE credential = %llu"
                 " ORDER BY name %s;",
                 with_clause ? with_clause : "",
                 available,
                 credential,
                 ascending ? "ASC" : "DESC");

  g_free (with_clause);
  g_free (available);
}

/**
 * @brief Get the uuid from an Credential OCI Image Target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Uuid, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_oci_target_iterator_uuid, 0);

/**
 * @brief Get the name from an Credential OCI Image Target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Name, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (credential_oci_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_oci_target_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 2);
}
#endif /* ENABLE_CONTAINER_SCANNING */

/**
 * @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, *with_clause;
  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, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT uuid, name, %s FROM scanners"
                 " WHERE credential = %llu"
                 " ORDER BY name %s;",
                 with_clause ? with_clause : "",
                 available,
                 credential,
                 ascending ? "ASC" : "DESC");

  g_free (with_clause);
  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);
}


/* 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 successfully 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 Check if an NVT exists.
 *
 * @param[in]  nvt  NVT OID.
 *
 * @return 1 if exists, else 0.
 */
static gboolean
nvt_exists (const char* nvt)
{
  gchar *quoted_nvt;

  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);
  return 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;
  double severity_dbl;

  if (acl_user_may ("create_note") == 0)
    return 99;

  if (nvt == NULL)
    return -1;

  if (!nvt_exists (nvt))
    return 1;

  if (port && validate_results_port (port))
    return 2;

  if (text == NULL)
    return -1;

  if (threat
      && strcmp (threat, "Critical")
      && strcmp (threat, "High")
      && strcmp (threat, "Medium")
      && strcmp (threat, "Low")
      && strcmp (threat, "Log")
      && 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))
        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, "Critical") == 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
        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, NULL);
}

/**
 * @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_remove_resource ("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_remove_resource ("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_id     Note.
 * @param[in]  active      NULL or -2 leave as is, -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_id     Task to apply note to, NULL for any task.
 * @param[in]  result_id   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, 4 failed to find NVT, 5 failed to find note,
 *         6 failed to find task, 7 failed to find result.
 */
int
modify_note (const gchar *note_id, const char *active, const char *nvt,
             const char *text, const char *hosts, const char *port,
             const char *severity, const char *threat, const gchar *task_id,
             const gchar *result_id)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  double severity_dbl;
  gchar *quoted_nvt;
  note_t note;
  task_t task;
  result_t result;

  note = 0;
  if (find_note_with_permission (note_id, &note, "modify_note"))
    return -1;
  else if (note == 0)
    return 5;

  task = 0;
  if (task_id)
    {
      if (find_task_with_permission (task_id, &task, NULL))
        return -1;
      if (task == 0)
        {
          if (find_trash_task_with_permission (task_id, &task, NULL))
            return -1;
          if (task == 0)
            return 6;
        }
    }

  result = 0;
  if (result_id)
    {
      if (find_result_with_permission (result_id, &result, NULL))
        return -1;
      if (result == 0)
        return 7;
    }

  if (text == NULL)
    return -1;

  if (nvt && !nvt_exists (nvt))
    return 4;

  if (threat
      && strcmp (threat, "Critical")
      && strcmp (threat, "High")
      && strcmp (threat, "Medium")
      && strcmp (threat, "Low")
      && strcmp (threat, "Log")
      && 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);
  quoted_nvt = sql_insert (nvt);

  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))
        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, "Critical") == 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
        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,"
         " %s%s%s"
         " task = %llu,"
         " result = %llu"
         " WHERE id = %llu;",
         time (NULL),
         quoted_text,
         quoted_hosts,
         quoted_port,
         quoted_severity,
         nvt ? "nvt = " : "",
         nvt ? quoted_nvt : "",
         nvt ? "," : "",
         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,"
           "%s%s%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,
           nvt ? "nvt = " : "",
           nvt ? quoted_nvt : "",
           nvt ? "," : "",
           task,
           result,
           note);
    }

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);
  g_free (quoted_nvt);

  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 },                     \
   { "notes.creation_time", NULL, KEYWORD_TYPE_INTEGER },                  \
   { "notes.modification_time", NULL, KEYWORD_TYPE_INTEGER },              \
   { "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 },                          \
   { "notes.task", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "notes.result", "result", KEYWORD_TYPE_INTEGER },                     \
   { "notes.end_time", "end_time", KEYWORD_TYPE_INTEGER },                 \
   { "CAST (((notes.end_time = 0) OR (notes.end_time >= m_now ()))"        \
     "      AS INTEGER)",                                                  \
     "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 },                           \
   { "notes_trash.creation_time", NULL, KEYWORD_TYPE_INTEGER },                  \
   { "notes_trash.modification_time", NULL, KEYWORD_TYPE_INTEGER },              \
   { "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 },                       \
   { "CAST (((notes_trash.end_time = 0) OR (notes_trash.end_time >= m_now ()))"  \
     "      AS INTEGER)",                                                        \
     "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, FILT_ID_NONE))
    {
      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_with_permission (task_id, &task, "get_tasks");
      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, FILT_ID_NONE))
    {
      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_with_permission (task_id, &task, "get_tasks");
      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 Initialise a note iterator not limited to result, task or NVT.
 *
 * @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_note_iterator_all (iterator_t* iterator, get_data_t *get)
{
  return init_note_iterator (iterator, get, 0, 0, 0);
}

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

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

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

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

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


/* 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 successfully 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 overridden 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;
  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;

  if (!nvt_exists (nvt))
    return 1;

  if (port && validate_results_port (port))
    return 2;

  if (threat
      && strcmp (threat, "Critical")
      && strcmp (threat, "High")
      && strcmp (threat, "Medium")
      && strcmp (threat, "Low")
      && strcmp (threat, "Log")
      && strcmp (threat, "Alarm")
      && strcmp (threat, ""))
    return -1;

  if (new_threat
      && strcmp (new_threat, "Critical")
      && strcmp (new_threat, "High")
      && strcmp (new_threat, "Medium")
      && strcmp (new_threat, "Low")
      && strcmp (new_threat, "Log")
      && 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))
        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, "Critical") == 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
        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))
        {
          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, "Critical") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "High") == 0)
        new_severity_dbl = 8.9;
      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
        return -1;
    }
  else
    {
      g_free (quoted_severity);
      return -1;
    }

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);

  result_nvt_notice (nvt);
  sql ("INSERT INTO overrides"
       " (uuid, owner, nvt, creation_time, modification_time, text, hosts,"
       "  port, severity, new_severity, task, result, end_time,"
       "  result_nvt)"
       " VALUES"
       " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', %i, %i, %s, %s, %s, %s, %1.1f, %llu, %llu, %i,"
       "  (SELECT id FROM result_nvts WHERE nvt = '%s'));",
       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),
       nvt);

  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, result_nvt",
                        1, new_override, NULL);
}

/**
 * @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_remove_resource ("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, result_nvt)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, new_severity,task,"
           "        result, end_time, result_nvt"
           " 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_remove_resource ("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_id  Override.
 * @param[in]  active       NULL or -2 leave as is, -1 on, 0 off, n on for n
 *                          days.
 * @param[in]  nvt         OID of noted 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 threat.
 * @param[in]  new_severity Severity score to override "Alarm" type results to.
 * @param[in]  task_id     Task to apply override to, 0 for any task.
 * @param[in]  result_id   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, 4 failed to find NVT, 5 failed to find
 *         override, 6 failed to find task, 7 failed to find result,
 *         8 invalid threat, 9 invalid new_threat, 10 invalid new_severity,
 *         11 missing new_severity.
 */
int
modify_override (const gchar *override_id, 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,
                 const gchar *task_id, const gchar *result_id)
{
  gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity;
  double severity_dbl, new_severity_dbl;
  gchar *quoted_nvt;
  GHashTable *reports;
  GString *cache_invalidated_sql;
  int cache_invalidated;
  override_t override;
  task_t task;
  result_t result;

  reports = NULL;
  cache_invalidated = 0;

  override = 0;
  if (find_override_with_permission (override_id, &override, "modify_override"))
    return -1;
  if (override == 0)
    return 5;

  task = 0;
  if (task_id)
    {
      if (find_task_with_permission (task_id, &task, NULL))
        return -1;
      if (task == 0)
        {
          if (find_trash_task_with_permission (task_id, &task, NULL))
            return -1;
          if (task == 0)
            return 6;
        }
    }

  result = 0;
  if (result_id)
    {
      if (find_result_with_permission (result_id, &result, NULL))
        return -1;
      if (result == 0)
        return 7;
    }

  if (text == NULL)
    return -1;

  if (port && validate_results_port (port))
    return 2;

  if (nvt && !nvt_exists (nvt))
    return 4;

  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))
        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, "Critical") == 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
        return 8;

      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))
        {
          g_free (quoted_severity);
          return 10;
        }
    }
  else if (new_threat != NULL && strcmp (new_threat, ""))
    {
      if (strcmp (new_threat, "Alarm") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "Critical") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "High") == 0)
        new_severity_dbl = 8.9;
      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
        {
          g_free (quoted_severity);
          return 9;
        }
    }
  else
    {
      g_free (quoted_severity);
      return 11;
    }

  quoted_text = sql_insert (text);
  quoted_hosts = sql_insert (hosts);
  quoted_port = sql_insert (port);
  quoted_nvt = nvt ? sql_quote (nvt) : NULL;

  // 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.
  result_nvt_notice (quoted_nvt);
  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,"
           " %s%s%s"
           " %s%s%s"
           " new_severity = %f,"
           " task = %llu,"
           " result = %llu"
           " WHERE id = %llu;",
           time (NULL),
           quoted_text,
           quoted_hosts,
           quoted_port,
           quoted_severity,
           nvt ? "nvt = '" : "",
           nvt ? quoted_nvt : "",
           nvt ? "'," : "",
           nvt ? "result_nvt = (SELECT id FROM result_nvts WHERE nvt='" : "",
           nvt ? quoted_nvt : "",
           nvt ? "')," : "",
           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,"
           " %s%s%s"
           " %s%s%s"
           " new_severity = %f,"
           " task = %llu,"
           " result = %llu"
           " WHERE id = %llu;",
           new_end_time,
           time (NULL),
           quoted_text,
           quoted_hosts,
           quoted_port,
           quoted_severity,
           nvt ? "nvt = '" : "",
           nvt ? quoted_nvt : "",
           nvt ? "'," : "",
           nvt ? "result_nvt = (SELECT id FROM result_nvts WHERE nvt='" : "",
           nvt ? quoted_nvt : "",
           nvt ? "')," : "",
           new_severity_dbl,
           task,
           result,
           override);
    }

  g_free (quoted_text);
  g_free (quoted_hosts);
  g_free (quoted_port);
  g_free (quoted_severity);
  g_free (quoted_nvt);

  if (cache_invalidated)
    {
      GHashTableIter reports_iter;
      report_t *reports_ptr;
      gchar *users_where;
      int auto_cache_rebuild;

      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 (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 },                      \
   { "overrides.creation_time", NULL, KEYWORD_TYPE_INTEGER },               \
   { "overrides.modification_time", NULL, KEYWORD_TYPE_INTEGER },           \
   { "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 },                    \
   {                                                                        \
     "CAST (((overrides.end_time = 0) OR (overrides.end_time >= m_now ()))" \
     "      AS INTEGER)",                                                   \
     "active",                                                              \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   {                                                                        \
     "(CASE"                                                                \
     " WHEN overrides.nvt LIKE 'CVE-%%'"                                    \
     " THEN overrides.nvt"                                                  \
     " ELSE (SELECT name FROM nvts WHERE oid = overrides.nvt)"              \
     " END)",                                                               \
     "nvt",                                                                 \
     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 },                      \
   { "overrides_trash.creation_time",                                       \
     NULL,                                                                  \
     KEYWORD_TYPE_INTEGER },                                                \
   { "overrides_trash.modification_time",                                   \
     NULL,                                                                  \
     KEYWORD_TYPE_INTEGER },                                                \
   { "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 },              \
   {                                                                        \
     "CAST (((overrides_trash.end_time = 0)"                                \
     "       OR (overrides_trash.end_time >= m_now ())) AS INTEGER)",       \
     "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, FILT_ID_NONE))
    {
      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_with_permission (task_id, &task, "get_tasks");
      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, FILT_ID_NONE))
    {
      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_with_permission (task_id, &task, "get_tasks");
      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 Initialise an override iterator not limited to result, task or NVT.
 *
 * @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_override_iterator_all (iterator_t* iterator, get_data_t *get)
{
  return init_override_iterator (iterator, get, 0, 0, 0);
}

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

  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]  credential_id     UUID of credential to use or NULL to create.
 * @param[in]  key_pub_path      Certificate path.
 * @param[in]  key_priv_path     Private key path.
 * @param[in]  relay_host        Relay host of scanner.
 * @param[in]  relay_port        Relay port of scanner.
 * @param[in]  no_default_certs  If non-zero, bypasses certificate validation
 *                               and disables automatic credential creation.
 *
 * @return 0 success, -1 failure.
 */
int
manage_create_scanner (GSList *log_config, const db_conn_info_t *database,
                       const char *name, const char *host, const char *port,
                       const char *type, const char *ca_pub_path,
                       const char *credential_id,
                       const char *key_pub_path, const char *key_priv_path,
                       const char *relay_host, const char *relay_port,
                       int no_default_certs)
{
  int ret;
  char *ca_pub = NULL, *key_pub = NULL, *key_priv = NULL;
  GError *error = NULL;
  credential_t new_credential;
  gchar *used_credential_id = NULL;
  gchar *name_for_credential;
  scanner_t scanner;

  g_info ("   Creating scanner.");

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return -1;

  current_credentials.uuid = "";

  if (!no_default_certs)
    {
      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 (credential_id)
    {
      used_credential_id = g_strdup (credential_id);
    }
  else
    {
      if (!no_default_certs)
        {
          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;

              char *encryption_key_uid = current_encryption_key_uid (TRUE);
              crypt_ctx = lsc_crypt_new (encryption_key_uid);
              free (encryption_key_uid);

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

          used_credential_id = credential_uuid (new_credential);
        }
      else
        {
          /* Bypass mode: do NOT auto-create a credential */
          used_credential_id = NULL;
        }
    }
  ret = create_scanner (name, NULL, host, port, type, &scanner, ca_pub,
                        used_credential_id, relay_host, relay_port);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  g_free (used_credential_id);

  switch (ret)
    {
      case CREATE_SCANNER_SUCCESS:
        {
          gchar *uuid;

          uuid = sql_string ("SELECT uuid FROM scanners WHERE id = %llu;",
                             scanner);
          add_role_permission_resource (ROLE_UUID_ADMIN, "GET_SCANNERS",
                                        "scanner", uuid);
          add_role_permission_resource (ROLE_UUID_GUEST, "GET_SCANNERS",
                                        "scanner", uuid);
          add_role_permission_resource (ROLE_UUID_OBSERVER, "GET_SCANNERS",
                                        "scanner", uuid);
          add_role_permission_resource (ROLE_UUID_USER, "GET_SCANNERS",
                                        "scanner", uuid);
          g_free (uuid);
        }

        printf ("Scanner created.\n");
        break;
      case CREATE_SCANNER_ALREADY_EXISTS:
        fprintf (stderr, "Scanner exists already.\n");
        break;
      case CREATE_SCANNER_MISSING_TYPE:
        fprintf (stderr, "Scanner type is required.\n");
        break;
      case CREATE_SCANNER_MISSING_HOST:
        fprintf (stderr, "Scanner host is required.\n");
        break;
      case CREATE_SCANNER_CREDENTIAL_NOT_FOUND:
        fprintf (stderr, "Credential not found.\n");
        break;
      case CREATE_SCANNER_CREDENTIAL_NOT_CC:
        fprintf (stderr, "Credential should be 'cc'.\n");
        break;
      case CREATE_SCANNER_INVALID_TYPE:
        fprintf (stderr, "Invalid scanner type.\n");
        break;
      case CREATE_SCANNER_INVALID_PORT:
        fprintf (stderr,
                 "Scanner port must be a valid port number (1 - 65535)"
                 " if host is not a UNIX socket path.\n");
        break;
      case CREATE_SCANNER_INVALID_HOST:
        fprintf (stderr,
                 "Scanner host must be a valid hostname,"
                 " IP address or UNIX socket path.\n");
        break;
      case CREATE_SCANNER_INVALID_RELAY_PORT:
        fprintf (stderr,
                 "Scanner relay port must be a valid port number (1 - 65535)"
                 " if relay host is not a UNIX socket path.\n");
        break;
      case CREATE_SCANNER_INVALID_RELAY_HOST:
        fprintf (stderr,
                 "Scanner relay host must be a valid hostname,"
                 " IP address, UNIX socket path or empty.\n");
        break;
      case CREATE_SCANNER_UNIX_SOCKET_UNSUPPORTED:
        fprintf (stderr, "Scanner type does not support UNIX sockets.\n");
        break;
      case CREATE_SCANNER_OPENVASD_DISABLED:
        fprintf (stderr,
                 "openvasd scanner type is not supported"
                 " because the openvasd feature flag is disabled.\n");
        break;
      case CREATE_SCANNER_AGENT_DISABLED:
        fprintf (stderr,
                 "Agent controller scanner type is not supported"
                 " because the Agents feature flag is disabled.\n");
        break;
      case CREATE_SCANNER_CONTAINER_SCANNING_DISABLED:
        fprintf (stderr,
                 "Container image scanner type is not supported"
                 " because the Container scanning feature flag is disabled.\n");
        break;
      case CREATE_SCANNER_PERMISSION_DENIED:
        fprintf (stderr, "Permission denied.\n");
        break;
      default:
        fprintf (stderr, "Failed to create scanner.\n");
        break;
    }

  manage_option_cleanup ();

  return ret ? -1 : 0;
}

/**
 * @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 too old, -3 database needs to be
 *         initialised from server, -5 database is too new.
 */
int
manage_delete_scanner (GSList *log_config, const db_conn_info_t *database,
                       const gchar *uuid)
{
  int ret;

  assert (uuid);

  g_info ("   Deleting scanner.");

  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,
                             0 /* avoid_db_check_inserts */);
  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]  credential_id    UUID of credential to use or NULL to create.
 * @param[in]  key_pub_path     Certificate path.
 * @param[in]  key_priv_path    Private key path.
 * @param[in]  relay_host       Relay host of scanner.
 * @param[in]  relay_port       Relay port of scanner.
 *
 * @return 0 success, -1 failed.
 */
int
manage_modify_scanner (GSList *log_config, const db_conn_info_t *database,
                       const char *scanner_id, const char *name,
                       const char *host, const char *port,
                       const char *type, const char *ca_pub_path,
                       const char *credential_id,
                       const char *key_pub_path, const char *key_priv_path,
                       const char *relay_host, const char *relay_port)
{
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;
  scanner_t scanner;
  credential_t new_credential;
  gchar *used_credential_id;
  gchar *name_for_credential;

  g_info ("   Modifying scanner.");

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return -1;

  current_credentials.uuid = "";

  if (scanner_id)
    {
      /* Because the credentials are empty this will find the scanner regardless
       * of permissions and ownership, but that's the intention. */
      if (find_scanner_with_permission (scanner_id, &scanner, "get_scanners"))
        {
          fprintf (stderr, "Error finding scanner.\n");
          manage_option_cleanup ();
          return -1;
        }
      if (scanner == 0)
        {
          fprintf (stderr, "Failed to find scanner %s.\n", scanner_id);
          manage_option_cleanup ();
          return -1;
        }
    }
  else
    {
      fprintf (stderr, "Scanner UUID required.\n");
      manage_option_cleanup ();
      return -1;
    }

  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 (credential_id)
    {
      key_pub = NULL;
      key_priv = NULL;
      used_credential_id = g_strdup (credential_id);
    }
  else
    {
      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;

              char *encryption_key_uid = current_encryption_key_uid (TRUE);
              crypt_ctx = lsc_crypt_new (encryption_key_uid);
              free (encryption_key_uid);

              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);
            }
          used_credential_id = credential_uuid (new_credential);
        }
      else
        used_credential_id = NULL;
    }
  ret = modify_scanner (scanner_id, name, NULL, host, port, type, ca_pub,
                        used_credential_id, relay_host, relay_port);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  g_free (used_credential_id);
  switch (ret)
    {
      case MODIFY_SCANNER_SUCCESS:
        printf ("Scanner modified.\n");
        break;
      case MODIFY_SCANNER_ALREADY_EXISTS:
        fprintf (stderr, "Scanner with new name exists already.\n");
        break;
      case MODIFY_SCANNER_MISSING_ID:
        fprintf (stderr, "Scanner ID required.\n");
        break;
      case MODIFY_SCANNER_NOT_FOUND:
        fprintf (stderr, "Scanner not found.\n");
        break;
      case MODIFY_SCANNER_CREDENTIAL_NOT_FOUND:
        fprintf (stderr, "Credential not found.\n");
        break;
      case MODIFY_SCANNER_CREDENTIAL_NOT_CC:
        fprintf (stderr, "Credential should be 'cc'.\n");
        break;
      case CREATE_SCANNER_INVALID_TYPE:
        fprintf (stderr, "Invalid scanner type.\n");
        break;
      case MODIFY_SCANNER_INVALID_PORT:
        fprintf (stderr,
                 "Scanner port must be a valid port number (1 - 65535)"
                 " if host is not a UNIX socket path.\n");
        break;
      case MODIFY_SCANNER_INVALID_HOST:
        fprintf (stderr,
                 "Scanner host must be a valid hostname,"
                 " IP address or UNIX socket path.\n");
        break;
      case MODIFY_SCANNER_INVALID_RELAY_PORT:
        fprintf (stderr,
                 "Scanner relay port must be a valid port number (1 - 65535)"
                 " if relay host is not a UNIX socket path.\n");
        break;
      case MODIFY_SCANNER_INVALID_RELAY_HOST:
        fprintf (stderr,
                 "Scanner relay host must be a valid hostname,"
                 " IP address, UNIX socket path or empty.\n");
        break;
      case MODIFY_SCANNER_UNIX_SOCKET_UNSUPPORTED:
        fprintf (stderr,
                 "Scanner type does not support UNIX sockets.\n");
        break;
      case MODIFY_SCANNER_OPENVASD_DISABLED:
        fprintf (stderr,
                 "openvasd scanner type is not supported"
                 " because the openvasd feature flag is disabled.\n");
        break;
      case MODIFY_SCANNER_AGENT_DISABLED:
        fprintf (stderr,
                 "Agent controller scanner type is not supported"
                 " because the Agents feature flag is disabled.\n");
        break;
      case MODIFY_SCANNER_CONTAINER_SCANNING_DISABLED:
        fprintf (stderr,
                 "Container image scanner type is not supported"
                 " because the Container scanning feature flag is disabled.\n");
        break;
      case MODIFY_SCANNER_PERMISSION_DENIED:
        fprintf (stderr, "Permission denied.\n");
        break;
      default:
        fprintf (stderr, "Failed to modify scanner.\n");
        break;
    }

  manage_option_cleanup ();

  return ret ? -1 : 0;
}

/**
 * @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 failure.
 */
int
manage_verify_scanner (GSList *log_config, const db_conn_info_t *database,
                       const gchar *uuid)
{
  int ret;
  char *version;

  assert (uuid);

  g_info ("   Verifying scanner.");

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return -1;

  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;
      case 3:
        fprintf (stderr, "Failed to authenticate. Scanner version: %s\n",
                 version);
        break;
      default:
        fprintf (stderr, "Internal Error.\n");
        break;
    }
  current_credentials.uuid = NULL;

  manage_option_cleanup ();
  return ret ? -1 : 0;
}

/**
 * @brief Find a scanner for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of scanner.
 * @param[out]  scanner     Scanner return, 0 if successfully 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 Get the type of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Type.
 */
scanner_type_t
get_scanner_type (scanner_t scanner)
{
  if (scanner == 0)
    return SCANNER_TYPE_NONE;
  return sql_int ("SELECT type FROM scanners WHERE id=%llu", scanner);
}


/**
 * @brief Insert a scanner for create_scanner.
 *
 * @param[in]   name         Name of scanner.
 * @param[in]   comment      Comment on scanner.
 * @param[in]   host         Host of scanner.
 * @param[in]   ca_pub       CA Certificate for scanner.
 * @param[in]   iport        Port of scanner.
 * @param[in]   itype        Type of scanner.
 * @param[in]   relay_host   Relay host of scanner.
 * @param[in]   irelay_port  Relay port of scanner.
 * @param[out]  new_scanner  The created scanner.
 */
static void
insert_scanner (const char* name, const char *comment, const char *host,
                const char *ca_pub, int iport, int itype,
                const char *relay_host, int irelay_port,
                scanner_t *new_scanner)
{
  char *quoted_name, *quoted_comment, *quoted_host, *quoted_ca_pub;
  char *quoted_relay_host;

  assert (current_credentials.uuid);

  quoted_name = sql_quote (name ?: "");
  quoted_comment = sql_quote (comment ?: "");
  quoted_host = sql_quote (host ?: "");
  quoted_ca_pub = sql_quote (ca_pub ?: "");
  quoted_relay_host = sql_quote (relay_host ?: "");

  sql ("INSERT INTO scanners (uuid, name, owner, comment, host, port, type,"
       "                      ca_pub, creation_time, modification_time,"
       "                      relay_host, relay_port)"
       " VALUES (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '%s', %d, %d, %s%s%s, m_now (), m_now (),"
       "  '%s', %d);",
       quoted_name, current_credentials.uuid, quoted_comment, quoted_host,
       iport, itype,
       ca_pub ? "'" : "",
       ca_pub ? quoted_ca_pub : "NULL",
       ca_pub ? "'" : "",
       quoted_relay_host,
       irelay_port);

  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_ca_pub);
  g_free (quoted_relay_host);

  if (new_scanner)
    *new_scanner = sql_last_insert_id ();
}

/**
 * @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.
 * @param[in]   relay_host  Relay host of scanner.
 * @param[in]   relay_port  Relay port of scanner.
 *
 * @return 0 success, error otherwise (see create_scanner_return_t)
 */
create_scanner_return_t
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,
                const char *relay_host, const char *relay_port)
{
  int iport, itype, irelay_port, unix_socket, relay_unix_socket = 0;
  credential_t credential;

  assert (name);

  sql_begin_immediate ();

  if (acl_user_may ("create_scanner") == 0)
    {
      sql_rollback ();
      return CREATE_SCANNER_PERMISSION_DENIED;
    }

  if (resource_with_name_exists (name, "scanner", 0))
    {
      sql_rollback ();
      return CREATE_SCANNER_ALREADY_EXISTS;
    }

  if (!type)
    {
      sql_rollback ();
      return CREATE_SCANNER_MISSING_TYPE;
    }

  if (!host)
    {
      sql_rollback ();
      return CREATE_SCANNER_MISSING_HOST;
    }

  unix_socket = host && (*host == '/');
  relay_unix_socket = relay_host && (*relay_host == '/');
  itype = atoi (type);

  if (scanner_type_valid (itype) == 0)
    {
      sql_rollback ();
      return CREATE_SCANNER_INVALID_TYPE;
    }

  scanner_feature_status_t feature_res = check_scanner_feature (
    (scanner_type_t) itype);
  if (feature_res == SCANNER_FEATURE_OPENVASD_DISABLED)
    {
      /* openvasd feature disabled */
      sql_rollback ();
      return CREATE_SCANNER_OPENVASD_DISABLED;
    }
  else if (feature_res == SCANNER_FEATURE_AGENTS_DISABLED)
    {
      /* Agent feature disabled */
      sql_rollback ();
      return CREATE_SCANNER_AGENT_DISABLED;
    }
  else if (feature_res == SCANNER_FEATURE_CONTAINER_DISABLED)
    {
      /* Container scanning feature disabled */
      sql_rollback ();
      return CREATE_SCANNER_CONTAINER_SCANNING_DISABLED;
    }
  if (unix_socket)
    {
      ca_pub = NULL;
      iport = 0;

      if (! scanner_type_supports_unix_sockets (itype))
        {
          sql_rollback ();
          return CREATE_SCANNER_UNIX_SOCKET_UNSUPPORTED;
        }
    }
  else
    {
      iport = atoi (port ?: "0");
      if (iport <= 0 || iport > 65535)
        {
          sql_rollback ();
          return CREATE_SCANNER_INVALID_PORT;
        }
      if (gvm_get_host_type (host) == -1)
        {
          sql_rollback ();
          return CREATE_SCANNER_INVALID_HOST;
        }
    }

  if (relay_host == NULL || strcmp (relay_host, "") == 0)
    {
      irelay_port = 0;
    }
  else if (relay_unix_socket)
    {
      if (! scanner_type_supports_unix_sockets (itype))
        {
          sql_rollback ();
          return CREATE_SCANNER_UNIX_SOCKET_UNSUPPORTED;
        }
      irelay_port = 0;
    }
  else
    {
      irelay_port = atoi (relay_port ?: "0");
      if (irelay_port <= 0 || irelay_port > 65535)
        {
          sql_rollback ();
          return CREATE_SCANNER_INVALID_RELAY_PORT;
        }
      if (gvm_get_host_type (host) == -1)
        {
          sql_rollback ();
          return CREATE_SCANNER_INVALID_RELAY_HOST;
        }
    }

  if (unix_socket)
    insert_scanner (name, comment, host, ca_pub, iport, itype,
                    relay_host, irelay_port, new_scanner);
  else
    {
      credential = 0;
      if (credential_id
          && strcmp (credential_id, "")
          && strcmp (credential_id, "0"))
        {
          if (find_credential_with_permission
              (credential_id, &credential, "get_credentials"))
            {
              sql_rollback ();
              return CREATE_SCANNER_INTERNAL_ERROR;
            }
          if (credential == 0)
            {
              sql_rollback ();
              return CREATE_SCANNER_CREDENTIAL_NOT_FOUND;
            }
          if (sql_int ("SELECT type != 'cc' FROM credentials"
                       " WHERE id = %llu;",
                       credential))
            {
              sql_rollback ();
              return CREATE_SCANNER_CREDENTIAL_NOT_CC;
            }
        }

      insert_scanner (name, comment, host, ca_pub, iport, itype,
                      relay_host, irelay_port, new_scanner);

      if (credential)
        {
          sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;",
              credential, sql_last_insert_id ());
        }
    }

  sql_commit ();
  return CREATE_SCANNER_SUCCESS;
}

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

/**
 * @brief Modify a 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.
 * @param[in]   relay_host  Relay host of scanner.
 * @param[in]   relay_port  Relay port of scanner.
 *
 * @return 0 success, -1 internal error, other non-zero for other errors.
 *         (See modify_scanner_return_t)
 */
modify_scanner_return_t
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,
                const char *relay_host, const char *relay_port)
{
  gchar *used_host, *used_relay_host;
  gchar *quoted_name, *quoted_comment, *quoted_host, *quoted_relay_host;
  scanner_t scanner = 0;
  credential_t credential = 0;
  int itype, iport, irelay_port;
  int unix_socket, relay_unix_socket, credential_given;

  assert (current_credentials.uuid);

  if (scanner_id == NULL)
    return MODIFY_SCANNER_MISSING_ID;

  sql_begin_immediate ();

  if (acl_user_may ("modify_scanner") == 0)
    {
      sql_rollback ();
      return MODIFY_SCANNER_PERMISSION_DENIED;
    }

  if (find_scanner_with_permission (scanner_id, &scanner, "modify_scanner"))
    {
      sql_rollback ();
      return MODIFY_SCANNER_INTERNAL_ERROR;
    }
  if (scanner == 0)
    {
      sql_rollback ();
      return MODIFY_SCANNER_NOT_FOUND;
    }

  if (type)
    {
      itype = atoi (type);
      if (scanner_type_valid (itype) == 0)
        {
          sql_rollback ();
          return MODIFY_SCANNER_INVALID_TYPE;
        }
    }
  else
    itype = sql_int ("SELECT type FROM scanners WHERE id = %llu;", scanner);

  scanner_feature_status_t feature_res = check_scanner_feature (
    (scanner_type_t) itype);
  if (feature_res == SCANNER_FEATURE_OPENVASD_DISABLED)
    {
      /* openvasd feature disabled */
      sql_rollback ();
      return MODIFY_SCANNER_OPENVASD_DISABLED;
    }
  else if (feature_res == SCANNER_FEATURE_AGENTS_DISABLED)
    {
      /* Agent feature disabled */
      sql_rollback ();
      return MODIFY_SCANNER_AGENT_DISABLED;
    }
  else if (feature_res == SCANNER_FEATURE_CONTAINER_DISABLED)
    {
      /* Container scanning feature disabled */
      sql_rollback ();
      return MODIFY_SCANNER_CONTAINER_SCANNING_DISABLED;
    }

  if (port)
    iport = atoi (port);
  else
    iport = sql_int ("SELECT port FROM scanners WHERE id = %llu;",
                     scanner);

  if (relay_port)
    irelay_port = atoi (relay_port);
  else
    irelay_port = sql_int ("SELECT relay_port FROM scanners WHERE id = %llu;",
                           scanner);

  if (host)
    used_host = g_strdup (host);
  else
    used_host = sql_string ("SELECT host FROM scanners"
                            " WHERE id = %llu;",
                            scanner);

  if (relay_host)
    used_relay_host = g_strdup (relay_host);
  else
    used_relay_host = sql_string ("SELECT relay_host FROM scanners"
                                  " WHERE id = %llu;",
                                  scanner);

  unix_socket = used_host && (*used_host == '/');
  relay_unix_socket = used_relay_host && (*used_relay_host == '/');

  if (unix_socket)
    {
      iport = 0;
      if (! scanner_type_supports_unix_sockets (itype))
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_UNIX_SOCKET_UNSUPPORTED;
        }
    }
  else
    {
      if (port)
        iport = atoi (port);
      else
        iport = sql_int ("SELECT port FROM scanners"
                         " WHERE id = %llu;",
                         scanner);

      if (iport <= 0 || iport > 65535)
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_INVALID_PORT;
        }
    }

  if (unix_socket == 0
      && (gvm_get_host_type (used_host) == -1))
    {
      sql_rollback ();
      g_free (used_host);
      g_free (used_relay_host);
      return MODIFY_SCANNER_INVALID_HOST;
    }

  if (used_relay_host == NULL || strcmp (used_relay_host, "") == 0)
    {
      irelay_port = 0;
    }
  else if (relay_unix_socket)
    {
      if (! scanner_type_supports_unix_sockets (itype))
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_UNIX_SOCKET_UNSUPPORTED;
        }
      irelay_port = 0;
    }
  else
    {
      if (relay_port)
        irelay_port = atoi (relay_port);
      else
        irelay_port = sql_int ("SELECT relay_port FROM scanners"
                               " WHERE id = %llu;",
                               scanner);

      if (irelay_port <= 0 || irelay_port > 65535)
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_INVALID_RELAY_PORT;
        }

      if (gvm_get_host_type (used_relay_host) == -1)
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_INVALID_RELAY_HOST;
        }
    }

  if (credential_id
      && (strcmp (credential_id, "") == 0 || strcmp (credential_id, "0") == 0))
    {
      credential = 0;
      credential_given = 1;
    }
  else if (credential_id && !unix_socket)
    {
      if (find_credential_with_permission (credential_id, &credential,
                                           "get_credentials"))
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_INTERNAL_ERROR;
        }

      if (credential == 0)
        {
          sql_rollback ();
          g_free (used_host);
          g_free (used_relay_host);
          return MODIFY_SCANNER_CREDENTIAL_NOT_FOUND;
        }

      credential_given = 1;
    }
  else
    {
      credential = 0;
      credential_given = 1;
      sql_int64 (&credential,
                 "SELECT credential FROM scanners WHERE id = %llu;",
                 scanner);
    }

  if (credential)
    {
      if (sql_int ("SELECT type != 'cc' FROM credentials WHERE id = %llu;",
                   credential))
        {
          sql_rollback ();
          return MODIFY_SCANNER_CREDENTIAL_NOT_CC;
        }
    }

  /* Check whether a scanner with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "scanner", scanner))
        {
          sql_rollback ();
          return MODIFY_SCANNER_ALREADY_EXISTS;
        }
    }

  quoted_name = name ? sql_insert (name) : g_strdup ("name");
  quoted_comment = comment ? sql_insert (comment) : g_strdup ("comment");
  quoted_host = sql_insert (used_host);
  quoted_relay_host = sql_insert (used_relay_host);

  sql ("UPDATE scanners SET name = %s, comment = %s, type = %d,"
       " host = %s, port = %d, relay_host = %s, relay_port = %d,"
       " modification_time = m_now () WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       itype,
       quoted_host,
       iport,
       quoted_relay_host,
       irelay_port,
       scanner);
  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_relay_host);

  if (ca_pub && !unix_socket)
    {
      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_given)
    {
      if (credential)
        sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;",
             credential, scanner);
      else
        sql ("UPDATE scanners SET credential = NULL WHERE id = %llu;",
             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 config or 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_remove_resource ("scanner", scanner, LOCATION_TRASH);

      sql ("DELETE FROM scanners_trash WHERE id = %llu;", scanner);
      sql_commit ();
      return 0;
    }

  if (ultimate == 0)
    {
      scanner_t trash_scanner;

#if ENABLE_AGENTS
      // check scanner is in use by agent_group
      if (agent_group_tasks_exist_by_scanner (scanner))
        {
          sql_rollback ();
          return 1;
        }
#endif // ENABLE_AGENTS

      if (sql_int ("SELECT count(*) FROM tasks"
                   " WHERE scanner = %llu"
                   " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE)
                   " AND hidden = 0;",
                   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);

      trash_scanner = sql_last_insert_id ();

      sql ("UPDATE tasks"
           " SET scanner = %llu,"
           "     scanner_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE scanner = %llu"
           " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           trash_scanner,
           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_remove_resource ("scanner", scanner, LOCATION_TABLE);

#if ENABLE_AGENTS
      if (agent_group_hidden_tasks_exist_by_scanner (scanner)
          || agent_group_tasks_exist_by_scanner (scanner))
        {
          sql_rollback ();
          return 1;
        }
#endif // ENABLE_AGENTS
    }

#if ENABLE_AGENTS
  // Delete agent groups related to the scanner.
  delete_agent_groups_by_scanner (scanner);
  // Delete agents related to the scanner.
  delete_agents_by_scanner_and_uuids (scanner, NULL);
#endif // ENABLE_AGENTS

  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",              \
   "relay_host", "relay_port", 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 },                                            \
   {                                                                   \
     "(SELECT type FROM credentials WHERE id = credential)",           \
     "credential_type",                                                \
     KEYWORD_TYPE_STRING                                               \
   },                                                                  \
   { "relay_host" , NULL, KEYWORD_TYPE_STRING },                       \
   { "relay_port" , NULL, KEYWORD_TYPE_INTEGER },                      \
   { 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 },                                             \
   {                                                                    \
     "(SELECT CASE"                                                     \
     " WHEN credential_location = " G_STRINGIFY (LOCATION_TABLE)        \
     " THEN (SELECT type FROM credentials WHERE id = credential)"       \
     " ELSE (SELECT type FROM credentials_trash WHERE id = credential)" \
     " END)",                                                            \
     "credential_type",                                                 \
     KEYWORD_TYPE_STRING                                                \
   },                                                                   \
   { "relay_host" , NULL, KEYWORD_TYPE_STRING },                        \
   { "relay_port" , NULL, KEYWORD_TYPE_INTEGER },                       \
   { 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, 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)
        {
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          iterator->crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
        }
      secret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
      private_key = lsc_crypt_get_private_key (iterator->crypt_ctx, secret);
    }

  return private_key;
}

/**
 * @brief Get the Credential type from a scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Credential type, or NULL if iteration is complete. Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_credential_type, GET_ITERATOR_COLUMN_COUNT + 10);

/**
 * @brief Get the relay host from an scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Relay host, or NULL if iteration is complete.  Freed
 *         by cleanup_iterator.
 */
DEF_ACCESS (scanner_iterator_relay_host, GET_ITERATOR_COLUMN_COUNT + 11);

/**
 * @brief Get the relay port from an scanner iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Relay port, or -1 if iteration is complete.
 */
int
scanner_iterator_relay_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 12);
  return ret;
}

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

/**
 * @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, *with_clause;
  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, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT id, uuid, name, usage_type, %s FROM tasks"
                 " WHERE scanner = %llu AND hidden = 0"
                 " ORDER BY name ASC;",
                 with_clause ? with_clause : "",
                 available,
                 scanner);

  g_free (with_clause);
  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 usage type 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_usage_type, 3);

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

/**
 * @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 whether the scanner has a relay host defined.
 *
 * @param[in]  scanner    Scanner.
 *
 * @return TRUE if a relay host is defined, FALSE otherwise.
 */
gboolean
scanner_has_relay (scanner_t scanner)
{
  return sql_int ("SELECT coalesce (relay_host, '') != ''"
                  " FROM scanners WHERE id = %llu;",
                  scanner);
}

/**
 * @brief Return the host of a scanner.
 *
 * @param[in]  scanner    Scanner.
 * @param[in]  get_relay  Whether to get the relay host.
 *
 * @return Newly allocated host.
 */
char *
scanner_host (scanner_t scanner, gboolean get_relay)
{
  return sql_string ("SELECT %s FROM scanners WHERE id = %llu;",
                     get_relay ? "relay_host" : "host",
                     scanner);
}

/**
 * @brief Return the port of a scanner.
 *
 * @param[in]  scanner    Scanner.
 * @param[in]  get_relay  Whether to get the relay host.
 *
 * @return Scanner port, -1 if not found;
 */
int
scanner_port (scanner_t scanner, gboolean get_relay)
{
  int port;
  char *str;
  str = sql_string ("SELECT %s FROM scanners WHERE id = %llu;",
                    get_relay ? "relay_port" : "port",
                    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;
      char *encryption_key_uid = current_encryption_key_uid (TRUE);
      crypt_ctx = lsc_crypt_new (encryption_key_uid);
      free (encryption_key_uid);

      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;
      char *encryption_key_uid = current_encryption_key_uid (TRUE);
      crypt_ctx = lsc_crypt_new (encryption_key_uid);
      free (encryption_key_uid);

      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 Return the name of a scanner in the trashcan.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
trash_scanner_name (scanner_t scanner)
{
  return sql_string ("SELECT name FROM scanners_trash WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Return the UUID of a scanner in the trashcan.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated UUID.
 */
char *
trash_scanner_uuid (scanner_t scanner)
{
  return sql_string ("SELECT uuid FROM scanners_trash WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Count number of scanners.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of scanners in filtered set.
 */
int
scanner_count (const get_data_t *get)
{
  static const char *extra_columns[] = SCANNER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = SCANNER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = SCANNER_ITERATOR_TRASH_COLUMNS;

  return count ("scanner", get, columns, trash_columns, extra_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Get the default scanner path or host.
 *
 * @return Newly allocated scanner path or host.
 */
char *
openvas_default_scanner_host ()
{
  return sql_string ("SELECT host FROM scanners WHERE uuid = '%s'",
                     SCANNER_UUID_DEFAULT);
}

/**
 * @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_connect_data_t conn_data;
  osp_connection_t *connection;

  assert (iterator);
  osp_connect_data_from_scanner_iterator (iterator, &conn_data);
  connection = osp_connect_with_data (&conn_data);
  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_connect_data_t conn_data;
  osp_connection_t *connection;

  assert (iterator);
  osp_connect_data_from_scanner_iterator (iterator, &conn_data);
  connection = osp_connect_with_data (&conn_data);
  if (!connection)
    return 1;
  if (osp_get_scanner_details (connection, desc, params))
    return 1;
  osp_connection_close (connection);
  return 0;
}

#if ENABLE_OPENVASD
/**
 * @brief Get an openvasd Scanner's get_scanner_preferences info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  desc        Scanner description.
 * @param[out]  params      Scanner parameters.
 *
 * @return 0 success, 1 for failure.
 */
int
openvasd_get_details_from_iterator (iterator_t *iterator, char **desc,
                               GSList **params)
{
  int port;
  http_scanner_connector_t connection;
  const char *host, *ca_pub, *key_pub, *key_priv, *protocol;

  assert (iterator);
  host = scanner_iterator_host (iterator);
  port = scanner_iterator_port (iterator);
  ca_pub = scanner_iterator_ca_pub (iterator);
  key_pub = scanner_iterator_key_pub (iterator);
  key_priv = scanner_iterator_key_priv (iterator);

  /* Determine protocol based on certificate presence */
  if (ca_pub && key_pub && key_priv)
    protocol = "https";
  else
    protocol = "http";

  connection = http_scanner_connector_new ();

  http_scanner_connector_builder (connection, HTTP_SCANNER_HOST, host);
  http_scanner_connector_builder (connection, HTTP_SCANNER_PROTOCOL, protocol);
  http_scanner_connector_builder (connection, HTTP_SCANNER_CA_CERT, ca_pub);
  http_scanner_connector_builder (connection, HTTP_SCANNER_KEY, key_priv);
  http_scanner_connector_builder (connection, HTTP_SCANNER_CERT, key_pub);
  http_scanner_connector_builder (connection, HTTP_SCANNER_PORT, (void *) &port);

  if (!connection)
    return 1;

  *desc = g_strdup_printf("openvasd Sensor on %s://%s:%d", protocol, host, port);
  if (http_scanner_parsed_scans_preferences (connection, params) < 0)
    return 1;
  http_scanner_connector_free (connection);
  return 0;
}
#endif

#if ENABLE_AGENTS
/**
 * @brief Get an Agent Control sensor's version info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  name        Scanner name.
 * @param[out]  version     Scanner version.
 *
 * @return 0 success, 1 for failure.
 */
int
agent_control_get_version_from_iterator (iterator_t *iterator,
                                         char **name,
                                         char **version)
{
  int port;
  const char *host, *ca_pub, *key_pub, *key_priv;
  const char *protocol = "https";
  agent_controller_connector_t connection;

  assert (iterator);

  host = scanner_iterator_host (iterator);
  port = scanner_iterator_port (iterator);
  ca_pub = scanner_iterator_ca_pub (iterator);
  key_pub = scanner_iterator_key_pub (iterator);
  key_priv = scanner_iterator_key_priv (iterator);

  if (!ca_pub || !key_pub)
    {
      g_debug ("%s: Falling back to HTTP due to missing CA or cert", __func__);
      protocol = "http";
    }

  connection = agent_controller_connector_new ();
  if (!connection)
    return 1;

  agent_controller_connector_builder (connection, AGENT_CONTROLLER_HOST, host);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_PROTOCOL, protocol);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_CA_CERT, ca_pub);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_KEY, key_priv);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_CERT, key_pub);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_PORT, (void *) &port);

  /*
   The following block has to be replaced by something like:

        agent_controller_get_version (name, version);
        agent_controller_connector_free (connection);
        if (name == NULL && version == NULL)
          return 1;

   as soon as the required endpoint is available.
   */
  agent_controller_agent_list_t agents = agent_controller_get_agents (connection);
  agent_controller_connector_free (connection);
  if (agents == NULL)
    return 1;
  *name = g_strdup ("TestName");
  *version = g_strdup ("TestVersion");
  agent_controller_agent_list_free (agents);

  return 0;
}

/**
 * @brief Get an Agent Control scanner's preferences info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  desc        Scanner description.
 * @param[out]  params      Scanner parameters.
 *
 * @return 0 success, 1 for failure.
 */
int
agent_control_get_details_from_iterator (iterator_t *iterator, char **desc,
                                         GSList **params)
{
  int port;
  const char *host, *ca_pub, *key_pub, *key_priv;
  const char *protocol = "https";
  agent_controller_connector_t connection;

  assert (iterator);

  host = scanner_iterator_host (iterator);
  port = scanner_iterator_port (iterator);
  ca_pub = scanner_iterator_ca_pub (iterator);
  key_pub = scanner_iterator_key_pub (iterator);
  key_priv = scanner_iterator_key_priv (iterator);

  if (!ca_pub || !key_pub)
    {
      g_debug ("%s: Falling back to HTTP due to missing CA or cert", __func__);
      protocol = "http";
    }

  connection = agent_controller_connector_new ();
  if (!connection)
    return 1;

  agent_controller_connector_builder (connection, AGENT_CONTROLLER_HOST, host);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_PROTOCOL, protocol);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_CA_CERT, ca_pub);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_KEY, key_priv);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_CERT, key_pub);
  agent_controller_connector_builder (connection, AGENT_CONTROLLER_PORT, (void *) &port);

  *desc = g_strdup_printf ("Agent Control Scanner on %s://%s:%d", protocol, host, port);

  agent_controller_agent_list_t agents = agent_controller_get_agents (connection);
  agent_controller_connector_free (connection);

  if (agents == NULL)
    return 1;

  agent_controller_agent_list_free (agents);
  return 0;
}
#endif

/**
 * @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 authentication failed, 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_OPENVAS
      || scanner_iterator_type (&scanner) == SCANNER_TYPE_OSP_SENSOR)
    {
      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_OPENVASD
      || scanner_iterator_type (&scanner) == SCANNER_TYPE_OPENVASD_SENSOR)
    {
      cleanup_iterator (&scanner);
      return 0;
    }
#if ENABLE_AGENTS
  else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_AGENT_CONTROLLER)
    {
      cleanup_iterator (&scanner);
      return 0;
    }
  else if (scanner_iterator_type (&scanner) ==
           SCANNER_TYPE_AGENT_CONTROLLER_SENSOR)
    {
      char *name;
      int ret = agent_control_get_version_from_iterator (&scanner, &name,
                                                         version);
      cleanup_iterator (&scanner);
      g_free (name);

      if (ret)
        return 2;
      return 0;
    }
#endif
#if ENABLE_CONTAINER_SCANNING
  else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_CONTAINER_IMAGE)
    {
      // Once container scanner is availabe and has version endpoint, replace
      // this
      if (version)
        *version = g_strdup ("TestVersion");

      cleanup_iterator (&scanner);
      return 0;
    }
#endif
  else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_CVE)
    {
      if (version)
        *version = g_strdup ("GVM/" GVMD_VERSION);
      cleanup_iterator (&scanner);
      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 db_conn_info_t *database)
{
  iterator_t scanners;
  int ret;

  g_info ("   Getting scanners.");

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return ret;

  init_iterator (&scanners,
                 "SELECT uuid, type, host, port, name FROM scanners;");

  while (next (&scanners))
    {
      const char *scanner_id, *scanner_host, *scanner_port, *scanner_name;
      scanner_type_t scanner_type;
      const char *scanner_type_str;

      scanner_id = iterator_string (&scanners, 0);
      scanner_type = iterator_int (&scanners, 1);
      scanner_host = iterator_string (&scanners, 2);
      scanner_port = iterator_string (&scanners, 3);
      scanner_name = iterator_string (&scanners, 4);

      switch (scanner_type)
        {
          case SCANNER_TYPE_OPENVAS:
            scanner_type_str = "OpenVAS";
            break;
          case SCANNER_TYPE_CVE:
            scanner_type_str = "CVE";
            break;
          case SCANNER_TYPE_OSP_SENSOR:
            scanner_type_str = "OSP-Sensor";
            break;
          case SCANNER_TYPE_OPENVASD:
            scanner_type_str = "openvasd";
            break;
          case SCANNER_TYPE_OPENVASD_SENSOR:
            scanner_type_str = "openvasd-sensor";
            break;
          case SCANNER_TYPE_AGENT_CONTROLLER:
            scanner_type_str = "agent-controller";
            break;
          case SCANNER_TYPE_AGENT_CONTROLLER_SENSOR:
            scanner_type_str = "agent-controller-sensor";
            break;
          case SCANNER_TYPE_CONTAINER_IMAGE:
            scanner_type_str = "container-image";
            break;
          default:
            scanner_type_str = NULL;
        }

      if (scanner_type_str)
        printf ("%s  %s  %s  %s  %s\n",
                scanner_id,
                scanner_type_str,
                scanner_host,
                scanner_port,
                scanner_name);
      else
        printf ("%s  Unknown-%d  %s  %s  %s\n",
                scanner_id,
                scanner_type,
                scanner_host,
                scanner_port,
                scanner_name);
    }
  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 successfully 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]   ical_string iCalendar string.  Overrides first_time, period,
 *                           period_months, byday and duration.
 * @param[in]   zone        Timezone.
 * @param[out]  schedule    Created schedule.
 * @param[out]  error_out   Output for iCalendar errors and warnings.
 *
 * @return 0 success, 1 schedule exists already,
 *         3 error in iCal string, 4 error in timezone, 99 permission denied.
 */
int
create_schedule (const char* name, const char *comment,
                 const char *ical_string, const char* zone,
                 schedule_t *schedule, gchar **error_out)
{
  gchar *quoted_comment, *quoted_name, *quoted_timezone;
  gchar *insert_timezone;
  int byday_mask;
  icalcomponent *ical_component;
  icaltimezone *ical_timezone;
  gchar *quoted_ical;
  time_t first_time, period, period_months, duration;

  assert (current_credentials.uuid);
  assert (ical_string && strcmp (ical_string, ""));

  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 (zone && strcmp (zone, ""))
    insert_timezone = g_strdup (zone);
  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");
        }
    }

  ical_timezone = icalendar_timezone_from_string (insert_timezone);
  if (ical_timezone == NULL)
    {
      g_free (insert_timezone);
      return 4;
    }

  quoted_comment = sql_quote (comment ? comment : "");
  quoted_timezone = sql_quote (insert_timezone);

  ical_component = icalendar_from_string (ical_string, ical_timezone,
                                          error_out);
  if (ical_component == NULL)
    {
      g_free (quoted_name);
      g_free (quoted_comment);
      g_free (insert_timezone);
      g_free (quoted_timezone);
      return 3;
    }
  quoted_ical = sql_quote (icalcomponent_as_ical_string (ical_component));
  first_time = icalendar_first_time_from_vcalendar (ical_component,
                                                    ical_timezone);
  duration = icalendar_duration_from_vcalendar (ical_component);

  icalendar_approximate_rrule_from_vcalendar (ical_component,
                                              &period,
                                              &period_months,
                                              &byday_mask);

  sql ("INSERT INTO schedules"
       " (uuid, name, owner, comment, first_time, period, period_months,"
       "  byday, duration, timezone, icalendar,"
       "  creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', %i, %i, %i, %i, %i,"
       "  '%s', '%s',"
       "  m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment, first_time,
       period, period_months, byday_mask, duration, quoted_timezone,
       quoted_ical);

  if (schedule)
    *schedule = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_comment);
  g_free (insert_timezone);
  g_free (quoted_timezone);
  g_free (quoted_ical);

  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, byday, duration,"
                        " timezone, icalendar",
                        1, new_schedule, NULL);
}

/**
 * @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_remove_resource ("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;",
                   schedule))
        {
          sql_rollback ();
          return 1;
        }

      sql ("INSERT INTO schedules_trash"
           " (uuid, owner, name, comment, first_time, period, period_months,"
           "  byday, duration, timezone, creation_time,"
           "  modification_time, icalendar)"
           " SELECT uuid, owner, name, comment, first_time, period, period_months,"
           "        byday, duration, timezone, creation_time,"
           "        modification_time, icalendar"
           " 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_remove_resource ("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 UUID of a trash schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Newly allocated UUID.
 */
char *
trash_schedule_uuid (schedule_t schedule)
{
  return sql_string ("SELECT uuid FROM schedules_trash 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 name of a trash schedule.
 *
 * @param[in]  schedule  Schedule.
 *
 * @return Newly allocated name.
 */
char *
trash_schedule_name (schedule_t schedule)
{
  return sql_string ("SELECT name FROM schedules_trash 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[in]  trash       Whether to get schedule from trash.
 * @param[out] icalendar      iCalendar string.
 * @param[out] zone           Timezone string.
 *
 * @return 0 success, -1 error.
 */
int
schedule_info (schedule_t schedule, int trash, gchar **icalendar, gchar **zone)
{
  iterator_t schedules;

  init_iterator (&schedules,
                 "SELECT icalendar, timezone FROM schedules%s"
                 " WHERE id = %llu;",
                 trash ? "_trash" : "",
                 schedule);
  if (next (&schedules))
    {
      *icalendar = g_strdup (iterator_string (&schedules, 0));
      *zone = g_strdup (iterator_string (&schedules, 1));
      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", "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 },                              \
   { "icalendar", NULL, KEYWORD_TYPE_STRING },                             \
   { "next_time_ical (icalendar, m_now()::bigint, 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 },                              \
   { "icalendar", NULL, KEYWORD_TYPE_STRING },                             \
   { 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, 2 failed to find"
 *         filter (filt_id), -1 error.
 */
int
init_schedule_iterator (iterator_t* iterator, 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 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 iCalendar string from a schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The iCalendar string or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (schedule_iterator_icalendar, GET_ITERATOR_COLUMN_COUNT + 5);

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

  ret = sql_begin_immediate_giveup ();
  if (ret)
    return ret;

  init_iterator (iterator,
                 "SELECT tasks.id, tasks.uuid,"
                 " schedules.id, tasks.schedule_next_time,"
                 " schedules.icalendar, schedules.timezone,"
                 " schedules.duration,"
                 " users.uuid, users.name"
                 " FROM tasks, schedules, users"
                 " WHERE tasks.schedule = schedules.id"
                 " AND tasks.hidden = 0"
                 " AND (tasks.owner = (users.id))"
                 /* 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;");

  return 0;
}

/**
 * @brief Cleanup a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 */
void
cleanup_task_schedule_iterator (iterator_t* iterator)
{
  cleanup_iterator (iterator);
  sql_commit ();
}

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

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

/**
 * @brief Get the next time from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Next time.
 */
static 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 iCalendar string from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return period.
 */
DEF_ACCESS (task_schedule_iterator_icalendar, 4);

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

/**
 * @brief Get the next time from a task schedule iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Next time.
 */
static time_t
task_schedule_iterator_duration (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (time_t) iterator_int64 (iterator, 6);
}

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

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

/**
 * @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_INTERRUPTED
       || 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)
{
  const char *icalendar, *zone;
  time_t duration;

  if (iterator->done) return FALSE;

  icalendar = task_schedule_iterator_icalendar (iterator);
  zone = task_schedule_iterator_timezone (iterator);
  duration = task_schedule_iterator_duration (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
          || run_status == TASK_STATUS_QUEUED)
        {
          time_t now, start;

          now = time (NULL);

          start = icalendar_next_time_from_string (icalendar, time(NULL), zone,
                                                   -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_INTERRUPTED
       || 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, *with_clause;
  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, 0,
                               &with_clause);
  array_free (permissions);
  init_iterator (iterator,
                 "%s"
                 " SELECT id, uuid, name, %s FROM tasks"
                 " WHERE schedule = %llu AND hidden = 0"
                 " ORDER BY name ASC;",
                 with_clause ? with_clause : "",
                 available,
                 schedule,
                 current_credentials.uuid);
  g_free (with_clause);
  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]   ical_string  iCalendar string.  Overrides first_time, period,
 *                           period_months, byday and duration.
 * @param[in]   zone         Timezone.
 * @param[out]  error_out    Output for iCalendar errors and warnings.
 *
 * @return 0 success, 1 failed to find schedule, 2 schedule with new name exists,
 *         3 error in type name, 4 schedule_id required,
 *         6 error in iCalendar, 7 error in zone,
 *         99 permission denied, -1 internal error.
 */
int
modify_schedule (const char *schedule_id, const char *name, const char *comment,
                 const char *ical_string, const char *zone, gchar **error_out)
{
  gchar *quoted_name, *quoted_comment, *quoted_timezone, *quoted_icalendar;
  icalcomponent *ical_component;
  icaltimezone *ical_timezone;
  schedule_t schedule;
  time_t new_next_time, ical_first_time, ical_duration;
  gchar *real_timezone;

  assert (ical_string && strcmp (ical_string, ""));

  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;

  /* Update basic data */
  quoted_comment = comment ? sql_quote (comment) : NULL;

  if (zone == NULL)
    quoted_timezone = NULL;
  else
    {
      quoted_timezone = g_strstrip (sql_quote (zone));
      if (strcmp (quoted_timezone, "") == 0)
        {
          g_free (quoted_timezone);
          quoted_timezone = NULL;
        }
    }

  sql ("UPDATE schedules SET"
       " name = %s%s%s,"
       " comment = %s%s%s,"
       " timezone = %s%s%s"
       " 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 ? "'" : "",
       schedule);

  real_timezone
    = sql_string ("SELECT timezone FROM schedules WHERE id = %llu;",
                  schedule);

  /* Update times */

  ical_timezone = icalendar_timezone_from_string (real_timezone);
  if (ical_timezone == NULL)
    {
      g_free (real_timezone);
      sql_rollback ();
      return 7;
    }

  ical_component = icalendar_from_string (ical_string, ical_timezone,
                                          error_out);
  if (ical_component == NULL)
    {
      g_free (real_timezone);
      sql_rollback ();
      return 6;
    }

  quoted_icalendar = sql_quote (icalcomponent_as_ical_string
                                  (ical_component));

  ical_first_time = icalendar_first_time_from_vcalendar (ical_component,
                                                         ical_timezone);
  ical_duration = icalendar_duration_from_vcalendar (ical_component);

  sql ("UPDATE schedules SET"
       " icalendar = '%s',"
       " first_time = %ld,"
       " period = 0,"
       " period_months = 0,"
       " byday = 0,"
       " duration = %d,"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_icalendar,
       ical_first_time,
       ical_duration,
       schedule);

  // Update scheduled next times for tasks

  new_next_time = icalendar_next_time_from_vcalendar (ical_component,
                                                      time(NULL),
                                                      real_timezone, 0);

  sql ("UPDATE tasks SET schedule_next_time = %ld"
       " WHERE schedule = %llu;",
       new_next_time,
       schedule);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_timezone);
  g_free (quoted_icalendar);

  free (real_timezone);
  icalcomponent_free (ical_component);

  sql_commit ();

  return 0;
}


/* 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 successfully failed to find
 *                          permission.
 *
 * @return FALSE on success (including if failed to find permission), TRUE on
 *         error.
 */
static 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]   check_access    Whether to check if user may get resource and
 *                              subject.
 * @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 (gboolean check_access, 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_gmp_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")
      && (((gmp_command_takes_resource (name_arg) == 0)
           && strcasecmp (name_arg, "super"))))
    return 9;

  if (resource_type_arg
      && strcasecmp (name_arg, "super") == 0
      && strcmp (resource_type_arg, "group")
      && strcmp (resource_type_arg, "role")
      && strcmp (resource_type_arg, "user"))
    return 5;

  if (resource_type_arg
      && strcasecmp (name_arg, "super")
      && (valid_db_resource_type (resource_type_arg) == 0
          || gmp_command_takes_resource (name_arg) == 0))
    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")
                        ? gmp_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 (check_access == FALSE)
            {
              if (find_resource_no_acl (*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 (*resource_type);
              *resource_type = g_strdup ("os");
              if (check_access == FALSE)
                {
                  if (find_resource_no_acl (*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;
                    }
                }
            }
        }
      else if (check_access == FALSE)
        {
          if (find_resource_no_acl (*resource_type, resource_id_arg, resource))
            {
              g_free (*name);
              g_free (*resource_type);
              return -1;
            }
        }
      else
        {
          gchar *get_permission;
          get_permission = g_strdup_printf ("get_%ss", *resource_type);
          if (find_resource_with_permission (*resource_type, resource_id_arg,
                                             resource, get_permission, 0))
            {
              g_free (*name);
              g_free (*resource_type);
              g_free (get_permission);
              return -1;
            }
          g_free (get_permission);
        }

      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 (check_access)
        {
          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
        {
          if (find_resource_no_acl (subject_type, subject_id, subject))
            {
              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",
                     __func__, subject_type);
        }
    }
  return subject_where;
}

/**
 * @brief Create a permission.
 *
 * Caller must organise the transaction.
 *
 * @param[in]   check_access    Whether to check if user may CREATE_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_internal (int check_access, 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 (check_access && (acl_user_may ("create_permission") == 0))
    return 99;

  ret = check_permission_args (check_access, 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 Permissions cache */
  if (strcasecmp (quoted_name, "super") == 0)
    cache_all_permissions_for_users (NULL);
  else if (resource_type && resource)
    cache_permissions_for_resource (resource_type, resource, NULL);

  /* 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 (1, 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.
 *
 * Does not require current user to have CREATE_PERMISSION access.
 *
 * @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_no_acl (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)
{
  return create_permission_internal (0, name_arg, comment, resource_type_arg,
                                     resource_id_arg, subject_type, subject_id,
                                     permission);
}

/**
 * @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;
  /* There are no permissions on permissions, so no need for the
   * "_with_permission" version. */
  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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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.
 */
static 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                                                     \
   },                                                                        \
   { "CAST ((resource_location = " G_STRINGIFY (LOCATION_TRASH) ")"          \
     "      AS INTEGER)",                                                    \
     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                                                     \
   },                                                                        \
   { "CAST ((subject_location = " G_STRINGIFY (LOCATION_TRASH) ")"           \
     "      AS INTEGER)",                                                    \
     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                                                     \
   },                                                                        \
   { "CAST ((resource_location = " G_STRINGIFY (LOCATION_TRASH) ")"          \
     "      AS INTEGER)",                                                    \
     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                                                     \
   },                                                                        \
   { "CAST ((subject_location = " G_STRINGIFY (LOCATION_TRASH) ")"           \
     "      AS INTEGER)",                                                    \
     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, 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 (type_is_info_subtype (type))
    permission = g_strdup ("get_info");
  else if (type_is_asset_subtype (type))
    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 successfully 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.
 */
static 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_remove_resource ("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_remove_resource ("permission", permission, LOCATION_TABLE);

  name = permission_name (permission);
  resource_type = permission_resource_type (permission);

  sql ("DELETE FROM permissions WHERE id = %llu;", permission);

  /* Update Permissions cache */
  if (strcasecmp (name, "super") == 0)
    cache_all_permissions_for_users (NULL);
  else if (resource_type && resource)
    cache_permissions_for_resource (resource_type, resource, NULL);

  /* Update Reports cache */
  if (resource_type && (resource > 0) && 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 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;
  /* There are no permissions on permissions, so no need for the
   * "_with_permission" version. */
  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
         (TRUE, 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);
      g_free (subject_where_old);
      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 = permission_resource (permission);


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

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

  /* Update permission caches according to the modifications. */

  if (strcasecmp (name, "super") == 0 || strcasecmp (old_name, "super") == 0)
    cache_all_permissions_for_users (NULL);
  else
    {
      if (resource_type && resource_id && strcmp (resource_id, ""))
        cache_permissions_for_resource (resource_type, resource, NULL);

      if (old_resource
          && old_resource_type
          && ((resource != old_resource)
              || (resource_type
                  && strcmp (old_resource_type, resource_type))))
        cache_permissions_for_resource (old_resource_type, old_resource, NULL);
    }

  /* 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);
  g_free (name);
  free (old_name);
  free (old_resource_type);
  g_free (subject_where);

  sql_commit ();

  return 0;
}

/**
 * @brief Add role permissions to feed objects according to the
 *        'Feed Import Roles' setting.
 *
 * @param[in]  type             The object type, e.g. report_format.
 * @param[in]  type_cap         Capitalized type, e.g. "Report Format"
 * @param[out] permission_count Number of permissions added.
 * @param[out] object_count     Number of data objects affected.
 */
static void
add_feed_role_permissions (const char *type,
                           const char *type_cap,
                           int *permission_count,
                           int *object_count)
{
  char *roles_str;
  gchar **roles;
  iterator_t resources;

  roles_str = NULL;
  setting_value (SETTING_UUID_FEED_IMPORT_ROLES, &roles_str);

  if (roles_str == NULL || strlen (roles_str) == 0)
    {
      g_message ("%s: No feed import roles defined", __func__);
      g_free (roles_str);
      return;
    }

  roles = g_strsplit (roles_str, ",", 0);
  free (roles_str);

  init_iterator (&resources,
                 "SELECT id, uuid, name, owner FROM %ss"
                 " WHERE predefined = 1",
                 type);
  while (next (&resources))
    {
      gboolean added_permission = FALSE;
      resource_t permission_resource = iterator_int64 (&resources, 0);
      const char *permission_resource_id = iterator_string (&resources, 1);
      const char *permission_resource_name = iterator_string (&resources, 2);
      user_t owner = iterator_int64 (&resources, 3);
      gchar **role = roles;

      while (*role)
        {
          char *role_name = NULL;
          resource_name ("role", *role, LOCATION_TABLE, &role_name);

          if (sql_int ("SELECT count(*) FROM permissions"
                       " WHERE name = 'get_%ss'"
                       "   AND subject_type = 'role'"
                       "   AND subject"
                       "         = (SELECT id FROM roles WHERE uuid='%s')"
                       "   AND resource = %llu",
                       type,
                       *role,
                       permission_resource))
            {
              g_debug ("Role %s (%s) already has read permission"
                       " for %s %s (%s).",
                       role_name,
                       *role,
                       type_cap,
                       permission_resource_name,
                       permission_resource_id);
            }
          else
            {
              gchar *permission_name;

              g_info ("Creating read permission for role %s (%s)"
                      " on %s %s (%s).",
                      role_name,
                      *role,
                      type_cap,
                      permission_resource_name,
                      permission_resource_id);

              added_permission = TRUE;
              if (permission_count)
                *permission_count = *permission_count + 1;

              permission_name = g_strdup_printf ("get_%ss", type);

              current_credentials.uuid = user_uuid (owner);
              switch (create_permission_internal
                       (0,
                        permission_name,
                        "Automatically created by"
                        " --optimize",
                        type,
                        permission_resource_id,
                        "role",
                        *role,
                        NULL))
                {
                  case 0:
                    // success
                    break;
                  case 2:
                    g_warning ("%s: failed to find role %s for permission",
                               __func__, *role);
                    break;
                  case 3:
                    g_warning ("%s: failed to find %s %s for permission",
                               __func__, type_cap, permission_resource_id);
                    break;
                  case 5:
                    g_warning ("%s: error in resource when creating permission"
                               " for %s %s",
                               __func__, type_cap, permission_resource_id);
                    break;
                  case 6:
                    g_warning ("%s: error in subject (Role %s)",
                               __func__, *role);
                    break;
                  case 7:
                    g_warning ("%s: error in name %s",
                               __func__, permission_name);
                    break;
                  case 8:
                    g_warning ("%s: permission on permission", __func__);
                    break;
                  case 9:
                    g_warning ("%s: permission %s does not accept resource",
                               __func__, permission_name);
                    break;
                  case 99:
                    g_warning ("%s: permission denied to create %s permission"
                               " for role %s on %s %s",
                               __func__, permission_name, *role, type_cap,
                               permission_resource_id);
                    break;
                  default:
                    g_warning ("%s: internal error creating %s permission"
                               " for role %s on %s %s",
                               __func__, permission_name, *role, type_cap,
                               permission_resource_id);
                    break;
                }

              free (current_credentials.uuid);
              current_credentials.uuid = NULL;
            }

          free (role_name);
          role ++;
        }
      if (object_count && added_permission)
        *object_count = *object_count + 1;
    }

  cleanup_iterator (&resources);
  g_strfreev (roles);

  return;
}


/**
 * @brief Delete permissions to feed objects for roles that are not set
 *        in the 'Feed Import Roles' setting.
 *
 * @param[in]  type  The object type, e.g. report_format.
 * @param[in]  type_cap         Capitalized type, e.g. "Report Format"
 * @param[out] permission_count Number of permissions added.
 * @param[out] object_count     Number of data objects affected.
 */
static void
clean_feed_role_permissions (const char *type,
                             const char *type_cap,
                             int *permission_count,
                             int *object_count)
{
  char *roles_str;
  gchar **roles, **role;
  GString *sql_roles;
  iterator_t resources;

  roles_str = NULL;
  setting_value (SETTING_UUID_FEED_IMPORT_ROLES, &roles_str);

  if (roles_str == NULL || strlen (roles_str) == 0)
    {
      g_message ("%s: No feed import roles defined", __func__);
      g_free (roles_str);
      return;
    }

  sql_roles = g_string_new ("(");

  roles = g_strsplit (roles_str, ",", 0);
  role = roles;
  while (*role)
    {
      gchar *quoted_role = sql_insert (*role);
      g_string_append (sql_roles, quoted_role);

      role ++;
      if (*role)
        g_string_append (sql_roles, ", ");
    }

  g_string_append (sql_roles, ")");
  g_debug ("%s: Keeping permissions for roles %s\n", __func__, sql_roles->str);

  init_iterator (&resources,
                 "SELECT id, uuid, name FROM %ss"
                 " WHERE predefined = 1",
                 type);

  while (next (&resources))
    {
      gboolean removed_permission = FALSE;
      resource_t permission_resource = iterator_int64 (&resources, 0);
      const char *permission_resource_id = iterator_string (&resources, 1);
      const char *permission_resource_name = iterator_string (&resources, 2);
      iterator_t permissions;
      roles = NULL;

      init_iterator (&permissions,
                     "DELETE FROM permissions"
                     " WHERE name = 'get_%ss'"
                     "   AND resource = %llu"
                     "   AND subject_type = 'role'"
                     "   AND subject NOT IN"
                     "     (SELECT id FROM roles WHERE uuid IN %s)"
                     " RETURNING"
                     "   (SELECT uuid FROM roles WHERE id = subject),"
                     "   (SELECT name FROM roles WHERE id = subject)",
                     type,
                     permission_resource,
                     sql_roles->str);

      while (next (&permissions))
        {
          const char *role_id = iterator_string (&permissions, 0);
          const char *role_name = iterator_string (&permissions, 1);
          g_info ("Removed permission on %s %s (%s) for role %s (%s)",
                  type_cap,
                  permission_resource_name,
                  permission_resource_id,
                  role_name,
                  role_id);

          if (permission_count)
            *permission_count = *permission_count + 1;
          removed_permission = TRUE;
        }

      if (object_count && removed_permission)
        *object_count = *object_count + 1;
    }

  cleanup_iterator (&resources);
  g_strfreev (roles);

  return;
}


/* Schema. */

/**
 * @brief Generate the GMP 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/gvmd_schema_XXXXXX";

    if (mkdtemp (output_dir) == NULL)
      {
        g_warning ("%s: mkdtemp failed", __func__);
        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 (GVMD_DATA_DIR,
                                   "global_schema_formats",
                                   uuid_format,
                                   NULL);

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

    if (!gvm_file_is_readable (script))
      {
        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. */

      previous_dir = getcwd (NULL, 0);
      if (previous_dir == NULL)
        {
          g_warning ("%s: Failed to getcwd: %s",
                     __func__,
                     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",
                     __func__,
                     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 " GVMD_DATA_DIR
                                 "/global_schema_formats"
                                 "/18e826fc-dab6-11df-b913-002264764cea/GMP.xml"
                                 " > %s"
                                 " 2> /dev/null",
                                 script,
                                 output_file);
      g_free (script);

      g_debug ("   command: %s", 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",
                     __func__,
                     ret,
                     WEXITSTATUS (ret),
                     command);
          if (chdir (previous_dir))
            g_warning ("%s: and chdir failed",
                       __func__);
          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",
                       __func__,
                       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",
                       __func__,
                       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. */

        gvm_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;
  int ret;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

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

#if ENABLE_AGENTS
  /* Agent Group. */
  ret = restore_agent_group (id);
  if (ret != 2)
    return ret;
#endif

  /* Port List. */
  ret = restore_port_list (id);
  if (ret != 2)
    return ret;

  /* Report Config. */
  ret = restore_report_config (id);
  if (ret != 2)
    return ret;

  /* Report Format. */
  ret = restore_report_format (id);
  if (ret != 2)
    return ret;

  /* Ticket. */
  ret = restore_ticket (id);
  if (ret != 2)
    return ret;

#if ENABLE_CONTAINER_SCANNING
  /* OCI Image Targets. */
  ret = restore_oci_image_target (id);
  if (ret != 2)
    return ret;
#endif /* ENABLE_CONTAINER_SCANNING */

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

      /* Check if it uses a scanner in the trashcan. */
      if (sql_int ("SELECT scanner_location = " G_STRINGIFY (LOCATION_TRASH)
                   " FROM configs_trash WHERE id = %llu;",
                   resource))
        {
          sql_rollback ();
          return 1;
        }

      sql ("INSERT INTO configs"
           " (uuid, owner, name, nvt_selector, comment, family_count,"
           "  nvt_count, families_growing, nvts_growing,"
           "  predefined, creation_time, modification_time, usage_type)"
           " SELECT uuid, owner, name, nvt_selector, comment, family_count,"
           "        nvt_count, families_growing, nvts_growing,"
           "        predefined, creation_time, modification_time, usage_type"
           " FROM configs_trash WHERE id = %llu;",
           resource);

      config = sql_last_insert_id ();

      sql ("INSERT INTO config_preferences"
           " (config, type, name, value, default_value, pref_nvt, pref_id,"
           "  pref_type, pref_name)"
           " SELECT %llu, type, name, value, default_value, pref_nvt, pref_id,"
           "        pref_type, pref_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,"
           "  active, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, event, condition, method,"
           "        filter, active, 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;
      GArray *affected_users;
      iterator_t users_iter;

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

      affected_users = g_array_new (TRUE, TRUE, sizeof (user_t));
      init_iterator (&users_iter,
                     "SELECT \"user\" FROM group_users"
                     " WHERE \"group\" = %llu",
                     group);
      while (next (&users_iter))
        {
          user_t user = iterator_int64 (&users_iter, 0);
          g_array_append_val (affected_users, user);
        }
      cleanup_iterator (&users_iter);
      cache_all_permissions_for_users (affected_users);
      g_array_free (affected_users, TRUE);

      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);
#if ENABLE_CONTAINER_SCANNING
      sql ("UPDATE oci_image_targets_trash"
           " SET credential_location = " G_STRINGIFY (LOCATION_TABLE) ","
           "     credential = %llu"
           " WHERE credential = %llu"
           " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           credential,
           resource);
#endif /* ENABLE_CONTAINER_SCANNING */

      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, result_nvt)"
           " SELECT uuid, owner, nvt, creation_time, modification_time, text,"
           "        hosts, port, severity, new_severity, task,"
           "        result, end_time, result_nvt"
           " 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, permission, LOCATION_TABLE);

      if (strcasecmp (name, "super") == 0)
        cache_all_permissions_for_users (NULL);
      else if (perm_resource != 0
               && resource_type && strcmp (resource_type, ""))
        cache_permissions_for_resource (resource_type, perm_resource, NULL);

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

  /* Role. */

  if (find_trash ("role", id, &resource))
    {
      sql_rollback ();
      return -1;
    }

  if (resource)
    {
      role_t role;
      GArray *affected_users;
      iterator_t users_iter;

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

      affected_users = g_array_new (TRUE, TRUE, sizeof (user_t));
      init_iterator (&users_iter,
                     "SELECT \"user\" FROM role_users"
                     " WHERE \"role\" = %llu",
                     role);
      while (next (&users_iter))
        {
          user_t user = iterator_int64 (&users_iter, 0);
          g_array_append_val (affected_users, user);
        }
      cleanup_iterator (&users_iter);
      cache_all_permissions_for_users (affected_users);
      g_array_free (affected_users, TRUE);

      sql_commit ();
      return 0;
    }

  /* Scanner. */

  if (find_trash ("scanner", id, &resource))
    {
      sql_rollback ();
      return -1;
    }

  if (resource)
    {
      scanner_t scanner;
      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 configs and tasks. */
      scanner = sql_last_insert_id ();

      sql ("UPDATE tasks"
           " SET scanner = %llu,"
           "     scanner_location = " G_STRINGIFY (LOCATION_TABLE)
           " WHERE scanner = %llu"
           " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH),
           scanner, 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,"
           "  byday, duration, timezone, creation_time,"
           "  modification_time, icalendar)"
           " SELECT uuid, owner, name, comment, first_time, period,"
           "        period_months, byday, duration, timezone,"
           "        creation_time, modification_time, icalendar"
           " 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)
    {
      tag_t restored_tag;

      sql ("INSERT INTO tags"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, resource_type, active, value)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, resource_type, active, value"
           " FROM tags_trash WHERE id = %llu;",
           resource);

      restored_tag = sql_last_insert_id ();

      sql ("INSERT INTO tag_resources"
           "  (tag, resource_type, resource, resource_uuid, resource_location)"
           " SELECT"
           "   %llu, resource_type, resource, resource_uuid, resource_location"
           " FROM tag_resources_trash WHERE tag = %llu;",
           restored_tag, resource);

      sql ("DELETE FROM tag_resources_trash WHERE tag = %llu",
           resource);

      permissions_set_locations ("tag", resource, restored_tag,
                                 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, allow_simultaneous_ips,"
           "  creation_time, modification_time)"
           " SELECT uuid, owner, name, hosts, exclude_hosts, comment,"
           "        port_list, reverse_lookup_only, reverse_lookup_unify,"
           "        alive_test, allow_simultaneous_ips,"
           "        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 tag_resources 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 tag_resources 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 tag_resources_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 tag_resources_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 ("INSERT INTO results"
           " (uuid, task, host, port, nvt, result_nvt, type, description,"
           "  report, nvt_version, severity, qod, qod_type, owner, date,"
           "  hostname, path)"
           " SELECT uuid, task, host, port, nvt, result_nvt, type,"
           "        description, report, nvt_version, severity, qod,"
           "         qod_type, owner, date, hostname, path"
           " FROM results_trash"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           resource);

      tickets_restore_task (resource);

      sql ("DELETE FROM results_trash"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           resource);

      /* For safety, in case anything recounted this report while it was in
       * the trash (which would have resulted in the wrong counts, because
       * the counting does not know about results_trash). */
      sql ("DELETE FROM report_counts"
           " WHERE report IN (SELECT id FROM reports WHERE task = %llu);",
           resource);

      sql ("UPDATE tasks SET hidden = 0 WHERE id = %llu;", resource);

      cache_permissions_for_resource ("task", resource, NULL);

      sql_commit ();
      return 0;
    }

  sql_rollback ();
  return 2;
}

/**
 * @brief Owner SQL for manage_empty_trash.
 */
#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 ()
{
  sql_begin_immediate ();

  if (acl_user_may ("empty_trashcan") == 0)
    {
      sql_rollback ();
      return 99;
    }
#if ENABLE_AGENTS
  empty_trashcan_agent_groups ();
#endif

  sql ("DELETE FROM nvt_selectors"
       " WHERE name != '" MANAGE_NVT_SELECTOR_UUID_ALL "'"
       " AND 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);
  empty_trashcan_tickets ();
  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);
#if ENABLE_CONTAINER_SCANNING
  sql ("DELETE FROM oci_image_targets_trash" WHERE_OWNER);
#endif /* ENABLE_CONTAINER_SCANNING */
  sql ("DELETE FROM overrides_trash" WHERE_OWNER);
  sql ("DELETE FROM permissions_trash" WHERE_OWNER);
  empty_trashcan_port_lists ();
  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 report_config_params_trash"
       " WHERE report_config IN (SELECT id FROM report_configs_trash"
       "                         WHERE owner = (SELECT id FROM users"
       "                                        WHERE uuid = '%s'));",
       current_credentials.uuid);
  sql ("DELETE FROM report_configs_trash" WHERE_OWNER);
  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);
  // Remove resource data of all trash tags of the current user.
  sql ("DELETE FROM tag_resources_trash"
       " WHERE tag IN (SELECT id FROM tags_trash"
       "                WHERE owner = (SELECT id FROM users"
       "                               WHERE uuid = '%s'));",
       current_credentials.uuid);
  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);
  // Remove all trash resources from all tags of the current user.
  sql ("DELETE FROM tag_resources"
       " WHERE tag IN (SELECT id FROM tags_trash"
       "                WHERE owner = (SELECT id FROM users"
       "                               WHERE uuid = '%s'))"
       "   AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
       current_credentials.uuid);

  /* Remove report formats last, because dir deletion can't be rolled back. */
  if (empty_trashcan_report_formats ())
    {
      sql_rollback ();
      return -1;
    }

  sql_commit ();
  return 0;
}


/* 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, 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_GLOBAL_OR_USER_OWNS () ""
                     " ORDER BY coalesce (owner, 0) DESC;",
                     resource,
                     current_credentials.uuid);
}

/**
 * @brief Return the Note/Override Excerpt Size user setting as an int.
 *
 * @return The excerpt size.
 */
int
setting_excerpt_size_int ()
{
  if (current_credentials.excerpt_size <= 0)
    return EXCERPT_SIZE_DEFAULT;
  return current_credentials.excerpt_size;
}

/**
 * @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.
 */
static 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.
 */
static int
setting_auto_cache_rebuild_int ()
{
  return sql_int ("SELECT coalesce"
                  "        ((SELECT value FROM settings"
                  "          WHERE uuid = '" SETTING_UUID_AUTO_CACHE_REBUILD "'"
                  "          AND " ACL_USER_OWNS () ""
                  "          ORDER BY coalesce (owner, 0) DESC LIMIT 1),"
                  "         '1');",
                  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, 0, NULL, NULL, NULL, NULL, NULL);

  quoted_uuid = uuid ? sql_quote (uuid) : NULL;
  columns = columns_build_select (select_columns);

  if (quoted_uuid)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM settings"
                   " WHERE uuid = '%s'"
                   " AND (owner = (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'))))",
                   columns,
                   quoted_uuid,
                   current_credentials.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 as a string.
 *
 * @param[in]   uuid   UUID of setting.
 * @param[out]  value  Freshly allocated value.
 *
 * @return 0 success, -1 error.
 */
int
setting_value_sql (const char *uuid, char **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_GLOBAL_OR_USER_OWNS () ";",
               quoted_uuid,
               current_credentials.uuid)
      == 0)
    {
      *value = NULL;
      g_free (quoted_uuid);
      return -1;
    }

  *value = sql_string
             ("SELECT value"
              " FROM settings"
              " WHERE uuid = '%s'"
              " AND " ACL_GLOBAL_OR_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 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_sql (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_GLOBAL_OR_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_GLOBAL_OR_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);
          if (g_utf8_validate (value, value_size, NULL) == FALSE)
            {
              if (r_errdesc)
                *r_errdesc = g_strdup ("Value cannot be decoded to"
                                       " valid UTF-8");
              g_free (value);
              return -1;
            }
        }
      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);
          if (g_utf8_validate (value, value_size, NULL) == FALSE)
            {
              if (r_errdesc)
                *r_errdesc = g_strdup ("Value cannot be decoded to"
                                       " valid UTF-8");
              g_free (value);
              return -1;
            }
        }
      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_AUTO_CACHE_REBUILD) == 0
               || strcmp (uuid, SETTING_UUID_AUTO_REFRESH) == 0
               || strcmp (uuid, SETTING_UUID_DEFAULT_SEVERITY) == 0
               || strcmp (uuid, SETTING_UUID_DYNAMIC_SEVERITY) == 0
               || strcmp (uuid, SETTING_UUID_EXCERPT_SIZE) == 0
               || strcmp (uuid, SETTING_UUID_PREFERRED_LANG) == 0
               || strcmp (uuid, SETTING_UUID_ROWS_PER_PAGE) == 0
               || strcmp (uuid, SETTING_UUID_USER_INTERFACE_DATE_FORMAT) == 0
               || strcmp (uuid, SETTING_UUID_USER_INTERFACE_TIME_FORMAT) == 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);
          if (g_utf8_validate (value, value_size, NULL) == FALSE)
            {
              if (r_errdesc)
                *r_errdesc = g_strdup ("Value cannot be decoded to"
                                       " valid UTF-8");
              g_free (value);
              return -1;
            }
        }
      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, SETTING_UUID_PREFERRED_LANG) == 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, SETTING_UUID_DYNAMIC_SEVERITY) == 0)
        {
          /* Dynamic Severity */
          current_credentials.dynamic_severity = atoi (value);
          reports_clear_count_cache (current_credentials.uuid);
        }

      if (strcmp (uuid, SETTING_UUID_EXCERPT_SIZE) == 0)
        {
          /* Note/Override Excerpt Size */
          current_credentials.excerpt_size = atoi (value);
        }

      if (strcmp (uuid, SETTING_UUID_DEFAULT_SEVERITY) == 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, SETTING_UUID_AUTO_CACHE_REBUILD) == 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;
            }
        }

      if (strcmp (uuid, SETTING_UUID_USER_INTERFACE_TIME_FORMAT) == 0)
        {
          /* User Interface Time Format */
          if (strcmp (value, "12") && strcmp (value, "24")
              && strcmp (value, "system_default"))
            {
              g_free (value);
              return 2;
            }
        }

      if (strcmp (uuid, SETTING_UUID_USER_INTERFACE_DATE_FORMAT) == 0)
        {
          /* User Interface Date Format */
          if (strcmp (value, "wmdy") && strcmp (value, "wdmy")
              && strcmp (value, "system_default"))
            {
              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, SETTING_UUID_FILE_DETAILS) == 0
          || strcmp (uuid, SETTING_UUID_FILE_LIST) == 0
          || strcmp (uuid, SETTING_UUID_FILE_REPORT) == 0))
    {
      gsize value_size;
      gchar *value, *quoted_value;

      assert (current_credentials.uuid);
      if (strcmp (uuid, SETTING_UUID_FILE_DETAILS) == 0)
        setting_name = "Details Export File Name";
      else if (strcmp (uuid, SETTING_UUID_FILE_LIST) == 0)
        setting_name = "List Export File Name";
      else if (strcmp (uuid, SETTING_UUID_FILE_REPORT) == 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);
          if (g_utf8_validate (value, value_size, NULL) == FALSE)
            {
              if (r_errdesc)
                *r_errdesc = g_strdup ("Value cannot be decoded to"
                                       " valid UTF-8");
              g_free (value);
              return -1;
            }
        }
      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, "b833a6f2-dcdc-4535-bfb0-a5154b5b5092") == 0)
        setting_name = g_strdup ("Alerts Filter");
#if ENABLE_AGENTS
      else if (strcmp (uuid, "391fc4f4-9f6c-4f0e-a689-37dd7d70d144") == 0)
        setting_name = g_strdup ("Agent Groups Filter");
      else if (strcmp (uuid, "c544a310-dc13-49c6-858e-f3160d75e221") == 0)
        setting_name = g_strdup ("Agents Filter");
      else if (strcmp (uuid, "a39a719a-e6bc-4d9f-a1e6-a53e5b014b05") == 0)
        setting_name = g_strdup ("Agent Installers Filter");
#endif
      else if (strcmp (uuid, "0f040d06-abf9-43a2-8f94-9de178b0e978") == 0)
        setting_name = g_strdup ("Assets Filter");
      else if (strcmp (uuid, "aaf1b63b-55a6-40ee-ae06-e8e50726f55a") == 0)
        setting_name = g_strdup ("Audits Filter");
      else if (strcmp (uuid, "45414da7-55f0-44c1-abbb-6b7d1126fbdf") == 0)
        setting_name = g_strdup ("Audit Reports 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, "f722e5a4-88d8-475f-95b9-e4dcafbc075b") == 0)
        setting_name = g_strdup ("Groups Filter");
      else if (strcmp (uuid, "37562dfe-1f7e-4cae-a7c0-fa95e6f194c5") == 0)
        setting_name = g_strdup ("Hosts Filter");
      else if (strcmp (uuid, "96abcd5a-9b6d-456c-80b8-c3221bfa499d") == 0)
        setting_name = g_strdup ("Notes Filter");
#if ENABLE_CONTAINER_SCANNING
      else if (strcmp (uuid, "db61a364-de40-4552-b1bc-a518744f847a") == 0)
        setting_name = g_strdup ("OCI Image Targets Filter");
#endif
      else if (strcmp (uuid, "f608c3ec-ce73-4ff6-8e04-7532749783af") == 0)
        setting_name = g_strdup ("Operating Systems 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 ("Policies Filter");
      else if (strcmp (uuid, "a17e1497-b27d-4389-9860-2f3b01dff9b2") == 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, "eca9738b-4339-4a3d-bd13-3c61173236ab") == 0)
        setting_name = g_strdup ("Report Configs 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, "ba00fe91-bdce-483c-b8df-2372e9774ad6") == 0)
        setting_name = g_strdup ("Scanners 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, "801544de-f06d-4377-bb77-bbb23369bad4") == 0)
        setting_name = g_strdup ("Tickets Filter");
      else if (strcmp (uuid, "34a176c1-0278-4c29-b84d-3d72117b2169") == 0)
        setting_name = g_strdup ("TLS Certificates Filter");
      else if (strcmp (uuid, "a33635be-7263-4549-bd80-c04d2dba89b4") == 0)
        setting_name = g_strdup ("Users Filter");
      else if (strcmp (uuid, "17c9d269-95e7-4bfa-b1b2-bc106a2175c7") == 0)
        setting_name = g_strdup ("Vulnerabilities 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, "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, "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");

      /* Content composer defaults */
      else if (strcmp (uuid, "b6b449ee-5d90-4ff0-af20-7e838c389d39") == 0)
        setting_name = g_strdup ("Report Composer Defaults");

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

      /* Audit Reports dashboard settings */
      else if (strcmp (uuid, "8083d77b-05bb-4b17-ab39-c81175cb512c") == 0)
        setting_name = g_strdup ("Audit 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");

      /* Vulns dashboard settings */
      else if (strcmp (uuid, "43690dcb-3174-4d84-aa88-58c1936c7f5c") == 0)
        setting_name = g_strdup ("Vulnerabilities Top Dashboard Configuration");

      /* Notes dashboard settings */
      else if (strcmp (uuid, "ce7b121-c609-47b0-ab57-fd020a0336f4a") == 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");

      /* TLS Certificate dashboard settings */
      else if (strcmp (uuid, "9b62bf16-bf90-11e9-ad97-28d24461215b") == 0)
        setting_name = g_strdup ("TLS Certificates 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");

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

      /*
       * Resilience / Remediation dashboards
       */

      /* Tickets */
      else if (strcmp (uuid, "70b0626f-a835-478e-8194-e09f97887a15") == 0)
        setting_name = g_strdup ("Tickets 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);
          if (g_utf8_validate (value, value_size, NULL) == FALSE)
            {
              if (r_errdesc)
                *r_errdesc = g_strdup ("Value cannot be decoded to"
                                       " valid UTF-8");
              g_free (value);
              return -1;
            }
        }
      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.
 *
 * @param[in]  max  Max.
 * @param[in]  ignore_max_rows_per_page  Whether to ignore "Max Rows Per Page"
 *
 * @return Adjusted max.
 */
int
manage_max_rows (int max, int ignore_max_rows_per_page)
{
  int max_rows;

  if (current_credentials.uuid == NULL
      || 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_AGENT_OWNER) == 0)
    return "Agent Owner";
  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";
  if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0)
    return "Debian LSC Package Maintainer";
  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0)
    return "Feed Import Owner";
  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
    return "Feed Import Roles";
  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    return "SecInfo SQL Buffer Threshold";
  if (strcmp (uuid, SETTING_UUID_CVE_CPE_MATCHING_VERSION) == 0)
    return "CVE-CPE Matching Version";

  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_AGENT_OWNER) == 0)
    return "User who is given ownership of new Agents.";
  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.";
  if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0)
    return "Maintainer email address used in generated Debian LSC packages.";
  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0)
    return "User who is given ownership of new resources from feed.";
  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
    return "Roles given access to new resources from feed.";
  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    return "Buffer size threshold in MiB for running buffered SQL statements"
           " in SecInfo updates before the end of the file being processed.";
  if (strcmp (uuid, SETTING_UUID_CVE_CPE_MATCHING_VERSION) == 0)
    return "Version of the CVE-CPE matching used in CVE scans.";

  return NULL;
}

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

  if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0)
    {
      if (g_regex_match_simple
            ("^([[:alnum:]\\-_]*@[[:alnum:]\\-_][[:alnum:]\\-_.]*)?$",
            value, 0, 0) == FALSE)
        return 1;
    }

  if ((strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0
     || (strcmp (uuid, SETTING_UUID_AGENT_OWNER) == 0 ))
    && strlen (value))
    {
      user_t value_user;
      gchar *quoted_uuid;

      quoted_uuid = sql_quote (value);
      switch (sql_int64 (&value_user,
                         "SELECT id FROM users WHERE uuid = '%s';",
                         quoted_uuid))
        {
          case 0:
            break;
          case 1:        /* Too few rows in result of query. */
            g_free (quoted_uuid);
            return 1;
          default:       /* Programming error. */
            assert (0);
          case -1:
            g_free (quoted_uuid);
            return 1;
        }
      g_free (quoted_uuid);
    }

  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
    {
      gchar **split, **point;

      point = split = g_strsplit (value, ",", 0);
      while (*point)
        {
          if (g_regex_match_simple ("^[-0123456789abcdefABCDEF]{36}$",
                                    g_strstrip (*point), 0, 0)
              == FALSE)
            {
              g_strfreev (split);
              return 1;
            }
          point++;
        }
      g_strfreev (split);
    }

  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    {
      int threshold;
      threshold = atoi (value);
      if (threshold < 0 || threshold > (INT_MAX / 1048576))
        return 1;
    }

  if (strcmp (uuid, SETTING_UUID_CVE_CPE_MATCHING_VERSION) == 0)
    {
      if (strcmp (value, "0") && strcmp (value, "1"))
        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);
    }

  if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0)
    {
      return g_strstrip (g_strdup (value));
    }

  if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
    {
      GString *normalised;
      gchar **split, **point;

      normalised = g_string_new ("");
      point = split = g_strsplit (value, ",", 0);

      while (*point)
        {
          g_string_append_printf (normalised,
                                  "%s%s",
                                  point == split ? "" : ",",
                                  g_strstrip (*point));
          point++;
        }

      g_strfreev (split);

      g_string_ascii_down (normalised);

      return g_string_free (normalised, FALSE);
    }

  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    {
      int threshold;
      threshold = atoi (value);
      if (threshold < 0)
        return NULL;
      return g_strdup_printf ("%i", threshold);
    }

  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 db_conn_info_t *database,
                       const gchar *name, const gchar *uuid, const char *value)
{
  int ret;
  gchar *quoted_name, *quoted_description, *quoted_value, *normalised;

  g_info ("   Modifying setting.");

  if (strcmp (uuid, SETTING_UUID_AGENT_OWNER)
      && strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT)
      && strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE)
      && strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER)
      && strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER)
      && strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES)
      && strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD)
      && strcmp (uuid, SETTING_UUID_CVE_CPE_MATCHING_VERSION))
    {
      fprintf (stderr, "Error in setting UUID.\n");
      return 3;
    }

  ret = manage_option_setup (log_config, database,
                             0 /* avoid_db_check_inserts */);
  if (ret)
    return ret;

  sql_begin_immediate ();

  if (setting_verify (uuid, value, name))
    {
      sql_rollback ();
      fprintf (stderr, "Syntax error in setting value.\n");
      manage_option_cleanup ();
      return 5;
    }

  if (name)
    {
      user_t user;

      if ((strcmp (uuid, SETTING_UUID_AGENT_OWNER) == 0)
          || (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
          || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0)
          || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
          || (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
          || (strcmp (uuid, SETTING_UUID_CVE_CPE_MATCHING_VERSION) == 0))
        {
          sql_rollback ();
          fprintf (stderr,
                   "Modifying this setting for a single user is forbidden.\n");
          manage_option_cleanup ();
          return 4;
        }

      if (find_user_by_name (name, &user))
        {
          sql_rollback ();
          fprintf (stderr, "Internal error.\n");
          manage_option_cleanup ();
          return -1;
        }

      if (user == 0)
        {
          sql_rollback ();
          fprintf (stderr, "Failed to find user.\n");
          manage_option_cleanup ();
          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);

          if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0)
            {
              migrate_predefined_configs ();
              migrate_predefined_port_lists ();
              if (migrate_predefined_report_formats ())
                {
                  sql_rollback ();
                  manage_option_cleanup ();
                  return -1;
                }
            }
        }
    }

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


/* Vulns. */

/**
 * @brief Filter columns for vuln iterator.
 */
#define VULN_ITERATOR_FILTER_COLUMNS                                         \
 {                                                                           \
   GET_ITERATOR_FILTER_COLUMNS, "results", "hosts", "severity",              \
   "qod", "oldest", "newest", "type", NULL                                   \
 }

/**
 * @brief Results SQL for VULN_ITERATOR_COLUMNS.
 */
#define VULN_RESULTS_WHERE                                                   \
     "  WHERE nvt = vulns.uuid"                                              \
     "    AND (opts.report IS NULL OR results.report = opts.report)"         \
     "    AND (opts.task IS NULL OR results.task = opts.task)"               \
     "    AND (opts.host IS NULL OR results.host = opts.host)"               \
     "    AND (results.severity != " G_STRINGIFY (SEVERITY_ERROR) ")"        \
     "    AND (SELECT has_permission FROM permissions_get_tasks"             \
     "         WHERE \"user\" = gvmd_user ()"                                \
     "           AND task = results.task)"

/**
 * @brief Vuln iterator columns.
 */
#define VULN_ITERATOR_COLUMNS                                                \
 {                                                                           \
   /* The following must match GET_ITERATOR_COLUMNS */                       \
   { "id", "id", KEYWORD_TYPE_INTEGER },                                     \
   { "uuid", "uuid", KEYWORD_TYPE_STRING },                                  \
   { "name", "name", KEYWORD_TYPE_STRING },                                  \
   { "''", "comment", KEYWORD_TYPE_STRING },                                 \
   { "creation_time", NULL, KEYWORD_TYPE_INTEGER },                          \
   { "modification_time", NULL, KEYWORD_TYPE_INTEGER },                      \
   { "creation_time", "created", KEYWORD_TYPE_INTEGER },                     \
   { "modification_time", "modified", KEYWORD_TYPE_INTEGER },                \
   { "cast (null AS text)", "_owner", KEYWORD_TYPE_INTEGER },                \
   { "''", "owner", KEYWORD_TYPE_STRING },                                   \
   /* Type specific columns */                                               \
   {                                                                         \
     "vuln_results.result_count",                                            \
     "results",                                                              \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT count(*) FROM"                                                 \
     "  (SELECT results.host FROM results"                                   \
     VULN_RESULTS_WHERE                                                      \
     "      GROUP BY results.host) AS hosts_subquery)",                      \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "severity", NULL, KEYWORD_TYPE_DOUBLE                                   \
   },                                                                        \
   {                                                                         \
     "qod", NULL, KEYWORD_TYPE_INTEGER                                       \
   },                                                                        \
   {                                                                         \
     "type", NULL, KEYWORD_TYPE_INTEGER                                      \
   },                                                                        \
   {                                                                         \
     "vuln_results.oldest",                                                  \
     "oldest",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "vuln_results.newest",                                                  \
     "newest",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Generate the extra_with string for a vuln iterator.
 *
 * @param[in]  task_id    UUID of the task to limit vulns to.
 * @param[in]  report_id  UUID of the report to limit vulns to.
 * @param[in]  host       IP address of the task to limit vulns to.
 *
 * @return Newly allocated string with the extra_with clause.
 */
static gchar*
vuln_iterator_extra_with (const gchar *task_id, const gchar *report_id,
                          const gchar *host)
{
  GString *ret;

  ret = g_string_new ("");

  g_string_append_printf (ret,
                          "vuln_results AS ("
                          " SELECT nvt, count(*) as result_count,"
                          "  min(date) AS oldest, max(date) AS newest"
                          "  FROM results"
                          "  WHERE severity != " G_STRINGIFY (SEVERITY_ERROR)
                          "    AND (" ACL_USER_OWNS ()
                          "         OR results.task IN"
                          "            (SELECT task FROM permissions_get_tasks"
                          "              WHERE \"user\" ="
                          "                (SELECT users.id FROM users WHERE"
                          "                 uuid = '%s')"
                          "                AND has_permission))",
                          current_credentials.uuid,
                          current_credentials.uuid);

  if (task_id && strcmp (task_id, ""))
    {
      task_t task = 0;
      find_task_with_permission (task_id, &task, "get_tasks");
      g_string_append_printf (ret, " AND results.task = %llu", task);
    }

  if (report_id && strcmp (report_id, ""))
    {
      report_t report = 0;
      find_report_with_permission (report_id, &report, "get_reports");
      g_string_append_printf (ret, " AND results.report = %llu", report);
    }

  if (host && strcmp (host, ""))
    {
      gchar *quoted_host = sql_quote (host);
      g_string_append_printf (ret, " AND results.host = '%s'", quoted_host);
      g_free (quoted_host);
    }

  g_string_append (ret, " GROUP BY nvt)");

  return g_string_free (ret, FALSE);
}

/**
 * @brief Generate the extra_with string for a vuln iterator using a filter.
 *
 * @param[in]  filter     The filter term to use.
 *
 * @return Newly allocated string with the extra_with clause.
 */
static gchar*
vuln_iterator_extra_with_from_filter (const gchar *filter)
{
  gchar *task_id, *report_id, *host;
  gchar *ret;

  if (filter)
    {
      task_id = filter_term_value (filter, "task_id");
      report_id = filter_term_value (filter, "report_id");
      host = filter_term_value (filter, "host");
    }
  else
    task_id = report_id = host = NULL;

  ret = vuln_iterator_extra_with (task_id, report_id, host);

  g_free (task_id);
  g_free (report_id);
  g_free (host);

  return ret;
}

/**
 * @brief Generate the extra_tables string for a vuln iterator.
 *
 * @param[in]  task_id    UUID of the task to limit vulns to.
 * @param[in]  report_id  UUID of the report to limit vulns to.
 * @param[in]  host       IP address of the task to limit vulns to.
 * @param[in]  min_qod    Minimum QoD.
 *
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
vuln_iterator_opts_table (const gchar *task_id, const gchar *report_id,
                          const gchar *host, int min_qod)
{
  GString *ret;

  ret = g_string_new (" JOIN vuln_results ON vuln_results.nvt = vulns.uuid,"
                      " (SELECT");

  if (task_id && strcmp (task_id, ""))
    {
      task_t task = 0;
      find_task_with_permission (task_id, &task, "get_tasks");
      g_string_append_printf (ret, " %llu AS task,", task);
    }
  else
    {
      g_string_append (ret, " cast (null AS integer) AS task,");
    }

  if (report_id && strcmp (report_id, ""))
    {
      report_t report = 0;
      find_report_with_permission (report_id, &report, "get_reports");
      g_string_append_printf (ret, " %llu AS report,", report);
    }
  else
    {
      g_string_append (ret, " cast (null AS integer) AS report,");
    }

  if (host && strcmp (host, ""))
    {
      gchar *quoted_host = sql_quote (host);
      g_string_append_printf (ret, " '%s' AS host,", quoted_host);
      g_free (quoted_host);
    }
  else
    {
      g_string_append (ret, " cast (null AS text) AS host,");
    }

  g_string_append_printf (ret, " %d AS min_qod) AS opts", min_qod);

  return g_string_free (ret, FALSE);
}

/**
 * @brief Generate the extra_tables string for a vuln iterator using a filter.
 *
 * @param[in]  filter     The filter term to use.
 *
 * @return Newly allocated string with the extra_tables clause.
 */
static gchar*
vuln_iterator_opts_from_filter (const gchar *filter)
{
  gchar *task_id, *report_id, *host;
  int min_qod;
  gchar *ret;

  if (filter)
    {
      task_id = filter_term_value (filter, "task_id");
      report_id = filter_term_value (filter, "report_id");
      host = filter_term_value (filter, "host");
    }
  else
    task_id = report_id = host = NULL;

  min_qod = filter_term_min_qod (filter);

  ret = vuln_iterator_opts_table (task_id, report_id, host, min_qod);

  g_free (task_id);
  g_free (report_id);
  g_free (host);

  return ret;
}

/**
 * @brief Initialise a vulnerability iterator, including observed vulns.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find vuln, 2 failed to find filter (filt_id),
 *         -1 error.
 */
int
init_vuln_iterator (iterator_t* iterator, const get_data_t *get)
{
  int ret;
  gchar *filter;
  gchar *extra_with, *extra_tables, *extra_where;
  static const char *filter_columns[] = VULN_ITERATOR_FILTER_COLUMNS;
  static column_t select_columns[] = VULN_ITERATOR_COLUMNS;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      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;

  extra_with
    = vuln_iterator_extra_with_from_filter (filter ? filter : get->filter);

  extra_tables
    = vuln_iterator_opts_from_filter (filter ? filter : get->filter);

  extra_where
    = vulns_extra_where (filter_term_min_qod (filter ? filter : get->filter));

  ret = init_get_iterator2_with (iterator,
                                 "vuln",
                                 get,
                                 select_columns,
                                 NULL, /* trash_select_columns */
                                 NULL, /* where_columns */
                                 NULL, /* trash_where_columns */
                                 filter_columns,
                                 0     /* distinct */,
                                 extra_tables, /* extra_tables */
                                 extra_where,  /* extra_where */
                                 NULL, /* extra_where_single */
                                 0,    /* owned */
                                 0,    /* ignore_id */
                                 NULL, /* extra_order */
                                 extra_with,
                                 0,    /* acl_with_optional */
                                 0);   /* assume_permitted */

  g_free (extra_with);
  g_free (extra_tables);
  g_free (extra_where);

  return ret;
}

/**
 * @brief Get the number of results from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The number of results.
 */
int
vuln_iterator_results (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 0);
}

/**
 * @brief Get the number of hosts from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The number of hosts.
 */
int
vuln_iterator_hosts (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the severity from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity.
 */
double
vuln_iterator_severity (iterator_t* iterator)
{
  if (iterator->done) return SEVERITY_MISSING;
  return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
}

/**
 * @brief Get the QoD from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The QoD.
 */
int
vuln_iterator_qod (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
}

/**
 * @brief Get the QoD from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The QoD.
 */
const char*
vuln_iterator_type (iterator_t* iterator)
{
  if (iterator->done) return NULL;
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the date of the oldest result from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The oldest result date.
 */
time_t
vuln_iterator_oldest (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5);
}

/**
 * @brief Get the date of the oldest result from a vuln iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The oldest result date.
 */
time_t
vuln_iterator_newest (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6);
}

/**
 * @brief Count number of vulns.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of vulns in filtered set.
 */
int
vuln_count (const get_data_t *get)
{
  gchar *filter;
  static const char *filter_columns[] = VULN_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = VULN_ITERATOR_COLUMNS;
  gchar *extra_with, *extra_tables, *extra_where;
  int ret;

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      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;

  extra_with
    = vuln_iterator_extra_with_from_filter (filter ? filter : get->filter);

  extra_tables
    = vuln_iterator_opts_from_filter (filter ? filter : get->filter);

  extra_where
    = vulns_extra_where (filter_term_min_qod (filter ? filter : get->filter));

  ret = count2 ("vuln", get, columns, NULL /*trash_columns*/, NULL, NULL,
                filter_columns, 0, extra_tables, extra_where, extra_with,
                FALSE);

  g_free (filter);
  g_free (extra_with);
  g_free (extra_tables);
  g_free (extra_where);

  return ret;
}

/**
 * @brief Extra WHERE clause for vulns.
 *
 * @param[in]  min_qod  Min QOD.
 *
 * @return WHERE clause.
 */
static gchar*
vulns_extra_where (int min_qod)
{
  return g_strdup_printf (" AND (vulns.qod >= %d)",
                          min_qod);
}

/**
 * @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.
 * @param[out]  ldaps_only    Whether to try LDAPS auth only.
 */
void
manage_get_ldap_info (int *enabled, gchar **host, gchar **authdn,
                      int *plaintext, gchar **cacert, int *ldaps_only)
{
  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';");

  if (sql_int ("SELECT count(*) FROM meta WHERE name = 'ldap_ldaps_only';"))
    *ldaps_only = sql_int ("SELECT value FROM meta"
                             " WHERE name = 'ldap_ldaps_only';");
  else
    *ldaps_only = 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.
 * @param[in]  ldaps_only       Whether to try LDAPS auth only, -1 to
 *                              keep current value.
 *
 * @return 0 success, -1 error.
 */
int
manage_set_ldap_info (int enabled, gchar *host, gchar *authdn,
                      int allow_plaintext, gchar *cacert, int ldaps_only)
{
  int err = 0;
  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)
    {
      /* check if the certificate is valid */
      err = get_certificate_info (cacert,
                                  -1,
                                  TRUE,
                                  NULL,   /* activation_time */
                                  NULL,   /* expiration_time */
                                  NULL,   /* md5_fingerprint */
                                  NULL,   /* sha256_fingerprint */
                                  NULL,   /* subject */
                                  NULL,   /* issuer */
                                  NULL,   /* serial */
                                  NULL);  /* certificate_format */

      if (!err)
        {
          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);
        }
    }

  if (ldaps_only >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_ldaps_only';");
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_ldaps_only', %i);",
           ldaps_only);
    }

  sql_commit ();
  return err;
}

/**
 * @brief Gets the current RADIUS secret key.
 *
 * @param[out] is_encrypted  Whether the key is encrypted. NULL to ignore.
 *
 * @return Freshly allocated RADIUS secret key.
 */
char *
get_radius_key (gboolean *is_encrypted)
{
  gchar *key = NULL, *secret;

  if (sql_int64_0 ("SELECT value FROM meta"
                   " WHERE name = 'radius_key_is_unencrypted'"))
    {
      if (is_encrypted)
        *is_encrypted = FALSE;
      key = sql_string ("SELECT value FROM meta"
                        " WHERE name = 'radius_key';");
    }
  else
    {
      if (is_encrypted)
        *is_encrypted = TRUE;
      secret = sql_string ("SELECT value FROM meta"
                           " WHERE name = 'radius_key';");
      if (!secret)
        key = strdup ("");
      else
        {
          const char *decrypted;
          lsc_crypt_ctx_t crypt_ctx;
          char *encryption_key_uid = current_encryption_key_uid (TRUE);
          crypt_ctx = lsc_crypt_new (encryption_key_uid);
          free (encryption_key_uid);
          decrypted = lsc_crypt_decrypt (crypt_ctx, secret, "secret_key");
          if (decrypted)
            key = strdup (decrypted);
          else
            key = strdup ("");
          lsc_crypt_release (crypt_ctx);
          g_free (secret);
        }
    }

  return key;
}

/**
 * @brief Set RADIUS key.
 *
 * @param[in]  key        Secret key.
 * @param[in]  encrypt    Whether to encrypt the key.
 */
void
set_radius_key (const char *key, gboolean encrypt)
{
  char *quoted;
  char *secret;
  lsc_crypt_ctx_t crypt_ctx;
  char *encryption_key_uid = current_encryption_key_uid (TRUE);
  crypt_ctx = lsc_crypt_new (encryption_key_uid);
  free (encryption_key_uid);

  if (encrypt == FALSE)
    {
      quoted = sql_quote (key);
      sql ("INSERT INTO meta (name, value)"
            " VALUES ('radius_key', '%s')"
            " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;",
            quoted);
      sql ("INSERT INTO meta (name, value)"
            " VALUES ('radius_key_is_unencrypted', '1')"
            " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;");
    }
  else
    {
      secret = lsc_crypt_encrypt (crypt_ctx, "secret_key", key, NULL);
      if (secret)
        {
          quoted = sql_quote (secret);
          sql ("INSERT INTO meta (name, value)"
                " VALUES ('radius_key', '%s')"
                " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;",
                quoted);
          g_free (secret);
          secret = NULL;
          g_free (quoted);
        }
      lsc_crypt_release(crypt_ctx);
      sql ("INSERT INTO meta (name, value)"
            " VALUES ('radius_key_is_unencrypted', '0')"
            " ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;");
    }
}

/**
 * @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 = get_radius_key (NULL);
}

/**
 * @brief Set RADIUS info.
 *
 * @param[in]  enabled    Whether RADIUS is enabled. -1 to keep current value.
 * @param[in]  host       RADIUS host. NULL to keep current value.
 * @param[in]  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 && strlen (key))
    {
      set_radius_key (key, disable_encrypted_credentials == FALSE);
    }

  sql_commit ();
}


/* Tags */

/**
 * @brief Add a resource to a tag.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource Type.
 * @param[in]  uuid        The resource UUID.
 * @param[in]  resource    The resource row id.
 * @param[in]  location    Whether the resource is in the trashcan.
 *
 * @return  0 success, -1 error
 */
static int
tag_add_resource (tag_t tag, const char *type, const char *uuid,
                  resource_t resource, int location)
{
  int already_added, ret;
  gchar *quoted_resource_uuid;

  ret = 0;

  quoted_resource_uuid = uuid ? sql_insert (uuid) : g_strdup ("''");

  if (type_is_info_subtype (type))
    already_added = sql_int ("SELECT count(*) FROM tag_resources"
                             " WHERE resource_type = '%s'"
                             " AND resource_uuid = %s"
                             " AND tag = %llu",
                             type, quoted_resource_uuid, tag);
  else
    already_added = sql_int ("SELECT count(*) FROM tag_resources"
                             " WHERE resource_type = '%s'"
                             " AND resource = %llu"
                             " AND resource_location = %d"
                             " AND tag = %llu",
                             type, resource, location, tag);

  if (already_added == 0)
    {
      g_debug ("%s - adding %s %s", __func__, type, uuid);
      sql ("INSERT INTO tag_resources"
           " (tag, resource_type, resource, resource_uuid, resource_location)"
           " VALUES (%llu, '%s', %llu, %s, %d)",
           tag, type, resource, quoted_resource_uuid, location);
    }
  else
    {
      g_debug ("%s - skipping %s %s", __func__, type, uuid);
    }

  g_free (quoted_resource_uuid);

  return ret;
}

/**
 * @brief Find a resource by UUID and add it as a tag resource.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource type.
 * @param[in]  tag_type    The tag type. Could be a sub-type.
 * @param[in]  uuid        The resource UUID.
 * @param[in]  permission  The permission required to get the resource.
 *
 * @return 0 success, -1 error, 1 resource not found.
 */
static int
tag_add_resource_uuid (tag_t tag,
                       const char *type,
                       const char *tag_type,
                       const char *uuid,
                       const char *permission)
{
  int resource_location = LOCATION_TABLE;
  resource_t resource;

  if (find_resource_with_permission (type, uuid,
                                     &resource, permission, 0))
    {
      g_warning ("%s: Failed to find %s %s",
                 __func__, type, uuid);
      return -1;
    }
  else if (resource == 0
           && type_has_trash (type))
    {
      if (find_resource_with_permission (type, uuid,
                                         &resource, permission,
                                         1))
        {
          g_warning ("%s: Failed to find trash %s %s",
                     __func__, type, uuid);
          return -1;
        }
      else if (resource != 0)
        resource_location = LOCATION_TRASH;
    }

  if (resource == 0)
    return 1;

  if ((strcmp (type, "task") == 0)
      || (strcmp (type, "config") == 0)
      || (strcmp (type, "report") == 0))
    {
      gchar *usage_type;
      if (strcmp (type, "report"))
        usage_type = sql_string ("SELECT usage_type FROM %ss WHERE id = %llu",
                                 type, resource);
      else
        {
          task_t task;
          if (report_task (resource, &task))
            return -1;

          usage_type = sql_string ("SELECT usage_type FROM tasks WHERE id = %llu",
                                   task);
        }

      if (usage_type == NULL)
        return -1;

      int same_type = (strcmp (tag_type, type) == 0);

      if (same_type && ((strcmp (usage_type, "audit") == 0)
                        || (strcmp (usage_type, "policy") == 0)))
        {
          g_free (usage_type);
          return 1;
        }
      if (!same_type && (strcmp (usage_type, "scan") == 0))
        {
          g_free (usage_type);
          return 1;
        }
      g_free (usage_type);
    }

  return tag_add_resource (tag, type, uuid, resource, resource_location);
}

/**
 * @brief Find resources from an array by UUID and insert as a tag resource.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource type.
 * @param[in]  uuids       The array of resource UUIDs.
 * @param[out] error_extra Extra error output. Contains UUID if not found.
 *
 * @return 0 success, -1 error, 1 resource not found.
 */
static int
tag_add_resources_list (tag_t tag, const char *type, array_t *uuids,
                        gchar **error_extra)
{
  gchar *resource_permission, *current_uuid;
  int index;

  gchar *resource_type = g_strdup (type);

  if (type_is_info_subtype (type))
    resource_permission = g_strdup ("get_info");
  else if (type_is_asset_subtype (type))
    resource_permission = g_strdup ("get_assets");
  else if (type_is_report_subtype (type))
    {
      resource_permission = g_strdup ("get_reports");
      g_free (resource_type);
      resource_type = g_strdup ("report");
    }
  else if (type_is_task_subtype (type))
    {
      resource_permission = g_strdup ("get_tasks");
      g_free (resource_type);
      resource_type = g_strdup ("task");
    }
  else if (type_is_config_subtype (type))
    {
      resource_permission = g_strdup ("get_configs");
      g_free (resource_type);
      resource_type = g_strdup ("config");
    }
  else
    resource_permission = g_strdup_printf ("get_%ss", type);

  index = 0;
  while ((current_uuid = g_ptr_array_index (uuids, index++)))
    {
      int ret;

      ret = tag_add_resource_uuid (tag, resource_type, type, current_uuid,
                                   resource_permission);
      if (ret)
        {
          g_free (resource_permission);
          g_free (resource_type);
          if (error_extra)
            *error_extra = g_strdup (current_uuid);
          return ret;
        }
    }
  g_free (resource_permission);
  g_free (resource_type);

  return 0;
}

/**
 * @brief Find resources using a filter and insert as tag resources.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource type.
 * @param[in]  filter      The filter to select resources with.
 *
 * @return 0 success, -1 error, 1 resource not found, 2 no resources returned.
 */
static int
tag_add_resources_filter (tag_t tag, const char *type, const char *filter)
{
  iterator_t resources;
  gchar *filtered_select;
  get_data_t resources_get;
  int ret;

  memset (&resources_get, '\0', sizeof (resources_get));
  resources_get.filter = g_strdup (filter);
  resources_get.filt_id = FILT_ID_NONE;
  resources_get.trash = LOCATION_TABLE;
  resources_get.type = g_strdup (type);
  resources_get.ignore_max_rows_per_page = 1;
  filtered_select = NULL;

  if (strcasecmp (type, "TICKET") == 0)
    {
      /* TODO This is how it should be done for all types, in order
       * to contain each per-resource implementation in its own file. */
      if (init_ticket_iterator (&resources, &resources_get))
        {
          g_warning ("%s: Failed to build filter SELECT", __func__);
          sql_rollback ();
          g_free (resources_get.filter);
          g_free (resources_get.type);
          return -1;
        }
    }
  else
    {
      if (strcasecmp (type, "task") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }
      else if (strcasecmp (type, "audit") == 0)
        {
          type = g_strdup ("task");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("audit"));
        }
      else if (strcasecmp (type, "policy") == 0)
        {
          type = g_strdup ("config");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("policy"));
        }
      else if (strcasecmp (type, "config") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }
      else if (strcasecmp (type, "audit_report") == 0)
        {
          type = g_strdup ("report");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("audit"));
        }
      else if (strcasecmp (type, "report") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }

      gchar *columns;

      columns = g_strdup_printf ("%ss.id, %ss.uuid", type, type);
      switch (type_build_select (type,
                                 columns,
                                 &resources_get, 0, 1, NULL, NULL, NULL,
                                 &filtered_select))
        {
          case 0:
            g_free (columns);
            if (sql_int ("SELECT count(*) FROM (%s) AS filter_selection",
                         filtered_select) == 0)
              {
                g_free (filtered_select);
                return 2;
              }

            init_iterator (&resources,
                           "%s",
                           filtered_select);

            break;
          default:
            g_free (columns);
            g_warning ("%s: Failed to build filter SELECT", __func__);
            sql_rollback ();
            g_free (resources_get.filter);
            g_free (resources_get.type);
            if (resources_get.extra_params)
              g_hash_table_destroy (resources_get.extra_params);
            return -1;
        }
    }

  g_free (resources_get.filter);
  g_free (resources_get.type);
  if (resources_get.extra_params)
    g_hash_table_destroy (resources_get.extra_params);

  ret = 2;
  while (next (&resources))
    {
      resource_t resource;
      const char *current_uuid;
      int add_ret;

      resource = iterator_int64 (&resources, 0);
      current_uuid = iterator_string (&resources, 1);

      add_ret = tag_add_resource (tag, type, current_uuid, resource,
                                  LOCATION_TABLE);
      if (add_ret)
        {
          ret = add_ret;
          break;
        }
      ret = 0;
    }
  cleanup_iterator (&resources);

  g_free (filtered_select);

  return ret;
}

/**
 * @brief Remove resources from a tag using a UUIDs array.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource type.
 * @param[in]  uuids       The array of resource UUIDs.
 * @param[out] error_extra Extra error output. Contains UUID if not found.
 *
 * @return 0 success, -1 error, 1 resource not found.
 */
static int
tag_remove_resources_list (tag_t tag, const char *type, array_t *uuids,
                           gchar **error_extra)
{
  gchar *current_uuid;
  int index;

  index = 0;
  while ((current_uuid = g_ptr_array_index (uuids, index++)))
    {
      gchar *uuid_escaped = g_markup_escape_text (current_uuid, -1);

      if (sql_int ("SELECT count(*) FROM tag_resources"
                   " WHERE tag = %llu AND resource_uuid = '%s'",
                   tag, uuid_escaped) == 0)
        {
          if (error_extra)
            *error_extra = g_strdup (current_uuid);
          g_free (uuid_escaped);
          return 1;
        }

      sql ("DELETE FROM tag_resources"
           " WHERE tag = %llu AND resource_uuid = '%s'",
           tag, uuid_escaped);
      g_free (uuid_escaped);
    }

  return 0;
}

/**
 * @brief Remove resources from a tag using a filter.
 *
 * @param[in]  tag         Tag to attach to the resource.
 * @param[in]  type        The resource type.
 * @param[in]  filter      The filter to select resources with.
 *
 * @return 0 success, -1 error, 1 resource not found, 2 no resources returned.
 */
static int
tag_remove_resources_filter (tag_t tag, const char *type, const char *filter)
{
  iterator_t resources;
  gchar *iterator_select;
  get_data_t resources_get;
  int ret;

  memset (&resources_get, '\0', sizeof (resources_get));
  resources_get.filter = g_strdup (filter);
  resources_get.filt_id = FILT_ID_NONE;
  resources_get.trash = LOCATION_TABLE;
  resources_get.type = g_strdup (type);
  resources_get.ignore_max_rows_per_page = 1;

  iterator_select = NULL;

  if (strcasecmp (type, "TICKET") == 0)
    {
      /* TODO This is how it should be done for all types, in order
       * to contain each per-resource implementation in its own file. */
      if (init_ticket_iterator (&resources, &resources_get))
        {
          g_warning ("%s: Failed to init ticket iterator", __func__);
          sql_rollback ();
          g_free (resources_get.filter);
          g_free (resources_get.type);
          return -1;
        }
    }
  else
    {
      if (strcasecmp (type, "task") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }
      else if (strcasecmp (type, "audit") == 0)
        {
          type = g_strdup ("task");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("audit"));
        }
      else if (strcasecmp (type, "policy") == 0)
        {
          type = g_strdup ("config");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("policy"));
        }
      else if (strcasecmp (type, "config") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }
      else if (strcasecmp (type, "audit_report") == 0)
        {
          type = g_strdup ("report");
          resources_get.type = g_strdup (type);
          get_data_set_extra (&resources_get,
                              "usage_type",
                              g_strdup ("audit"));
        }
      else if (strcasecmp (type, "report") == 0)
        {
          get_data_set_extra (&resources_get, "usage_type", g_strdup ("scan"));
        }

      gchar *columns;

      columns = g_strdup_printf ("%ss.id", type);
      switch (type_build_select (type,
                                 columns,
                                 &resources_get, 0, 1, NULL, NULL, NULL,
                                 &iterator_select))
        {
          case 0:
            g_free (columns);
            init_iterator (&resources, "%s", iterator_select);
            break;
          default:
            g_free (columns);
            g_warning ("%s: Failed to build filter SELECT", __func__);
            sql_rollback ();
            g_free (resources_get.filter);
            g_free (resources_get.type);
            if (resources_get.extra_params)
              g_hash_table_destroy (resources_get.extra_params);
            return -1;
        }
    }

  g_free (resources_get.filter);
  g_free (resources_get.type);
  if (resources_get.extra_params)
      g_hash_table_destroy (resources_get.extra_params);

  ret = 2;
  while (next (&resources))
    {
      resource_t resource;

      resource = iterator_int64 (&resources, 0);

      ret = 0;
      sql ("DELETE FROM tag_resources"
           " WHERE tag = %llu"
           " AND resource = %llu"
           " AND resource_location = %d",
           tag, resource, resources_get.trash);
    }
  cleanup_iterator (&resources);

  g_free (iterator_select);

  return ret;
}

/**
 * @brief Find a tag for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of tag.
 * @param[out]  tag         Tag return, 0 if successfully failed to find tag.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find tag), TRUE on error.
 */
static 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_return  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)
{
  int ret = 0;
  tag_t new_tag, old_tag;

  ret = copy_resource ("tag", name, comment, tag_id,
                       "value, resource_type, active",
                       1, &new_tag, &old_tag);

  if (ret)
    return ret;

  if (new_tag_return)
    *new_tag_return = new_tag;

  sql ("INSERT INTO tag_resources"
       " (tag, resource_type, resource, resource_uuid, resource_location)"
       " SELECT"
       "  %llu, resource_type, resource, resource_uuid, resource_location"
       "   FROM tag_resources"
       "  WHERE tag = %llu",
       new_tag, old_tag);

  return 0;
}

/**
 * @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_uuids   Unique IDs of the resource to attach the tag to.
 * @param[in]  resources_filter Filter to select resources to attach tag to.
 * @param[in]  active        0 for inactive, NULL or any other value for active.
 * @param[out] tag          Created tag.
 * @param[out] error_extra  Extra string for error (e.g. missing resource ID)
 *
 * @return 0 success, 1 resource ID not found (sets error_extra to UUID),
 *   2 filter returned no results, 3 too many resources selected,
 *   99 permission denied, -1 error.
 */
int
create_tag (const char * name, const char * comment, const char * value,
            const char * resource_type, array_t * resource_uuids,
            const char * resources_filter, const char * active, tag_t * tag,
            gchar **error_extra)
{
  gchar *quoted_name, *quoted_comment, *quoted_value;
  gchar *lc_resource_type, *quoted_resource_type;
  tag_t new_tag;

  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)
    {
      if (!valid_subtype (lc_resource_type))
        {
          g_free (lc_resource_type);
          sql_rollback ();
          return -1;
        }
    }

  quoted_name = sql_insert (name);
  quoted_resource_type = sql_insert (lc_resource_type);

  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, active)"
      " VALUES"
      " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s'),"
      "  %i, %i, %s, %s, %s, %s, %i);",
      current_credentials.uuid,
      time (NULL),
      time (NULL),
      quoted_name,
      quoted_comment,
      quoted_value,
      quoted_resource_type,
      active
       ? (strcmp (active, "0") == 0
           ? 0
           : 1)
       : 1);

  new_tag = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_comment);
  g_free (quoted_value);
  g_free (quoted_resource_type);

  /* Handle resource IDs */
  if (resource_uuids)
    {
      int ret;
      ret = tag_add_resources_list (new_tag, lc_resource_type, resource_uuids,
                                    error_extra);

      if (ret)
        {
          // Assume tag_add_resources_list return codes match
          sql_rollback ();
          g_free (lc_resource_type);
          return ret;
        }
    }

  /* Handle filter */
  if (resources_filter && strcmp (resources_filter, ""))
    {
      int ret;
      ret = tag_add_resources_filter (new_tag, lc_resource_type,
                                      resources_filter);

      if (ret)
        {
          // Assume tag_add_resources_list return codes match
          sql_rollback ();
          g_free (lc_resource_type);
          return ret;
        }
    }

  g_free (lc_resource_type);

  if (tag)
    *tag = new_tag;

  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 tag_resources_trash WHERE tag = %llu", tag);
      sql ("DELETE FROM tags_trash WHERE id = %llu;", tag);
      sql_commit ();
      return 0;
    }

  if (ultimate == 0)
    {
      tag_t trash_tag;

      sql ("INSERT INTO tags_trash"
           " (uuid, owner, name, comment, creation_time,"
           "  modification_time, resource_type, active, value)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time, resource_type, active, value"
           " FROM tags WHERE id = %llu;",
           tag);

      trash_tag = sql_last_insert_id ();

      sql ("INSERT INTO tag_resources_trash"
           "  (tag, resource_type, resource, resource_uuid, resource_location)"
           " SELECT"
           "   %llu, resource_type, resource, resource_uuid, resource_location"
           " FROM tag_resources WHERE tag = %llu;",
           trash_tag, tag);

      permissions_set_locations ("tag", tag, trash_tag, LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("tag", tag, LOCATION_TABLE);
      tags_remove_resource ("tag", tag, LOCATION_TABLE);
    }

  sql ("DELETE FROM tag_resources WHERE tag = %llu", tag);
  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_uuids    New Unique IDs of the resources to attach.
 * @param[in]  resources_filter  Filter to select resources to attach tag to.
 * @param[in]  resources_action  Resources action, e.g. "add" or "remove".
 * @param[in]  active            0 for inactive, any other for active or NULL.
 * @param[out] error_extra  Extra string for error (e.g. missing resource ID)
 *
 * @return 0 success, 1 failed to find tag, 2 tag_id required,
 *         3 unexpected resource action,
 *         4 resource ID not found (sets error_extra to UUID),
 *         5 filter returned no results, 6 too many resources selected,
 *         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,
            array_t *resource_uuids, const char *resources_filter,
            const char *resources_action, const char *active,
            gchar **error_extra)
{
  gchar *quoted_name, *quoted_comment, *quoted_value;
  gchar *lc_resource_type, *quoted_resource_type;
  tag_t tag;
  gchar *current_resource_type;

  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)
    {
      if (!valid_subtype (lc_resource_type))
        {
          sql_rollback ();
          return -1;
        }
    }

  quoted_resource_type = sql_insert (lc_resource_type);
  quoted_name = sql_insert (name ? name : "");
  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 (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 (quoted_resource_type);
  g_free (quoted_comment);
  g_free (quoted_value);

  current_resource_type = sql_string ("SELECT resource_type"
                                      " FROM tags"
                                      " WHERE id = %llu",
                                      tag);

  /* Clear old resources */
  if (resources_action == NULL
      || strcmp (resources_action, "") == 0
      || strcmp (resources_action, "set") == 0)
    {
      if (resource_uuids
          || (resources_filter && strcmp (resources_filter, "")))
        {
          sql ("DELETE FROM tag_resources WHERE tag = %llu", tag);
        }
    }
  else if (strcmp (resources_action, "add")
           && strcmp (resources_action, "remove"))
    {
      sql_rollback ();
      g_free (current_resource_type);
      g_free (lc_resource_type);
      return 3;
    }

  /* Handle resource IDs */
  if (resource_uuids)
    {
      int ret;

      if (resources_action && strcmp (resources_action, "remove") == 0)
        ret = tag_remove_resources_list (tag, current_resource_type,
                                         resource_uuids, error_extra);
      else
        ret = tag_add_resources_list (tag, current_resource_type,
                                      resource_uuids, error_extra);

      if (ret)
        {
          sql_rollback ();
          g_free (current_resource_type);
          g_free (lc_resource_type);
          // Assume return codes besides -1 are offset from create_tag
          if (ret > 0)
            return ret + 3;
          else
            return ret;
        }
    }

  /* Handle filter */
  if (resources_filter && strcmp (resources_filter, ""))
    {
      int ret;

      if (resources_action && strcmp (resources_action, "remove") == 0)
        ret = tag_remove_resources_filter (tag, current_resource_type,
                                           resources_filter);
      else
        ret = tag_add_resources_filter (tag, current_resource_type,
                                        resources_filter);

      if (ret)
        {
          // Assume tag_add_resources_list return codes match
          sql_rollback ();
          g_free (current_resource_type);
          g_free (lc_resource_type);
          // Assume return codes besides -1 are offset from create_tag
          if (ret > 0)
            return ret + 3;
          else
            return ret;
        }
    }

  g_free (current_resource_type);
  g_free (lc_resource_type);

  sql_commit ();

  return 0;
}


/**
 * @brief Filter columns for Tag iterator.
 */
#define TAG_ITERATOR_FILTER_COLUMNS                                           \
 { GET_ITERATOR_FILTER_COLUMNS, "resource_type", "active", "value",           \
   "resources", NULL }

/**
 * @brief Tag iterator columns.
 */
#define TAG_ITERATOR_COLUMNS                                                  \
 {                                                                           \
   GET_ITERATOR_COLUMNS (tags),                                              \
   { "resource_type", NULL, KEYWORD_TYPE_STRING },                           \
   { "active", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { "value", NULL, KEYWORD_TYPE_STRING },                                   \
   { "tag_resources_count (tags.id, tags.resource_type)",                    \
     "resources", KEYWORD_TYPE_INTEGER },                                    \
   { 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 },                           \
   { "active", NULL, KEYWORD_TYPE_INTEGER },                                 \
   { "value", NULL, KEYWORD_TYPE_STRING },                                   \
   { "tag_resources_trash_count (tags_trash.id, tags_trash.resource_type)",  \
     "resources", KEYWORD_TYPE_INTEGER },                                    \
   { 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, 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 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 + 1);
  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 + 2);

/**
 * @brief Get number of resources linked to tag.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Count of resources linked to tag.
 */
int
tag_iterator_resources (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
  return ret;
}

/**
 * @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, 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, *with_clause;
  const char *parent_type;

  assert (type);
  assert (resource);
  assert (current_credentials.uuid);

  get.trash = 0;
  owned_clause = acl_where_owned ("tag", &get, 1, "any", 0, NULL, 0,
                                  &with_clause);

  if (type_is_report_subtype (type))
    parent_type = "report";
  else if (type_is_task_subtype (type))
    parent_type = "task";
  else if (type_is_config_subtype (type))
    parent_type = "config";
  else
    parent_type = type;

  init_iterator (iterator,
                 "%s"
                 " SELECT id, uuid, name, value, comment"
                 " FROM tags"
                 " WHERE resource_type = '%s'"
                 " AND EXISTS"
                 "  (SELECT * FROM tag_resources"
                 "   WHERE resource_type = '%s'"
                 "   AND resource = %llu"
                 "   AND resource_location = %d"
                 "   AND tag = tags.id)"
                 "%s"
                 " AND %s"
                 " ORDER BY %s %s;",
                 with_clause ? with_clause : "",
                 type,
                 parent_type,
                 resource,
                 LOCATION_TABLE,
                 active_only ? " AND active=1" : "",
                 owned_clause,
                 sort_field ? sort_field : "active DESC, name",
                 ascending ? "ASC" : "DESC");

  g_free (with_clause);
  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 Check if there are 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 1 if resource has tags, else 0.
 */
int
resource_tag_exists (const char* type, resource_t resource, int active_only)
{
  int ret;

  assert (type);
  assert (resource);

  ret = sql_int ("SELECT EXISTS (SELECT *"
                 "               FROM tags"
                 "               WHERE resource_type = '%s'"
                 "               AND EXISTS"
                 "                   (SELECT * FROM tag_resources"
                 "                    WHERE tag = tags.id"
                 "                    AND resource = %llu"
                 "                    AND resource_location = %d"
                 "                    AND tags.resource_type"
                 "                        = tag_resources.resource_type)"
                 "               %s);",
                 type,
                 resource,
                 LOCATION_TABLE,
                 active_only ? "AND active=1": "");

  return ret;
}

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

  assert (type);
  assert (resource);

  if (type_is_report_subtype (type))
    parent_type = "report";
  else if (type_is_task_subtype (type))
    parent_type = "task";
  else if (type_is_config_subtype (type))
    parent_type = "config";
  else
    parent_type = type;

  ret = sql_int ("SELECT count (id)"
                " FROM tags"
                " WHERE resource_type = '%s'"
                "   AND EXISTS"
                "     (SELECT * FROM tag_resources"
                "      WHERE tag = tags.id"
                "        AND resource = %llu"
                "        AND resource_location = %d"
                "        AND tag_resources.resource_type = '%s')"
                "   %s;",
                type,
                resource,
                LOCATION_TABLE,
                parent_type,
                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 columns for a resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 *
 * @return The columns.
 */
static column_t *
type_select_columns (const char *type)
{
#if ENABLE_AGENTS
  static column_t agent_columns[] = AGENT_ITERATOR_COLUMNS;
  static column_t agent_group_columns[] = AGENT_GROUP_ITERATOR_COLUMNS;
  static column_t agent_installer_columns[] = AGENT_INSTALLER_ITERATOR_COLUMNS;
#endif
  static column_t alert_columns[] = ALERT_ITERATOR_COLUMNS;
  static column_t cert_bund_adv_columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS;
  static column_t config_columns[] = CONFIG_ITERATOR_COLUMNS;
  static column_t cpe_columns[] = CPE_INFO_ITERATOR_COLUMNS;
  static column_t credential_columns[] = CREDENTIAL_ITERATOR_COLUMNS;
  static column_t cve_columns[] = CVE_INFO_ITERATOR_COLUMNS;
  static column_t dfn_cert_adv_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS;
  static column_t filter_columns[] = FILTER_ITERATOR_COLUMNS;
  static column_t group_columns[] = GROUP_ITERATOR_COLUMNS;
  static column_t host_columns[] = HOST_ITERATOR_COLUMNS;
  static column_t note_columns[] = NOTE_ITERATOR_COLUMNS;
  static column_t nvt_columns[] = NVT_ITERATOR_COLUMNS;
  static column_t os_columns[] = OS_ITERATOR_COLUMNS;
  static column_t override_columns[] = OVERRIDE_ITERATOR_COLUMNS;
  static column_t permission_columns[] = PERMISSION_ITERATOR_COLUMNS;
  static column_t report_columns[] = REPORT_ITERATOR_COLUMNS;
  static column_t result_columns[] = RESULT_ITERATOR_COLUMNS;
  static column_t result_columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT;
  static column_t role_columns[] = ROLE_ITERATOR_COLUMNS;
  static column_t scanner_columns[] = SCANNER_ITERATOR_COLUMNS;
  static column_t schedule_columns[] = SCHEDULE_ITERATOR_COLUMNS;
  static column_t tag_columns[] = TAG_ITERATOR_COLUMNS;
  static column_t target_columns[] = TARGET_ITERATOR_COLUMNS;
  static column_t task_columns[] = TASK_ITERATOR_COLUMNS;
  static column_t user_columns[] = USER_ITERATOR_COLUMNS;
  static column_t vuln_columns[] = VULN_ITERATOR_COLUMNS;

  if (type == NULL)
    return NULL;
#if ENABLE_AGENTS
  if (strcasecmp (type, "AGENT") == 0)
    return agent_columns;
  if (strcasecmp (type, "AGENT_GROUP") == 0)
    return agent_group_columns;
  if (strcasecmp (type, "AGENT_INSTALLER") == 0)
    return agent_installer_columns;
#endif
  if (strcasecmp (type, "ALERT") == 0)
    return alert_columns;
  if (strcasecmp (type, "CERT_BUND_ADV") == 0)
    return cert_bund_adv_columns;
  if (strcasecmp (type, "CONFIG") == 0)
    return config_columns;
  if (strcasecmp (type, "CPE") == 0)
    return cpe_columns;
  if (strcasecmp (type, "CREDENTIAL") == 0)
    return credential_columns;
  if (strcasecmp (type, "CVE") == 0)
    return cve_columns;
  if (strcasecmp (type, "DFN_CERT_ADV") == 0)
    return dfn_cert_adv_columns;
  if (strcasecmp (type, "FILTER") == 0)
    return filter_columns;
  if (strcasecmp (type, "GROUP") == 0)
    return group_columns;
  if (strcasecmp (type, "HOST") == 0)
    return host_columns;
  if (strcasecmp (type, "NOTE") == 0)
    return note_columns;
  if (strcasecmp (type, "NVT") == 0)
    return nvt_columns;
  if (strcasecmp (type, "OS") == 0)
    return os_columns;
  if (strcasecmp (type, "OVERRIDE") == 0)
    return override_columns;
  if (strcasecmp (type, "PERMISSION") == 0)
    return permission_columns;
  if (strcasecmp (type, "PORT_LIST") == 0)
    return port_list_select_columns ();
  if (strcasecmp (type, "REPORT") == 0)
    return report_columns;
  if (strcasecmp (type, "REPORT_CONFIG") == 0)
    return report_config_select_columns ();
  if (strcasecmp (type, "REPORT_FORMAT") == 0)
    return report_format_select_columns ();
  if (strcasecmp (type, "RESULT") == 0)
    {
      if (manage_cert_loaded ())
        return result_columns;
      return result_columns_no_cert;
    }
  if (strcasecmp (type, "ROLE") == 0)
    return role_columns;
  if (strcasecmp (type, "SCANNER") == 0)
    return scanner_columns;
  if (strcasecmp (type, "SCHEDULE") == 0)
    return schedule_columns;
  if (strcasecmp (type, "TAG") == 0)
    return tag_columns;
  if (strcasecmp (type, "TARGET") == 0)
    return target_columns;
  if (strcasecmp (type, "TASK") == 0)
    return task_columns;
  /* Tickets don't use this. */
  assert (strcasecmp (type, "ticket"));
  if (strcasecmp (type, "TLS_CERTIFICATE") == 0)
    return tls_certificate_select_columns ();
  if (strcasecmp (type, "USER") == 0)
    return user_columns;
  if (strcasecmp (type, "VULN") == 0)
    return vuln_columns;
  return NULL;
}

/**
 * @brief Return the columns for a resource iterator.
 *
 * @param[in]  type             Resource type to get columns of.
 *
 * @return The columns.
 */
static column_t *
type_where_columns (const char *type)
{
  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;
  if (strcasecmp (type, "TASK") == 0)
    return task_columns;
  if (strcasecmp (type, "REPORT") == 0)
    return report_columns;
  if (strcasecmp (type, "HOST") == 0)
    return host_columns;
  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.
 *
 * @return The filter columns.
 */
static const char**
type_filter_columns (const char *type)
{
  if (type == NULL)
    return NULL;
#if ENABLE_AGENTS
  if (strcasecmp (type, "AGENT") == 0)
    {
      static const char *ret[] = AGENT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "AGENT_GROUP") == 0)
    {
      static const char *ret[] = AGENT_GROUP_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "AGENT_INSTALLER") == 0)
    {
      static const char *ret[] = AGENT_INSTALLER_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
#endif
  if (strcasecmp (type, "ALERT") == 0)
    {
      static const char *ret[] = ALERT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "CERT_BUND_ADV") == 0)
    {
      static const char *ret[] = CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "CONFIG") == 0)
    {
      static const char *ret[] = CONFIG_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "CREDENTIAL") == 0)
    {
      static const char *ret[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "CPE") == 0)
    {
      static const char *ret[] = CPE_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "CVE") == 0)
    {
      static const char *ret[] = CVE_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "DFN_CERT_ADV") == 0)
    {
      static const char *ret[] = DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "FILTER") == 0)
    {
      static const char *ret[] = FILTER_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "GROUP") == 0)
    {
      static const char *ret[] = GROUP_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "HOST") == 0)
    {
      static const char *ret[] = HOST_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "NOTE") == 0)
    {
      static const char *ret[] = NOTE_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "NVT") == 0)
    {
      static const char *ret[] = NVT_INFO_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "OS") == 0)
    {
      static const char *ret[] = OS_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "OVERRIDE") == 0)
    {
      static const char *ret[] = OVERRIDE_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "PERMISSION") == 0)
    {
      static const char *ret[] = PERMISSION_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "PORT_LIST") == 0)
    return port_list_filter_columns ();
  if (strcasecmp (type, "REPORT") == 0)
    {
      static const char *ret[] = REPORT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "REPORT_CONFIG") == 0)
    return report_config_filter_columns ();
  if (strcasecmp (type, "REPORT_FORMAT") == 0)
    return report_format_filter_columns ();
  if (strcasecmp (type, "RESULT") == 0)
    {
      static const char *ret[] = RESULT_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "ROLE") == 0)
    {
      static const char *ret[] = ROLE_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "SCANNER") == 0)
    {
      static const char *ret[] = SCANNER_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "SCHEDULE") == 0)
    {
      static const char *ret[] = SCHEDULE_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "TAG") == 0)
    {
      static const char *ret[] = TAG_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "TARGET") == 0)
    {
      static const char *ret[] = TARGET_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "TASK") == 0)
    {
      static const char *ret[] = TASK_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  /* Tickets don't use this. */
  assert (strcasecmp (type, "ticket"));
  if (strcasecmp (type, "TLS_CERTIFICATE") == 0)
    return tls_certificate_filter_columns ();
  if (strcasecmp (type, "USER") == 0)
    {
      static const char *ret[] = USER_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  if (strcasecmp (type, "VULN") == 0)
    {
      static const char *ret[] = VULN_ITERATOR_FILTER_COLUMNS;
      return ret;
    }
  return NULL;

}

/**
 * @brief Return the opts subquery definition for a resource type.
 *
 * @param[in]  type    Resource type to get columns of.
 * @param[in]  filter  Filter to apply.
 *
 * @return The SQL subquery definition.
 */
static gchar*
type_opts_table (const char *type, const char *filter)
{
  if (type == NULL)
    return NULL;
  if (strcasecmp (type, "CVE") == 0)
    return g_strdup (" LEFT JOIN scap.epss_scores ON cve = cves.uuid");
  if (strcasecmp (type, "TASK") == 0)
    return task_iterator_opts_table (filter_term_apply_overrides (filter),
                                     filter_term_min_qod (filter), 0);
  if (strcasecmp (type, "OS") == 0)
    return asset_os_iterator_opts_table ();
  if (strcasecmp (type, "REPORT") == 0)
    return report_iterator_opts_table (filter_term_apply_overrides (filter),
                                       filter_term_min_qod (filter));
  if (strcasecmp (type, "RESULT") == 0)
    return result_iterator_opts_table (filter_term_apply_overrides (filter),
                                       setting_dynamic_severity_int ());
  if (strcasecmp (type, "VULN") == 0)
    {
      gchar *task_id, *report_id, *host;
      gchar *ret;

      task_id = filter_term_value (filter, "task_id");
      report_id = filter_term_value (filter, "report_id");
      host = filter_term_value (filter, "host");

      ret = vuln_iterator_opts_table (task_id, report_id, host,
                                      filter_term_min_qod (filter));

      g_free (task_id);
      g_free (report_id);
      g_free (host);

      return ret;
    }
  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.
 */
static char*
type_table (const char *type, int trash)
{
  if (type == NULL)
    return NULL;
  if (trash && type_trash_in_table (type) == 0)
    return g_strdup_printf ("%ss_trash", type);
  if (trash == 0 || type_trash_in_table (type))
    return g_strdup_printf ("%ss", type);
  return NULL;
}

/**
 * @brief Return addition to the WHERE clause if required for a resource type.
 *
 * @param[in]  type     Resource type to get columns of.
 * @param[in]  trash    Whether to get the trash table.
 * @param[in]  filter   The filter term.
 * @param[in]  extra_params  Optional extra parameters.
 *
 * @return The newly allocated WHERE clause additions.
 */
static gchar*
type_extra_where (const char *type, int trash, const char *filter,
                  GHashTable *extra_params)
{
  gchar *extra_where;

  if (strcasecmp (type, "CONFIG") == 0 && extra_params)
    {
      gchar *usage_type;
      if (extra_params)
        usage_type = g_hash_table_lookup (extra_params, "usage_type");
      else
        usage_type = NULL;

      extra_where = configs_extra_where (usage_type);
      if (extra_where == NULL)
        extra_where = g_strdup ("");
    }
  else if (strcasecmp (type, "HOST") == 0)
    {
      extra_where = asset_host_extra_where (filter);
    }
  else if (strcasecmp (type, "TASK") == 0)
    {
      gchar *usage_type;
      if (extra_params)
        usage_type = g_hash_table_lookup (extra_params, "usage_type");
      else
        usage_type = NULL;

      extra_where = tasks_extra_where (trash, usage_type);
    }
  else if (strcasecmp (type, "TLS_CERTIFICATE") == 0)
    {
      extra_where = tls_certificate_extra_where (filter);
    }
  else if (strcasecmp (type, "REPORT") == 0)
    {
      gchar *usage_type;
      if (extra_params)
        usage_type = g_hash_table_lookup (extra_params, "usage_type");
      else
        usage_type = NULL;

      extra_where = reports_extra_where (trash, filter, usage_type);
    }
  else if (strcasecmp (type, "RESULT") == 0)
    {
      int apply_overrides;
      gchar *report_id;
      report_t report;

      /* Note: This keyword may be removed or renamed at any time once there
       * is a better solution like an operator for conditions that must always
       * apply or support for parentheses in filters. */
      report_id = filter_term_value (filter,
                                     "_and_report_id");
      report = 0;

      if (report_id)
        {
          if (find_report_with_permission (report_id,
                                           &report,
                                           NULL))
            {
              g_free (report_id);
              g_warning ("Failed to get report");
              return NULL;
            }

          if (report == 0)
            report = -1;
        }
      g_free (report_id);

      apply_overrides = filter_term_apply_overrides (filter);

      extra_where = results_extra_where (trash, report, NULL,
                                         apply_overrides,
                                         setting_dynamic_severity_int (),
                                         filter,
                                         NULL);
    }
  else if (strcasecmp (type, "VULN") == 0)
    {
      extra_where = vulns_extra_where (filter_term_min_qod (filter));
    }
  else
    extra_where = g_strdup ("");

  return extra_where;
}

/**
 * @brief Get the extra WITH clauses for a resource type.
 *
 * @param[in]  type    The resource type.
 * @param[in]  filter  Filter term.
 *
 * @return The extra WITH clauses.
 */
static gchar *
type_extra_with (const char *type, const char *filter)
{
  if (strcasecmp (type, "VULN") == 0)
    {
      return vuln_iterator_extra_with_from_filter (filter);
    }
  return NULL;
}

/**
 * @brief Builds a filtered SELECT statement for a certain type.
 *
 * @param[in]  type               Resource type
 * @param[in]  columns_str        Columns to get (as used in SQL)
 * @param[in]  get                The get data
 * @param[in]  distinct           Whether to get distinct items
 * @param[in]  ordered            Whether to apply the ordering
 * @param[in]  extra_tables       Extra tables / subqueries
 * @param[in]  given_extra_where  Extra expressions for WHERE clause
 * @param[in]  group_by           Column(s) to group by, NULL for no grouping
 * @param[out] select             Output of newly allocated SELECT statement
 *
 * @return 0: success, 1: filter not found, -1 error.
 */
static int
type_build_select (const char *type, const char *columns_str,
                   const get_data_t *get,
                   gboolean distinct, gboolean ordered,
                   const char *extra_tables, const char *given_extra_where,
                   const char *group_by,
                   gchar **select)
{
  gchar *filter, *with;
  gchar *from_table, *opts_table;
  gchar *clause, *extra_where, *lateral_clause, *filter_order;
  int first, max;
  gchar *owned_clause, *owner_filter;
  array_t *permissions;
  gchar *extra_with;

  column_t *select_columns, *where_columns;
  const char **filter_columns;
  gchar *pagination_clauses;

  assert (select);

  // Get filter
  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      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 1;
        }
    }
  else
    filter = NULL;

  // FROM ... part
  from_table = type_table (type, get->trash);

  opts_table = type_opts_table (type, filter ? filter : get->filter);
  if (strcasecmp (type, "RESULT") == 0)
    {
      gchar *original;
      int overrides, dynamic;

      overrides = filter_term_apply_overrides (filter ? filter : get->filter);
      dynamic = setting_dynamic_severity_int ();

      original = opts_table;

      lateral_clause = result_iterator_lateral (overrides,
                                                dynamic,
                                                "results",
                                                "nvts");

      opts_table = g_strdup_printf (" LEFT OUTER JOIN result_vt_epss"
                                    " ON results.nvt = result_vt_epss.vt_id"
                                    " LEFT OUTER JOIN nvts"
                                    " ON results.nvt = nvts.oid %s,"
                                    " LATERAL %s AS lateral_new_severity",
                                    original,
                                    lateral_clause);
      g_free (original);
      g_free (lateral_clause);
    }

  // WHERE ... part
  select_columns = type_select_columns (type);
  where_columns = type_where_columns (type);
  filter_columns = type_filter_columns (type);

  if (filter_columns == NULL)
    return -1;

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

  owned_clause = acl_where_owned (type, get, type_owned (type),
                                  owner_filter, 0, permissions, 0,
                                  &with);

  if (given_extra_where)
    extra_where = g_strdup (given_extra_where);
  else
    extra_where = type_extra_where (type, get->trash,
                                    filter ? filter : get->filter,
                                    get->extra_params);

  if (get->ignore_pagination)
    pagination_clauses = NULL;
  else
    pagination_clauses = g_strdup_printf (" LIMIT %s OFFSET %d",
                                          sql_select_limit (max),
                                          first);

  extra_with = type_extra_with (type, filter ? filter : get->filter);

  if (extra_with)
    {
      if (with)
        {
          gchar *old_with;

          old_with = with;
          with = g_strdup_printf ("%s, %s", old_with, extra_with);
          g_free (old_with);
        }
      else
        with = g_strdup_printf ("WITH %s", extra_with);
    }

  *select = g_strdup_printf
             ("%s"           // WITH
              "SELECT%s %s"  // DISTINCT, columns
              " FROM %s%s%s" // from_table, opts_table, extra_tables
              " WHERE"
              " %s%s"        // owned_clause, extra_where
              " %s%s%s"      // (filter) clause
              " %s%s"        // group_by
              " %s"          // ORDER BY (filter_order)
              " %s",         // pagination_clauses
              with ? with : "",
              distinct ? " DISTINCT" : "",
              columns_str,
              from_table,
              opts_table ? opts_table : "",
              extra_tables ? extra_tables : "",
              owned_clause,
              extra_where,
              clause ? " AND (" : "",
              clause ? clause : "",
              clause ? ")" : "",
              group_by ? " GROUP BY " : "",
              group_by ? group_by : "",
              (ordered && filter_order) ? filter_order : "",
              pagination_clauses ? pagination_clauses : "");

  g_free (with);
  g_free (from_table);
  g_free (opts_table);
  g_free (owned_clause);
  g_free (extra_with);
  g_free (extra_where);
  g_free (pagination_clauses);

  return 0;
}

/**
 * @brief Remove a resource from tags.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource.
 * @param[in]  location  Location: table or trash.
 */
void
tags_remove_resource (const char *type, resource_t resource, int location)
{
  sql ("DELETE FROM tag_resources"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
}

/**
 * @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 tag_resources 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 tag_resources_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  Get a GArray of all users as user_t.
 *
 * @return  Newly allocated GArray containing all users.
 */
static GArray*
all_users_array ()
{
  iterator_t users_iter;
  GArray *ret;

  ret = g_array_new (TRUE, TRUE, sizeof (resource_t));

  init_iterator (&users_iter, "SELECT id FROM users;");

  while (next (&users_iter))
    {
      user_t user = iterator_int64 (&users_iter, 0);
      g_array_append_val (ret, user);
    }

  cleanup_iterator (&users_iter);

  return ret;
}

/**
 * @brief Update permissions cache for a resource.
 *
 * @param[in]  type         Resource type.
 * @param[in]  resource     The resource to update the cache for.
 * @param[in]  cache_users  GArray of users to create cache for or NULL for all.
 */
static void
cache_permissions_for_resource (const char *type, resource_t resource,
                                GArray *cache_users)
{
  int free_users;

  if (type == NULL || resource == 0 || resource == -1)
    return;

  if (cache_users == NULL)
    {
      g_debug ("%s: Getting all users", __func__);
      free_users = 1;
      cache_users = all_users_array ();
    }
  else
    free_users = 0;

  if (strcmp (type, "task") == 0)
    {
      char* old_current_user_id;
      gchar *resource_id;
      int user_index;

      old_current_user_id = current_credentials.uuid;
      resource_id = resource_uuid (type, resource);

      g_debug ("%s: Caching permissions on %s \"%s\" for %d user(s)",
               __func__, type, resource_id, cache_users->len);

      for (user_index = 0; user_index < cache_users->len; user_index++)
        {
          user_t user;
          gchar *user_id;

          user = g_array_index (cache_users, user_t, user_index);
          user_id = user_uuid (user);

          current_credentials.uuid = user_id;
          manage_session_init (user_id);

          if (sql_int ("SELECT count(*) FROM permissions_get_%ss"
                       " WHERE \"user\" = %llu"
                       "   AND %s = %llu;",
                       type,
                       user,
                       type,
                       resource))
            {
              sql ("UPDATE permissions_get_%ss"
                   "  SET has_permission"
                   "       = user_has_access_uuid (cast ('%s' as text),"
                   "                               cast ('%s' as text),"
                   "                               cast ('get_%ss' as text),"
                   "                               0)"
                   " WHERE \"user\" = %llu"
                   "   AND %s = %llu;",
                   type,
                   type,
                   resource_id,
                   type,
                   user,
                   type,
                   resource);
            }
          else
            {
              sql ("INSERT INTO permissions_get_%ss"
                   "              (\"user\", %s, has_permission)"
                   "  SELECT %llu, %llu,"
                   "         user_has_access_uuid (cast ('%s' as text),"
                   "                               cast ('%s' as text),"
                   "                               cast ('get_%ss' as text),"
                   "                               0);",
                   type,
                   type,
                   user,
                   resource,
                   type,
                   resource_id,
                   type);
            }

          g_free (user_id);
          current_credentials.uuid = NULL;
        }

      current_credentials.uuid = old_current_user_id;
      manage_session_init (old_current_user_id);

      g_free (resource_id);
    }

  if (free_users)
    g_array_free (cache_users, TRUE);
}

/**
 * @brief Update permissions cache for a given type and selection of users.
 *
 * @param[in]  type         Type.
 * @param[in]  cache_users  GArray of users to create cache for.
 */
static void
cache_permissions_for_users (const char *type, GArray *cache_users)
{
  int free_users;

  if (type == NULL)
    return;

  if (cache_users == NULL)
    {
      g_debug ("%s: Getting all users", __func__);
      free_users = 1;
      cache_users = all_users_array ();
    }
  else
    free_users = 0;

  if (strcmp (type, "task") == 0)
    {
      iterator_t resources;

      init_iterator (&resources, "SELECT id FROM %ss;", type);

      while (next (&resources))
        {
          resource_t resource = iterator_int64 (&resources, 0);
          cache_permissions_for_resource (type, resource, cache_users);
        }

      cleanup_iterator (&resources);
    }

  if (free_users)
    g_array_free (cache_users, TRUE);
}

/**
 * @brief Update entire permission cache the given users.
 *
 * @param[in]  cache_users  GArray of users to create cache for.  NULL means
 *                          all users.
 */
void
cache_all_permissions_for_users (GArray *cache_users)
{
  int free_users;

  if (cache_users == NULL)
    {
      g_debug ("%s: Getting all users", __func__);
      free_users = 1;
      cache_users = all_users_array ();
    }
  else
    free_users = 0;

  cache_permissions_for_users ("task", cache_users);

  if (free_users)
    g_array_free (cache_users, TRUE);
}

/**
 * @brief Delete permission cache a resource.
 *
 * @param[in]  type      Resource type.
 * @param[in]  resource  Resource.
 */
void
delete_permissions_cache_for_resource (const char* type, resource_t resource)
{
  if (type == NULL || resource == 0)
    return;

  if (strcmp (type, "task") == 0)
    {
      sql ("DELETE FROM permissions_get_%ss WHERE \"%s\" = %llu",
           type, type, resource);
    }
}

/**
 * @brief Delete permission cache the given user.
 *
 * @param[in]  user  User.
 */
void
delete_permissions_cache_for_user (user_t user)
{
  sql ("DELETE FROM permissions_get_tasks WHERE \"user\" = %llu;", user);
}


/* 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 too old, -3 database needs to be initialised
 *         from server, -5 database is too new.
 */
int
manage_optimize (GSList *log_config, const db_conn_info_t *database,
                 const gchar *name)
{
  gchar *success_text;
  int ret;

  g_info ("   Optimizing: %s.", name);

  if (name == NULL)
    {
      fprintf (stderr, "Name required for optimize.\n");
      return 1;
    }

  int avoid_db_check_inserts = 0;
  /* The optimize=cleanup-sequences option may be used if a sequence has
   * already reached its maximum value, so avoid any inserts that may cause
   * a sequence maximum error. *
   */
  if (strcasecmp (name, "cleanup-sequences") == 0)
    avoid_db_check_inserts = 1;

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

  ret = 0;
  if (strcasecmp (name, "vacuum") == 0)
    {
      gchar *quoted_db_name;
      unsigned long long int old_size, new_size;

      quoted_db_name = sql_quote (sql_database ());

      old_size = sql_int64_0 ("SELECT pg_database_size ('%s')",
                              quoted_db_name);

      sql ("VACUUM;");

      new_size = sql_int64_0 ("SELECT pg_database_size ('%s')",
                              quoted_db_name);

      g_free (quoted_db_name);

      if (old_size <= 0 || new_size <= 0)
        success_text = g_strdup_printf ("Optimized: vacuum.");
      else if (new_size <= old_size)
        success_text = g_strdup_printf ("Optimized: vacuum."
                                        " Database file size reduced by"
                                        " %llu 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."
                                        " Database file size *increased* by"
                                        " %llu MiB (%0.1f %%).\n",
                                        (new_size - old_size) / (1024 * 1024),
                                        (new_size - old_size)
                                          * 100.0 / old_size);
    }
  else if (strcasecmp (name, "add-feed-permissions") == 0)
    {
      int permissions_count, object_count;
      permissions_count = 0;
      object_count = 0;
      sql_begin_immediate ();
      add_feed_role_permissions ("config",
                                 "Scan Config / Policy",
                                 &permissions_count,
                                 &object_count);
      add_feed_role_permissions ("port_list",
                                 "Port List",
                                 &permissions_count,
                                 &object_count);
      add_feed_role_permissions ("report_format",
                                 "Report Format",
                                 &permissions_count,
                                 &object_count);
      sql_commit ();
      success_text = g_strdup_printf ("Optimized: add-feed-permissions."
                                      " Added %d permissions"
                                      " for %d data objects.",
                                      permissions_count,
                                      object_count);
    }
  else if (strcasecmp (name, "analyze") == 0)
    {
      sql ("ANALYZE;");
      success_text = g_strdup_printf ("Optimized: analyze.");
    }
  else if (strcasecmp (name, "cleanup-config-prefs") == 0)
    {
      sql ("DELETE FROM config_preferences WHERE id NOT IN"
           " (SELECT min(id) FROM config_preferences"
           "  GROUP BY config, type, name, value);");

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

      sql ("UPDATE config_preferences"
           " SET pref_nvt = NULL,"
           "     pref_id = NULL,"
           "     pref_type = NULL,"
           "     pref_name = NULL"
           " WHERE type = 'SERVER_PREFS' AND pref_nvt IS NOT NULL;");

      sql ("UPDATE config_preferences"
          " SET pref_nvt = substring (name, '^([^:]*)'),"
          "     pref_id = CAST(substring (name, '^[^:]*:([0-9]+)') AS integer),"
          "     pref_type = substring (name, '^[^:]*:[0-9]+:([^:]*):'),"
          "     pref_name = substring (name, '^[^:]*:[0-9]+:[^:]*:(.*)')"
          " WHERE type = 'PLUGINS_PREFS'"
          "       AND (pref_nvt = '(null)' OR pref_nvt IS NULL"
          "            OR pref_type = '(null)' OR pref_type IS NULL"
          "            OR pref_name = '(null)' OR pref_name IS NULL)"
          "       AND name ~ '^[^:]*:[0-9]+:[^:]*:.*'"
          "       AND type = 'PLUGINS_PREFS';");

      success_text = g_strdup_printf ("Optimized: cleanup-config-prefs.");
    }
  else if (strcasecmp (name, "cleanup-feed-permissions") == 0)
    {
      int permissions_count, object_count;
      permissions_count = 0;
      object_count = 0;
      sql_begin_immediate ();
      clean_feed_role_permissions ("config",
                                   "Scan Config / Policy",
                                   &permissions_count,
                                   &object_count);
      clean_feed_role_permissions ("port_list",
                                   "Port List",
                                   &permissions_count,
                                   &object_count);
      clean_feed_role_permissions ("report_format",
                                   "Report Format",
                                   &permissions_count,
                                   &object_count);
      sql_commit ();
      success_text = g_strdup_printf ("Optimized: cleanup-feed-permissions."
                                      " Removed %d permissions"
                                      " for %d data objects.",
                                      permissions_count,
                                      object_count);
    }
  else if (strcasecmp (name, "cleanup-port-names") == 0)
    {
      int changes_iana, changes_old_format;

      sql_begin_immediate ();
      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-report-formats") == 0)
    {
      iterator_t alert_data;
      int alert_changes = 0;

      /* Clean up alerts with missing report formats */
      sql_begin_immediate ();

      init_iterator (&alert_data,
                     "SELECT id, data,"
                     "       (SELECT uuid FROM alerts WHERE id = alert)"
                     "  FROM alert_method_data"
                     " WHERE (name = 'notice_attach_format'"
                     "        OR name = 'notice_report_format'"
                     "        OR name = 'send_report_format')"
                     "   AND data NOT IN (SELECT uuid"
                     "                      FROM report_formats)"
                     "   AND data NOT IN (SELECT uuid"
                     "                      FROM report_formats_trash)");
      while (next (&alert_data))
        {
          alert_changes ++;
          g_message ("Alert %s uses a non-existent report format (%s)"
                     " and will now use the TXT report format (%s)"
                     " if TXT exists.",
                     iterator_string (&alert_data, 2),
                     iterator_string (&alert_data, 1),
                     "a3810a62-1f62-11e1-9219-406186ea4fc5");

          sql ("UPDATE alert_method_data SET data = '%s'"
               " WHERE id = %llu",
               "a3810a62-1f62-11e1-9219-406186ea4fc5",
               iterator_int64 (&alert_data, 0));
        }
      cleanup_iterator(&alert_data);

      init_iterator (&alert_data,
                     "SELECT id, data,"
                     "       (SELECT uuid FROM alerts_trash WHERE id = alert)"
                     "  FROM alert_method_data_trash"
                     " WHERE (name = 'notice_attach_format'"
                     "        OR name = 'notice_report_format'"
                     "        OR name = 'send_report_format')"
                     "   AND data NOT IN (SELECT uuid"
                     "                      FROM report_formats)"
                     "   AND data NOT IN (SELECT uuid"
                     "                      FROM report_formats_trash)");
      while (next (&alert_data))
        {
          alert_changes ++;
          g_warning ("Trash Alert %s uses a non-existent report format (%s)"
                     " and will now use the TXT report format (%s)"
                     " if TXT exists.",
                     iterator_string (&alert_data, 2),
                     iterator_string (&alert_data, 1),
                     "a3810a62-1f62-11e1-9219-406186ea4fc5");

          sql ("UPDATE alert_method_data_trash SET data = '%s'"
               " WHERE id = %llu",
               "a3810a62-1f62-11e1-9219-406186ea4fc5",
               iterator_int64 (&alert_data, 0));
        }
      cleanup_iterator(&alert_data);

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: cleanup-report-formats."
                                      " Cleaned up report format references in"
                                      " %d alert(s).",
                                      alert_changes);
    }
  else if (strcasecmp (name, "cleanup-result-nvts") == 0)
    {
      sql_begin_immediate ();

      if (cleanup_result_nvts ())
        {
          sql_rollback();
          fprintf (stderr, "Clean-up of result_nvts failed.\n");
          manage_option_cleanup ();
          return 1;
        }

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: Cleaned up result_nvts.");
    }
  else if (strcasecmp (name, "cleanup-result-severities") == 0)
    {
      int missing_severity_changes = 0;
      sql_begin_immediate ();

      sql ("UPDATE results"
          " SET severity"
          "       = (SELECT CAST (value AS real) FROM settings"
          "           WHERE uuid = '" SETTING_UUID_DEFAULT_SEVERITY "'"
          "             AND (settings.owner = results.owner"
          "                  OR settings.owner IS NULL)"
          "          ORDER BY settings.owner DESC LIMIT 1)"
          " 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-result-encoding") == 0)
    {
      sql_begin_immediate ();

      g_debug ("%s: Stripping control chars out of result descriptions",
               __func__);

      sql ("UPDATE results"
           " SET description = regexp_replace (description,"
           "                                   '[\x01-\x09\xB-\x1F]',"
           "                                   ' ',"
           "                                   'g')"
           " WHERE description ~ '[\x01-\x09\xB-\x1F]';");

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: Cleaned up result encoding.");
    }
  else if (strcasecmp (name, "cleanup-schedule-times") == 0)
    {
      int changes;

      sql_begin_immediate ();

      changes = cleanup_schedule_times ();

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: cleanup-schedule-times."
                                      " Due date updated for %d tasks.",
                                      changes);
    }
  else if (strcasecmp (name, "cleanup-sequences") == 0)
    {
      success_text = NULL;

      if (cleanup_config_sequences ())
        {
          fprintf (stderr, "Failed to clean up config related sequences.\n");
          ret = -1;
        }
      else if (cleanup_nvt_sequences ())
        {
          fprintf (stderr, "Failed to clean up NVT related sequences.\n");
          ret = -1;
        }
      else if (cleanup_port_list_sequences ())
        {
          fprintf (stderr, "Failed to clean up port list related sequences.\n");
          ret = -1;
        }
      else
        {
          success_text = g_strdup_printf ("Optimized: cleanup-sequences."
                                          " Cleaned up id sequences.");
        }
    }
  else if (strcasecmp (name, "cleanup-tls-certificate-encoding") == 0)
    {
      int changes;
      sql_begin_immediate ();

      g_debug ("%s: Cleaning up encoding of TLS certificate DNs",
               __func__);

      changes = cleanup_tls_certificate_encoding ();

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: Cleaned up encoding"
                                      " of %d TLS certificate(s).",
                                      changes);
    }
  else if (strcasecmp (name, "rebuild-permissions-cache") == 0)
    {
      sql_begin_immediate ();

      sql ("DELETE FROM permissions_get_tasks");

      cache_all_permissions_for_users (NULL);

      sql_commit ();

      success_text = g_strdup_printf ("Optimized: rebuild-permissions-cache."
                                      " Permission cache rebuilt.");
    }
  else if (strcasecmp (name, "rebuild-report-cache") == 0)
    {
      int changes;

      sql_begin_immediate ();

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

      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", __func__);
  return sql_cancel_internal ();
}

/**
 * @brief Get the VT verification collation override.
 *
 * @return The collation or NULL for automatic.
 */
const char *
get_vt_verification_collation ()
{
  return vt_verification_collation;
}

/**
 * @brief Sets the VT verification collation override.
 *
 * This must be done before the SQL functions are created to be effective.
 *
 * @param[in]  new_collation  The new collation.
 */
void
set_vt_verification_collation (const char *new_collation)
{
  g_free (vt_verification_collation);
  if (new_collation && strcmp (new_collation, ""))
    vt_verification_collation = g_strdup(new_collation);
  else
    vt_verification_collation = NULL;
}

/**
 * @brief Resets the id sequence for a table and assigns new ids to all rows.
 *
 * This ensures the id column has the lowest possible value, closing any gaps
 * while preserving the original order.
 *
 * @param[in]  table  The table to assign new ids to.
 *
 * @return 0 success, -1 error.
 */
int
cleanup_ids_for_table (const char *table)
{
  char *sequence_name;
  iterator_t ids;

  g_info ("Cleaning up id sequence for table \"%s\"", table);

  if (sql_int ("SELECT try_exclusive_lock ('%s')", table) == 0)
    {
      g_warning ("%s: Failed to acquire lock on table \"%s\"",
                 __func__, table);
      return -1;
    }

  sequence_name = sql_string ("SELECT pg_get_serial_sequence('%s', 'id')",
                              table);
  if (sequence_name == NULL)
    {
      g_warning ("%s: Failed to get sequence name for %s id",
                 __func__, table);
      return -1;
    }

  sql ("SET ROLE \"%s\";", DB_SUPERUSER_ROLE);
  sql ("ALTER SEQUENCE %s RESTART;", sequence_name);
  sql ("RESET ROLE;");

  init_iterator (&ids,
                 "SELECT id AS old_id, nextval('%s'::regclass) AS new_id"
                 "   FROM %s"
                 "   ORDER BY old_id ASC"
                 "   FOR UPDATE",
                 sequence_name,
                 table);

  while (next (&ids))
    {
      resource_t old_id, new_id;
      old_id = iterator_int64 (&ids, 0);
      new_id = iterator_int64 (&ids, 1);

      sql ("UPDATE %s SET id = %llu WHERE id = %llu", table, new_id, old_id);
    }
  cleanup_iterator(&ids);

  free (sequence_name);
  return 0;
}

#if ENABLE_HTTP_SCANNER

/**
 * @brief Get the QoD value for a given nvti OID.
 *
 * @param nvti_oid The unique identifier for the vulnerability test.
 *
 * @return The QoD value as an integer (1–100), or
 *         QOD_DEFAULT if unavailable or invalid.
 */
int
get_http_scanner_nvti_qod (const char *nvti_oid)
{
  int qod = QOD_DEFAULT;

  nvti_t *nvti = lookup_nvti (nvti_oid);
  if (nvti != NULL)
    {
      const char *qod_str = nvti_qod (nvti);
      if (qod_str != NULL)
        {
          int parsed_qod = atoi (qod_str);
          if (parsed_qod > 0 && parsed_qod <= 100)
            {
              qod = parsed_qod;
            }
        }
    }

  return qod;
}

/**
 * @brief Generate the hash value for the fields of the result and
 * check if HTTP scanner result for report already exists
 *
 * @param[in]  report      Report.
 * @param[in]  task        Task.
 * @param[in]  r_entity    entity of the result.
 * @param[out] entity_hash_value  The generated hash value of r_entity.
 *
 * @return     "1" if openvasd result already exists, else "0"
 */
int
check_http_scanner_result_exists (report_t report,
                                  task_t task,
                                  http_scanner_result_t res,
                                  char **entity_hash_value,
                                  GHashTable *hashed_results)
{
  GString *result_string;
  int return_value = 0;
  result_string = g_string_new ("");
  g_string_append_printf (result_string, "host:%s\n"
                          "hostname:%s\n"
                          "type:%s\n"
                          "description:%s\n"
                          "port:%s\n"
                          "oid:%s",res->ip_address, res->hostname,
                          res->type, res->message, res->port, res->oid);

 *entity_hash_value = get_md5_hash_from_string (result_string->str);
  if (g_hash_table_contains (hashed_results, *entity_hash_value))
    {
      return_value = 1;
    }
  else
    {
      g_hash_table_insert (hashed_results,
                           g_strdup(*entity_hash_value),
                           GINT_TO_POINTER(1));
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM results"
                   "  WHERE report = %llu and hash_value = '%s');",
                   report, *entity_hash_value))
        {
          const char *desc, *type, *severity = NULL, *host = NULL;
          const char *hostname, *port = NULL;
          gchar *quoted_desc, *quoted_type, *quoted_host;
          gchar *quoted_hostname, *quoted_port;
          double severity_double = 0.0;
          int qod_int = get_http_scanner_nvti_qod (res->oid);

          // ip_address for container scanning is an image digest
          host = extract_sha256_digest_if_found (res->ip_address);
          hostname = res->hostname;
          type = convert_http_scanner_type_to_osp_type(res->type);
          desc = res->message;
          severity = nvt_severity (res->oid, type);
          port = res->port;

          if (!severity)
            {
              g_debug ("%s: Result without severity", __func__);
              g_string_free (result_string, TRUE);
              return 0;
            }
          else
            {
              severity_double = strtod (severity, NULL);
            }

          quoted_host = sql_quote (host ?: "");
          quoted_hostname = sql_quote (hostname ?: "");
          quoted_type = sql_quote (type ?: "");
          quoted_desc = sql_quote (desc ?: "");
          quoted_port = sql_quote (port ?: "");

          if (sql_int ("SELECT EXISTS"
                       " (SELECT * FROM results"
                       "   WHERE report = %llu and hash_value = '%s'"
                       "    and host = '%s' and hostname = '%s'"
                       "    and type = '%s' and description = '%s'"
                       "    and port = '%s' and severity = %1.1f::real"
                       "    and qod = %d"
                       " );",
                       report, *entity_hash_value,
                       quoted_host, quoted_hostname,
                       quoted_type, quoted_desc,
                       quoted_port, severity_double,
                       qod_int))
            {
              g_info ("Captured duplicate result, report: %llu hash_value: %s",
                      report, *entity_hash_value);
              g_debug ("Entity string: %s", result_string->str);
              return_value = 1;
            }

          g_free (quoted_host);
          g_free (quoted_hostname);
          g_free (quoted_type);
          g_free (quoted_desc);
          g_free (quoted_port);
        }
    }
  if (return_value)
    {
      g_debug ("Captured duplicate result, report: %llu hash_value: %s",
                report, *entity_hash_value);
      g_debug ("Entity string: %s", result_string->str);
    }
  g_string_free (result_string, TRUE);
  return return_value;
}

/**
 * @brief Convert HTTP scanner result types to OSP result types.
 *
 * @param http_scanner_type The HTTP scanner type as a string.
 *
 * @return A new string with the corresponding OSP type.
 */
char *
convert_http_scanner_type_to_osp_type (const char *http_scanner_type)
{
  if (g_strcmp0 (http_scanner_type, "alarm") == 0)
    return g_strdup ("Alarm");
  else if (g_strcmp0 (http_scanner_type, "error") == 0)
    return g_strdup ("Error Message");
  else if (g_strcmp0 (http_scanner_type, "log") == 0
           || g_strcmp0 (http_scanner_type, "host_detail") == 0)
    return g_strdup ("Log Message");

  return g_strdup (http_scanner_type);
}

/**
 * @brief Convert and add a single http scanner result to the report structure.
 *
 * @param[in]  res            The http scanner result to be processed.
 * @param[in,out] results_aux Pointer to auxiliary report data,
 *                            containing the report handle, task reference,
 *                            results array, and hash maps for deduplication.
 */
static void
add_http_scanner_result_to_report (http_scanner_result_t res, gpointer *results_aux)
{

  struct report_aux *rep_aux = *results_aux;
  result_t result;
  char *type, *name, *severity, *host, *hostname, *test_id;
  char *port = NULL, *path = NULL;
  char *desc = NULL, *nvt_id = NULL, *severity_str = NULL;
  int qod_int;

  type = convert_http_scanner_type_to_osp_type (res->type);
  name = NULL;
  severity = NULL;
  test_id = res->oid;
  host = res->ip_address;
  hostname = res->hostname;
  port = res->port;

  /* Add report host if it doesn't exist. */
  manage_report_host_add (rep_aux->report, host, 0, 0);
  if (!strcmp (port, "general/Host_Details"))
    {
      gchar *hash_value = NULL;
      gchar *r_uuid = report_uuid (rep_aux->report);
      if (!check_host_detail_exists (rep_aux->report, host,
                                     res->detail_source_type,
                                     res->detail_source_name,
                                     res->detail_source_description,
                                     res->detail_name,
                                     res->detail_value,
                                     &hash_value,
                                     rep_aux->hash_hostdetails))
        {
          insert_report_host_detail (rep_aux->report, host,
                                     res->detail_source_type,
                                     res->detail_source_name,
                                     res->detail_source_description,
                                     res->detail_name,
                                     res->detail_value,
                                     hash_value);
        }
      /* Add host identifiers to asset snapshot array */
      asset_snapshot_add_report_host_identifier (host,
                                                  res->detail_name,
                                                  res->detail_value,
                                                  r_uuid,
                                                  res->detail_source_name);
      desc = res->message;
      g_free (hash_value);
      g_free (type);
      g_free (r_uuid);
      return;
    }
  else if (g_str_has_prefix (test_id, "1.3.6.1.4.1.25623.1."))
    {
      nvt_id = g_strdup (test_id);
      severity_str = nvt_severity (test_id, type);
      desc = res->message;
    }
  else
    {
      nvt_id = g_strdup (name);
      desc = res->message;
    }
  qod_int = get_http_scanner_nvti_qod (test_id);
  if (port && strcmp (port, "general/Host_Details") == 0)
    {
      /* TODO: This should probably be handled by the "Host Detail"
       *        result type with extra source info in OSP.
       *
       * TODO: 17.12.2025 ozgen - For openvasd scans, host details do not pass through
       *       manage_report_host_detail(). When "in_assets" is enabled, this may
       *       prevent host identifiers from being added as assets.
       */
      if (manage_report_host_detail (rep_aux->report, host, desc,
                                     rep_aux->hash_hostdetails))
        g_warning ("%s: Failed to add report detail for host '%s': %s",
                   __func__, host, desc);
    }
  else if (host && desc && (strcmp (type, "host_start") == 0))
    {
      set_scan_host_start_time_ctime (rep_aux->report, host, desc);
    }
  else if (host && desc && (strcmp (type, "host_end") == 0))
    {
      set_scan_host_end_time_ctime (rep_aux->report, host, desc);
      add_assets_from_host_in_report (rep_aux->report, host);
    }
  else
    {
      char *hash_value;
      if (!check_http_scanner_result_exists (rep_aux->report, rep_aux->task, res,
                                             &hash_value, rep_aux->hash_results))
        {
          result = make_osp_result (rep_aux->task,
                                    host ?: "",
                                    hostname ?: "",
                                    nvt_id ?: "",
                                    type ?: "",
                                    desc ?: "",
                                    port ?: "",
                                    severity_str ?: severity,
                                    qod_int,
                                    path ?: "",
                                    hash_value);
          g_array_append_val (rep_aux->results_array, result);
        }
      g_free (hash_value);
    }

  g_free (nvt_id);
  g_free (type);

  return;
}

/**
 * @brief Parse http scanner results.
 *
 * @param[in]  task        Task.
 * @param[in]  report      Report.
 * @param[in]  results     http scanner results.
 */
void
parse_http_scanner_report (task_t task, report_t report, GSList *results,
                       time_t start_time, time_t end_time)
{
  char *defs_file = NULL;
  gboolean has_results = FALSE;
  GArray *results_array = NULL;
  GHashTable *hashed_scanner_results = NULL;
  GHashTable *hashed_host_details = NULL;
  struct report_aux *rep_aux;

  assert (task);
  assert (report);

  sql_begin_immediate ();
  /* Set the report's start and end times. */

  if (start_time)
    set_scan_start_time_epoch (report, start_time);

  if (end_time)
    set_scan_end_time_epoch (report, end_time);

  if (results == NULL)
    {
      sql_commit ();
      return;
    }

  hashed_scanner_results = g_hash_table_new_full (g_str_hash,
                                              g_str_equal,
                                              g_free,
                                              NULL);

  hashed_host_details = g_hash_table_new_full (g_str_hash,
                                               g_str_equal,
                                               g_free,
                                               NULL);

  has_results = TRUE;

  defs_file = task_definitions_file (task);

  results_array = g_array_new(TRUE, TRUE, sizeof(result_t));
  rep_aux = g_malloc0 (sizeof (struct report_aux));
  rep_aux->report = report;
  rep_aux->task = task;
  rep_aux->results_array = results_array;
  rep_aux->hash_results = hashed_scanner_results;
  rep_aux->hash_hostdetails = hashed_host_details;

  g_slist_foreach(results, (GFunc) add_http_scanner_result_to_report, &rep_aux);

  if (has_results)
    {
      sql ("UPDATE reports SET modification_time = m_now() WHERE id = %llu;",
           report);
      report_add_results_array (report, results_array);
    }

  sql_commit ();
  if (results_array && has_results)
    g_array_free (results_array, TRUE);

  g_hash_table_destroy (hashed_scanner_results);
  g_hash_table_destroy (hashed_host_details);
  g_free (defs_file);
  g_free (rep_aux);
}
#endif
