/* Copyright (C) 2009-2022 Greenbone AG
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

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

#include "debug_utils.h"
#include "manage_sql.h"
#include "manage_port_lists.h"
#include "manage_report_formats.h"
#include "manage_sql_secinfo.h"
#include "manage_sql_nvts.h"
#include "manage_tickets.h"
#include "manage_sql_configs.h"
#include "manage_sql_port_lists.h"
#include "manage_sql_report_configs.h"
#include "manage_sql_report_formats.h"
#include "manage_sql_tickets.h"
#include "manage_sql_tls_certificates.h"
#include "manage_acl.h"
#include "manage_authentication.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 <pwd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <grp.h>
#include <gpgme.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/gpgmeutils.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"

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

const char *threat_message_type (const char *);

int delete_reports (task_t);

int
stop_task_internal (task_t);

int
validate_username (const gchar *);

void
set_task_interrupted (task_t, const gchar *);

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

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

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

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

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

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

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

static resource_t
permission_resource (permission_t);

static resource_t
permission_subject (permission_t);

static char *
permission_subject_type (permission_t);

static int
role_is_predefined (role_t);

static int
role_is_predefined_id (const char *);

static int
task_second_last_report (task_t, report_t *);

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

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

static void
check_for_new_scap ();

static void
check_for_new_cert ();

static void
check_for_updated_scap ();

static void
check_for_updated_cert ();

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

static gboolean
find_group_with_permission (const char *, group_t *, const char *);

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 int
task_last_report_any_status (task_t, report_t *);

static int
task_report_previous (task_t task, report_t, report_t *);

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
cache_all_permissions_for_users (GArray*);

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

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

static int
report_host_dead (report_host_t);

static int
report_host_result_count (report_host_t);

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

static char *
setting_timezone ();

static int
setting_delta_reports_version_int ();

static double
task_severity_double (task_t, int, int, int);

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

static char*
trash_filter_name (filter_t);

static char*
trash_target_comment (target_t);

static int
user_resources_in_use (user_t,
                       const char *, int(*)(resource_t),
                       const char *, int(*)(resource_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.
 */
static manage_connection_forker_t manage_fork_connection;

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

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

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

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

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

/**
 * @brief Default max number of bytes of user-defined message in email alerts.
 */
#define MAX_EMAIL_MESSAGE_LENGTH 2000

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

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

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

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

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

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

/**
 * @brief The GMP command list.
 */
command_t gmp_commands[]
 = {{"AUTHENTICATE", "Authenticate with the manager." },
    {"CREATE_ALERT", "Create an alert."},
    {"CREATE_ASSET", "Create an asset."},
    {"CREATE_CONFIG", "Create a config."},
    {"CREATE_CREDENTIAL", "Create a credential."},
    {"CREATE_FILTER", "Create a filter."},
    {"CREATE_GROUP", "Create a group."},
    {"CREATE_NOTE", "Create a note."},
    {"CREATE_OVERRIDE", "Create an override."},
    {"CREATE_PERMISSION", "Create a permission."},
    {"CREATE_PORT_LIST", "Create a port list."},
    {"CREATE_PORT_RANGE", "Create a port range in a port list."},
    {"CREATE_REPORT", "Create a report."},
    {"CREATE_REPORT_CONFIG", "Create a report config."},
    {"CREATE_REPORT_FORMAT", "Create a report format."},
    {"CREATE_ROLE", "Create a role."},
    {"CREATE_SCANNER", "Create a scanner."},
    {"CREATE_SCHEDULE", "Create a schedule."},
    {"CREATE_TAG", "Create a tag."},
    {"CREATE_TARGET", "Create a target."},
    {"CREATE_TASK", "Create a task."},
    {"CREATE_TICKET", "Create a ticket."},
    {"CREATE_TLS_CERTIFICATE", "Create a TLS certificate."},
    {"CREATE_USER", "Create a new user."},
    {"DELETE_ALERT", "Delete an alert."},
    {"DELETE_ASSET", "Delete an asset."},
    {"DELETE_CONFIG", "Delete a config."},
    {"DELETE_CREDENTIAL", "Delete a credential."},
    {"DELETE_FILTER", "Delete a filter."},
    {"DELETE_GROUP", "Delete a group."},
    {"DELETE_NOTE", "Delete a note."},
    {"DELETE_OVERRIDE", "Delete an override."},
    {"DELETE_PERMISSION", "Delete a permission."},
    {"DELETE_PORT_LIST", "Delete a port list."},
    {"DELETE_PORT_RANGE", "Delete a port range."},
    {"DELETE_REPORT", "Delete a report."},
    {"DELETE_REPORT_CONFIG", "Delete a report config."},
    {"DELETE_REPORT_FORMAT", "Delete a report format."},
    {"DELETE_ROLE", "Delete a role."},
    {"DELETE_SCANNER", "Delete a scanner."},
    {"DELETE_SCHEDULE", "Delete a schedule."},
    {"DELETE_TAG", "Delete a tag."},
    {"DELETE_TARGET", "Delete a target."},
    {"DELETE_TASK", "Delete a task."},
    {"DELETE_TICKET", "Delete a ticket."},
    {"DELETE_TLS_CERTIFICATE", "Delete a TLS certificate."},
    {"DELETE_USER", "Delete an existing user."},
    {"DESCRIBE_AUTH", "Get details about the used authentication methods."},
    {"EMPTY_TRASHCAN", "Empty the trashcan."},
    {"GET_AGGREGATES", "Get aggregates of resources."},
    {"GET_ALERTS", "Get all alerts."},
    {"GET_ASSETS", "Get all assets."},
    {"GET_CONFIGS", "Get all configs."},
    {"GET_CREDENTIALS", "Get all credentials."},
    {"GET_FEEDS", "Get details of one or all feeds this Manager uses."},
    {"GET_FILTERS", "Get all filters."},
    {"GET_GROUPS", "Get all groups."},
    {"GET_INFO", "Get raw information for a given item."},
    {"GET_LICENSE", "Get license information." },
    {"GET_NOTES", "Get all notes."},
    {"GET_NVTS", "Get one or all available NVTs."},
    {"GET_NVT_FAMILIES", "Get a list of all NVT families."},
    {"GET_OVERRIDES", "Get all overrides."},
    {"GET_PERMISSIONS", "Get all permissions."},
    {"GET_PORT_LISTS", "Get all port lists."},
    {"GET_PREFERENCES", "Get preferences for all available NVTs."},
    {"GET_REPORTS", "Get all reports."},
    {"GET_REPORT_CONFIGS", "Get all report configs."},
    {"GET_REPORT_FORMATS", "Get all report formats."},
    {"GET_RESULTS", "Get results."},
    {"GET_ROLES", "Get all roles."},
    {"GET_SCANNERS", "Get all scanners."},
    {"GET_SCHEDULES", "Get all schedules."},
    {"GET_SETTINGS", "Get all settings."},
    {"GET_SYSTEM_REPORTS", "Get all system reports."},
    {"GET_TAGS", "Get all tags."},
    {"GET_TARGETS", "Get all targets."},
    {"GET_TASKS", "Get all tasks."},
    {"GET_TICKETS", "Get all tickets."},
    {"GET_TLS_CERTIFICATES", "Get all TLS certificates."},
    {"GET_USERS", "Get all users."},
    {"GET_VERSION", "Get the Greenbone Management Protocol version."},
    {"GET_VULNS", "Get all vulnerabilities."},
    {"HELP", "Get this help text."},
    {"MODIFY_ALERT", "Modify an existing alert."},
    {"MODIFY_ASSET", "Modify an existing asset."},
    {"MODIFY_AUTH", "Modify the authentication methods."},
    {"MODIFY_CONFIG", "Update an existing config."},
    {"MODIFY_CREDENTIAL", "Modify an existing credential."},
    {"MODIFY_FILTER", "Modify an existing filter."},
    {"MODIFY_GROUP", "Modify an existing group."},
    {"MODIFY_LICENSE", "Modify the existing license."},
    {"MODIFY_NOTE", "Modify an existing note."},
    {"MODIFY_OVERRIDE", "Modify an existing override."},
    {"MODIFY_PERMISSION", "Modify an existing permission."},
    {"MODIFY_PORT_LIST", "Modify an existing port list."},
    {"MODIFY_REPORT_CONFIG", "Modify an existing report config."},
    {"MODIFY_REPORT_FORMAT", "Modify an existing report format."},
    {"MODIFY_ROLE", "Modify an existing role."},
    {"MODIFY_SCANNER", "Modify an existing scanner."},
    {"MODIFY_SCHEDULE", "Modify an existing schedule."},
    {"MODIFY_SETTING", "Modify an existing setting."},
    {"MODIFY_TAG", "Modify an existing tag."},
    {"MODIFY_TARGET", "Modify an existing target."},
    {"MODIFY_TASK", "Update an existing task."},
    {"MODIFY_TICKET", "Modify an existing ticket."},
    {"MODIFY_TLS_CERTIFICATE", "Modify an existing TLS certificate."},
    {"MODIFY_USER", "Modify a user."},
    {"MOVE_TASK", "Assign task to another slave scanner, even while running."},
    {"RESTORE", "Restore a resource."},
    {"RESUME_TASK", "Resume a stopped task."},
    {"RUN_WIZARD", "Run a wizard."},
    {"START_TASK", "Manually start an existing task."},
    {"STOP_TASK", "Stop a running task."},
    {"SYNC_CONFIG", "Synchronize a config with a scanner."},
    {"TEST_ALERT", "Run an alert."},
    {"VERIFY_REPORT_FORMAT", "Verify a report format."},
    {"VERIFY_SCANNER", "Verify a scanner."},
    {NULL, NULL}};

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

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

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


/* General helpers. */

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

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

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

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

/**
 * @brief Find a resource in the trashcan given a UUID.
 *
 * @param[in]   type      Type of resource.
 * @param[in]   uuid      UUID of resource.
 * @param[out]  resource  Resource return, 0 if 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;
    else
      vector++;
  return NULL;
}

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

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

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

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

/**
 * @brief Setup for an option process.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Database.
 *
 * @return 0 success, -1 error, -2 database is 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 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);
  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++;
    }
}


/* Filter utilities. */

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

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

/**
 * @brief Get whether a keyword is special (like "and").
 *
 * @param[in]  keyword  Keyword.
 *
 * @return 1 if special, else 0.
 */
int
keyword_special (keyword_t *keyword)
{
  if (keyword->string)
    return (strcmp (keyword->string, "and") == 0)
           || (strcmp (keyword->string, "or") == 0)
           || (strcmp (keyword->string, "not") == 0)
           || (strcmp (keyword->string, "re") == 0)
           || (strcmp (keyword->string, "regexp") == 0);
  return 0;
}

/**
 * @brief Parse a filter column relation.
 *
 * @param[in]  relation  Filter relation.
 *
 * @return keyword relation
 */
static keyword_relation_t
parse_column_relation (const char relation)
{
  switch (relation)
    {
      case '=': return KEYWORD_RELATION_COLUMN_EQUAL;
      case '~': return KEYWORD_RELATION_COLUMN_APPROX;
      case '>': return KEYWORD_RELATION_COLUMN_ABOVE;
      case '<': return KEYWORD_RELATION_COLUMN_BELOW;
      case ':': return KEYWORD_RELATION_COLUMN_REGEXP;
      default:  return KEYWORD_RELATION_COLUMN_APPROX;
    }
}

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

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

  /* Special values to substitute */

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

  /* The type. */

  string = keyword->string;
  if (*string == '\0')
    {
      keyword->type = KEYWORD_TYPE_STRING;
      return;
    }
  if (*string && *string == '-' && strlen (string) > 1) string++;
  digits = 0;
  while (*string && isdigit (*string))
    {
      digits = 1;
      string++;
    }
  if (digits == 0)
    keyword->type = KEYWORD_TYPE_STRING;
  else if (*string)
    {
      struct tm date;
      gchar next;
      int parsed_integer;
      double parsed_double;
      char dummy[2];
      memset (&date, 0, sizeof (date));
      next = *(string + 1);
      if (next == '\0' && *string == 's')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + atoi (keyword->string);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'm')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 60);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'h')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 3600);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'd')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + (atoi (keyword->string) * 86400);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'w')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = now + atoi (keyword->string) * 604800;
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'M')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = add_months (now, atoi (keyword->string));
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (next == '\0' && *string == 'y')
        {
          time_t now;
          now = time (NULL);
          keyword->integer_value = add_months (now,
                                               atoi (keyword->string) * 12);
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      // Add cases for t%H:%M although it is incorrect sometimes it is easier
      // to call filter.lower on the frontend then it can happen that the
      // T indicator is lowered as well.
      else if (strptime (keyword->string, "%Y-%m-%dt%H:%M", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
          g_debug ("Parsed Y-m-dtH:M %s to timestamp %d.",
                   keyword->string, keyword->integer_value);
        }
      else if (strptime (keyword->string, "%Y-%m-%dt%Hh%M", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
          g_debug ("Parsed Y-m-dtHhM %s to timestamp %d.",
                   keyword->string, keyword->integer_value);
        }
      else if (strptime (keyword->string, "%Y-%m-%dT%H:%M", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
          g_debug ("Parsed Y-m-dTH:M %s to timestamp %d.",
                   keyword->string, keyword->integer_value);
        }
      // Add T%Hh%M for downwards compatible filter
      else if (strptime (keyword->string, "%Y-%m-%dT%Hh%M", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
          g_debug ("Parsed Y-m-dTHhM %s to timestamp %d.",
                   keyword->string, keyword->integer_value);
        }
      else if (memset (&date, 0, sizeof (date)),
               strptime (keyword->string, "%Y-%m-%d", &date))
        {
          keyword->integer_value = mktime (&date);
          keyword->type = KEYWORD_TYPE_INTEGER;
          g_debug ("Parsed Y-m-d %s to timestamp %d.",
                   keyword->string, keyword->integer_value);
        }
      else if (sscanf (keyword->string, "%d%1s", &parsed_integer, dummy) == 1)
        {
          keyword->integer_value = parsed_integer;
          keyword->type = KEYWORD_TYPE_INTEGER;
        }
      else if (sscanf (keyword->string, "%lf%1s", &parsed_double, dummy) == 1
               && parsed_double <= DBL_MAX)
        {
          keyword->double_value = parsed_double;
          keyword->type = KEYWORD_TYPE_DOUBLE;
        }
      else
        keyword->type = KEYWORD_TYPE_STRING;
    }
  else
    {
      keyword->integer_value = atoi (keyword->string);
      keyword->type = KEYWORD_TYPE_INTEGER;
    }
}

/**
 * @brief Cleans up keywords with special conditions and relations.
 *
 * @param[in]  keyword  Keyword to clean up.
 */
static void
cleanup_keyword (keyword_t *keyword)
{
  if (keyword->column == NULL)
    return;

  if (strcasecmp (keyword->column, "first") == 0)
    {
      /* "first" must be >= 1 */
      if (keyword->integer_value <= 0)
        {
          g_free (keyword->string);
          keyword->integer_value = 1;
          keyword->string = g_strdup ("1");
        }
      keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
    }
  else if (strcasecmp (keyword->column, "rows") == 0)
    {
      /* rows must be >= 1 or a special value (-1 or -2) */
      if (keyword->integer_value == 0)
        {
          g_free (keyword->string);
          keyword->integer_value = 1;
          keyword->string = g_strdup ("1");
        }
      else if (keyword->integer_value < -2)
        {
          g_free (keyword->string);
          keyword->integer_value = -1;
          keyword->string = g_strdup ("-1");
        }
      keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
    }
  else if (strcasecmp (keyword->column, "min_qod") == 0)
    {
      /* min_qod must be a percentage (between 0 and 100) */
      if (keyword->integer_value < 0)
        {
          g_free (keyword->string);
          keyword->integer_value = 0;
          keyword->string = g_strdup ("0");
        }
      else if (keyword->integer_value > 100)
        {
          g_free (keyword->string);
          keyword->integer_value = 100;
          keyword->string = g_strdup ("100");
        }
      keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
    }
  else if (strcasecmp (keyword->column, "apply_overrides") == 0
           || strcasecmp (keyword->column, "overrides") == 0
           || strcasecmp (keyword->column, "notes") == 0
           || strcasecmp (keyword->column, "result_hosts_only") == 0)
    {
      /* Boolean options (0 or 1) */
      if (keyword->integer_value != 0 && keyword->integer_value != 1)
        {
          g_free (keyword->string);
          keyword->integer_value = 1;
          keyword->string = g_strdup ("1");
        }
      keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
    }
  else if (strcasecmp (keyword->column, "delta_states") == 0
           || strcasecmp (keyword->column, "levels") == 0
           || strcasecmp (keyword->column, "sort") == 0
           || strcasecmp (keyword->column, "sort-reverse") == 0)
    {
      /* Text options */
      keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL;
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return 1;
}

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

/**
 * @brief Flag to control the default sorting produced by split_filter.
 *
 * If this is true, and the filter does not specify a sort field, then
 * split_filter will not insert a default sort term, so that the random
 * (and fast) table order in the database will be used.
 */
static int table_order_if_sort_not_specified = 0;

/**
 * @brief Ensure filter parts contains the special keywords.
 *
 * @param[in]  parts         Array of keyword strings.
 * @param[in]  given_filter  Filter term.
 */
void
split_filter_add_specials (array_t *parts, const gchar* given_filter)
{
  int index, first, max, sort;
  keyword_t *keyword;

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

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

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

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

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

  assert (given_filter);

  /* Collect the filter terms in an array. */

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

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

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

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

  /* Make sure the special keywords appear in the array. */

  split_filter_add_specials (parts, given_filter);

  array_add (parts, NULL);

  return parts;
}

/**
 * @brief Get info from a filter.
 *
 * It's up to the caller to ensure that max is adjusted for Max Rows Per Page
 * (by calling manage_max_rows).
 *
 * @param[in]   filter      Filter.
 * @param[out]  first       Number of first item.
 * @param[out]  max         Max number of rows.
 * @param[out]  sort_field  Sort field.
 * @param[out]  sort_order  Sort order.
 */
void
manage_filter_controls (const gchar *filter, int *first, int *max,
                        gchar **sort_field, int *sort_order)
{
  keyword_t **point;
  array_t *split;

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

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

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

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

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "rows") == 0))
            {
              *max = atoi (keyword->string);
              if (*max == -2)
                setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max);
              else if (*max < 1)
                *max = -1;
              break;
            }
          point++;
        }
    }

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

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

  filter_free (split);
  return;
}

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

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

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

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

/**
 * @brief Get info from a result filter for a report.
 *
 * It's up to the caller to ensure that max is adjusted for Max Rows Per Page
 * (by calling manage_max_rows).
 *
 * @param[in]   filter      Filter.
 * @param[out]  first       Number of first item.
 * @param[out]  max         Max number of rows.
 * @param[out]  sort_field  Sort field.
 * @param[out]  sort_order  Sort order.
 * @param[out]  result_hosts_only  Whether to show only hosts with results.
 * @param[out]  min_qod        Minimum QoD base of included results.  All
 *                              results if NULL.
 * @param[out]  levels         String describing threat levels (message types)
 *                             to include in count (for example, "hmlg" for
 *                             High, Medium, Low and loG). All levels if NULL.
 * @param[out]  compliance_levels   String describing compliance levels
 *                             to include in count (for example, "yniu" for
 *                             "yes" (compliant), "n" for "no" (not compliant),
 *                             "i" for "incomplete" and "u" for "undefined" 
 *                              (without compliance information). 
 *                              All levels if NULL.
 * @param[out]  delta_states   String describing delta states to include in count
 *                             (for example, "sngc" Same, New, Gone and Changed).
 *                             All levels if NULL.
 * @param[out]  search_phrase      Phrase that results must include.  All results
 *                                 if NULL or "".
 * @param[out]  search_phrase_exact  Whether search phrase is exact.
 * @param[out]  notes              Whether to include notes.
 * @param[out]  overrides          Whether to include overrides.
 * @param[out]  apply_overrides    Whether to apply overrides.
 * @param[out]  zone               Timezone.
 */
void
manage_report_filter_controls (const gchar *filter, int *first, int *max,
                               gchar **sort_field, int *sort_order,
                               int *result_hosts_only, gchar **min_qod,
                               gchar **levels, gchar **compliance_levels,
                               gchar **delta_states, gchar **search_phrase, 
                               int *search_phrase_exact, int *notes, 
                               int *overrides, int *apply_overrides, 
                               gchar **zone)
{
  keyword_t **point;
  array_t *split;
  int val;
  gchar *string;

  if (filter == NULL)
    return;

  split = split_filter (filter);

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

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

      (*first)--;
    }

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

          keyword = *point;
          if (keyword->column && (strcmp (keyword->column, "rows") == 0))
            {
              *max = atoi (keyword->string);
              if (*max == -2)
                setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max);
              else if (*max < 1)
                *max = -1;
              break;
            }
          point++;
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

  filter_free (split);
  return;
}

/**
 * @brief Append relation to filter.
 *
 * @param[in]  clean     Filter.
 * @param[in]  keyword   Keyword
 * @param[in]  relation  Relation char.
 */
static void
append_relation (GString *clean, keyword_t *keyword, const char relation)
{
  if (strcmp (keyword->column, "rows") == 0)
    {
      int max;

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

      g_string_append_printf (clean,
                              " %s%c%i",
                              keyword->column,
                              relation,
                              manage_max_rows (max));
    }
  else if (keyword->quoted)
    g_string_append_printf (clean,
                            " %s%c\"%s\"",
                            keyword->column,
                            relation,
                            keyword->string);
  else
    g_string_append_printf (clean,
                            " %s%c%s",
                            keyword->column,
                            relation,
                            keyword->string);
}

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

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

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

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

            case KEYWORD_RELATION_APPROX:
              if (keyword->quoted)
                g_string_append_printf (clean, " \"%s\"", keyword->string);
              else
                g_string_append_printf (clean, " %s", keyword->string);
              break;
          }
      else
        {
          const char *relation_symbol;
          if (keyword->equal)
            relation_symbol = "=";
          else if (keyword->approx)
            relation_symbol = "~";
          else
            relation_symbol = "";

          if (keyword->quoted)
            g_string_append_printf (clean, " %s\"%s\"",
                                    relation_symbol,
                                    keyword->string);
          else
            g_string_append_printf (clean, " %s%s",
                                    relation_symbol,
                                    keyword->string);
        }
      point++;
    }
  filter_free (split);
  return g_strstrip (g_string_free (clean, FALSE));
}

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

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

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

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

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

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

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

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

/**
 * @brief Check whether a keyword applies to a column.
 *
 * @param[in]  keyword  Keyword.
 * @param[in]  column   Column.
 *
 * @return 1 if applies, else 0.
 */
static int
keyword_applies_to_column (keyword_t *keyword, const char* column)
{
  if ((strcmp (column, "threat") == 0)
      && (strstr ("None", keyword->string) == NULL)
      && (strstr ("False Positive", keyword->string) == NULL)
      && (strstr ("Error", keyword->string) == NULL)
      && (strstr ("Alarm", keyword->string) == NULL)
      && (strstr ("High", keyword->string) == NULL)
      && (strstr ("Medium", keyword->string) == NULL)
      && (strstr ("Low", keyword->string) == NULL)
      && (strstr ("Log", keyword->string) == NULL))
    return 0;
  if ((strcmp (column, "trend") == 0)
      && (strstr ("more", keyword->string) == NULL)
      && (strstr ("less", keyword->string) == NULL)
      && (strstr ("up", keyword->string) == NULL)
      && (strstr ("down", keyword->string) == NULL)
      && (strstr ("same", keyword->string) == NULL))
    return 0;
  if ((strcmp (column, "status") == 0)
      && (strstr ("Delete Requested", keyword->string) == NULL)
      && (strstr ("Ultimate Delete Requested", keyword->string) == NULL)
      && (strstr ("Done", keyword->string) == NULL)
      && (strstr ("New", keyword->string) == NULL)
      && (strstr ("Running", keyword->string) == NULL)
      && (strstr ("Queued", keyword->string) == NULL)
      && (strstr ("Stop Requested", keyword->string) == NULL)
      && (strstr ("Stopped", keyword->string) == NULL)
      && (strstr ("Interrupted", keyword->string) == NULL)
      && (strstr ("Processing", keyword->string) == NULL))
    return 0;
  return 1;
}

/**
 * @brief Append parts for a "tag" keyword to a filter clause.
 *
 * @param[in,out] clause      Buffer for the filter clause to append to.
 * @param[in]  keyword        The keyword to create the filter clause part for.
 * @param[in]  type           The resource type.
 * @param[in]  first_keyword  Whether keyword is first.
 * @param[in]  last_was_and   Whether last keyword was "and".
 * @param[in]  last_was_not   Whether last keyword was "not".
 */
static void
filter_clause_append_tag (GString *clause, keyword_t *keyword,
                          const char *type, int first_keyword,
                          int last_was_and, int last_was_not)
{
  gchar *quoted_keyword;
  gchar **tag_split, *tag_name, *tag_value;
  int value_given;

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

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

  if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL
      || keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE
      || keyword->relation == KEYWORD_RELATION_COLUMN_BELOW)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.name = '%s'"
          "   AND tags.active != 0"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)"
          "   %s%s%s))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          tag_name,
          type,
          type,
          (value_given
            ? "AND tags.value = '"
            : ""),
          value_given ? tag_value : "",
          (value_given
            ? "'"
            : ""));
    }
  else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.name %s '%%%%%s%%%%'"
          "   AND tags.active != 0"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)"
          "   AND tags.value %s '%%%%%s%%%%'))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          sql_ilike_op (),
          tag_name,
          type,
          type,
          sql_ilike_op (),
          tag_value);
    }
  else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.name %s '%s'"
          "   AND tags.active != 0"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)"
          "   AND tags.value"
          "       %s '%s'))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          sql_regexp_op (),
          tag_name,
          type,
          type,
          sql_regexp_op (),
          tag_value);
    }

  g_free (quoted_keyword);
  g_strfreev(tag_split);
  g_free(tag_name);
  g_free(tag_value);
}

/**
 * @brief Append parts for a "tag_id" keyword to a filter clause.
 *
 * @param[in,out] clause      Buffer for the filter clause to append to.
 * @param[in]  keyword        The keyword to create the filter clause part for.
 * @param[in]  type           The resource type.
 * @param[in]  first_keyword  Whether keyword is first.
 * @param[in]  last_was_and   Whether last keyword was "and".
 * @param[in]  last_was_not   Whether last keyword was "not".
 */
static void
filter_clause_append_tag_id (GString *clause, keyword_t *keyword,
                             const char *type, int first_keyword,
                             int last_was_and, int last_was_not)
{
  gchar *quoted_keyword;

  quoted_keyword = sql_quote (keyword->string);

  if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL
      || keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE
      || keyword->relation == KEYWORD_RELATION_COLUMN_BELOW)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.uuid = '%s'"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          quoted_keyword,
          type,
          type);
    }
  else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.uuid %s '%%%%%s%%%%'"
          "   AND tags.active != 0"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          sql_ilike_op (),
          quoted_keyword,
          type,
          type);
    }
  else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP)
    {
      g_string_append_printf
         (clause,
          "%s"
          "(EXISTS"
          "  (SELECT * FROM tags"
          "   WHERE tags.uuid %s '%s'"
          "   AND tags.active != 0"
          "   AND user_has_access_uuid (CAST ('tag' AS text),"
          "                             CAST (tags.uuid AS text),"
          "                             CAST ('get_tags' AS text),"
          "                             0)"
          "   AND EXISTS (SELECT * FROM tag_resources"
          "                WHERE tag_resources.resource_uuid"
          "                        = %ss.uuid"
          "                  AND tag_resources.resource_type"
          "                        = '%s'"
          "                  AND tag = tags.id)))",
          get_join (first_keyword, last_was_and,
                    last_was_not),
          sql_regexp_op (),
          quoted_keyword,
          type,
          type);
    }

  g_free (quoted_keyword);
}

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

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

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

  if (permissions)
    *permissions = make_array ();

  if (owner_filter)
    *owner_filter = NULL;

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

  if (max_return)
    *max_return = -2;

  clause = g_string_new ("");
  order = g_string_new ("");
  /* NB This may add terms that are missing, like "sort". */
  split = split_filter (filter);
  point = (keyword_t**) split->pdata;
  first_keyword = 1;
  last_was_and = 0;
  last_was_not = 0;
  last_was_re = 0;
  first_order = 1;
  while (*point)
    {
      gchar *quoted_keyword;
      int index;
      keyword_t *keyword;

      skip = 0;

      keyword = *point;

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

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

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

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

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

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

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

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

          if (first_order)
            {
              if ((strcmp (type, "report") == 0)
                  && (strcmp (keyword->string, "status") == 0))
                g_string_append_printf
                 (order,
                  " ORDER BY"
                  "  (CASE WHEN (SELECT target = 0 FROM tasks"
                  "              WHERE tasks.id = task)"
                  "    THEN 'Container'"
                  "    ELSE run_status_name (scan_run_status)"
                  "         || (SELECT CAST (temp / 100 AS text)"
                  "                    || CAST (temp / 10 AS text)"
                  "                    || CAST (temp %% 10 as text)"
                  "             FROM (SELECT report_progress (id) AS temp)"
                  "                  AS temp_sub)"
                  "    END)"
                  " ASC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "status") == 0))
                g_string_append_printf
                 (order,
                  " ORDER BY"
                  "  (CASE WHEN target = 0"
                  "    THEN 'Container'"
                  "    ELSE run_status_name (run_status)"
                  "         || (SELECT CAST (temp / 100 AS text)"
                  "                    || CAST (temp / 10 AS text)"
                  "                    || CAST (temp %% 10 as text)"
                  "             FROM (SELECT report_progress (id) AS temp"
                  "                   FROM reports"
                  "                   WHERE task = tasks.id"
                  "                   ORDER BY creation_time DESC LIMIT 1)"
                  "                  AS temp_sub)"
                  "    END)"
                  " ASC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_threat (%s) ASC",
                                          column);
                }
              else if (strcmp (keyword->string, "severity") == 0
                       || strcmp (keyword->string, "original_severity") == 0
                       || strcmp (keyword->string, "cvss") == 0
                       || strcmp (keyword->string, "cvss_base") == 0
                       || strcmp (keyword->string, "max_cvss") == 0
                       || strcmp (keyword->string, "fp_per_host") == 0
                       || strcmp (keyword->string, "log_per_host") == 0
                       || strcmp (keyword->string, "low_per_host") == 0
                       || strcmp (keyword->string, "medium_per_host") == 0
                       || strcmp (keyword->string, "high_per_host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  g_string_append_printf (order,
                                          " ORDER BY CASE CAST (%s AS text)"
                                          " WHEN '' THEN '-Infinity'::real"
                                          " ELSE coalesce(%s::real,"
                                          "               '-Infinity'::real)"
                                          " END ASC",
                                          column,
                                          column);
                }
              else if (strcmp (keyword->string, "roles") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY"
                                          " CASE WHEN %s %s 'Admin.*'"
                                          " THEN '0' || %s"
                                          " ELSE '1' || %s END ASC",
                                          column,
                                          sql_regexp_op (),
                                          column,
                                          column);
                }
              else if ((strcmp (keyword->string, "created") == 0)
                       || (strcmp (keyword->string, "modified") == 0)
                       || (strcmp (keyword->string, "published") == 0)
                       || (strcmp (keyword->string, "qod") == 0)
                       || (strcmp (keyword->string, "cves") == 0)
                       || (strcmp (keyword->string, "high") == 0)
                       || (strcmp (keyword->string, "medium") == 0)
                       || (strcmp (keyword->string, "low") == 0)
                       || (strcmp (keyword->string, "log") == 0)
                       || (strcmp (keyword->string, "false_positive") == 0)
                       || (strcmp (keyword->string, "hosts") == 0)
                       || (strcmp (keyword->string, "result_hosts") == 0)
                       || (strcmp (keyword->string, "results") == 0)
                       || (strcmp (keyword->string, "latest_severity") == 0)
                       || (strcmp (keyword->string, "highest_severity") == 0)
                       || (strcmp (keyword->string, "average_severity") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY %s ASC",
                                          column);
                }
              else if ((strcmp (keyword->string, "ips") == 0)
                       || (strcmp (keyword->string, "total") == 0)
                       || (strcmp (keyword->string, "tcp") == 0)
                       || (strcmp (keyword->string, "udp") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) ASC",
                                          column);
                }
              else if (strcmp (keyword->string, "ip") == 0
                       || strcmp (keyword->string, "host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_inet (%s) ASC",
                                          column);
                }
              else if ((strcmp (type, "note")
                        && strcmp (type, "override"))
                       || (strcmp (keyword->string, "nvt")
                           && strcmp (keyword->string, "name")))
                {
                  gchar *column;
                  keyword_type_t column_type;
                  column = columns_select_column_with_type (select_columns,
                                                            where_columns,
                                                            keyword->string,
                                                            &column_type);
                  assert (column);
                  if (column_type == KEYWORD_TYPE_INTEGER)
                    g_string_append_printf (order,
                                            " ORDER BY"
                                            " cast (%s AS bigint) ASC",
                                            column);
                  else if (column_type == KEYWORD_TYPE_DOUBLE)
                    g_string_append_printf (order,
                                            " ORDER BY"
                                            " cast (%s AS real) ASC",
                                            column);
                  else
                    g_string_append_printf (order, " ORDER BY lower (%s) ASC",
                                            column);
                }
              else
                /* Special case for notes text sorting. */
                g_string_append_printf (order,
                                        " ORDER BY nvt ASC,"
                                        "          lower (%ss%s.text) ASC",
                                        type,
                                        trash ? "_trash" : "");
              first_order = 0;
            }
          else
            /* To help the client split_filter restricts the filter to one
             * sorting term, preventing this from happening. */
            g_string_append_printf (order, ", %s ASC",
                                    keyword->string);
          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "sort-reverse") == 0))
        {
          if (vector_find_filter (filter_columns, keyword->string) == 0)
            {
              point++;
              continue;
            }

          if (first_order)
            {
              if ((strcmp (type, "report") == 0)
                  && (strcmp (keyword->string, "status") == 0))
                g_string_append_printf
                 (order,
                  " ORDER BY"
                  "  (CASE WHEN (SELECT target = 0 FROM tasks"
                  "              WHERE tasks.id = task)"
                  "    THEN 'Container'"
                  "    ELSE run_status_name (scan_run_status)"
                  "         || (SELECT CAST (temp / 100 AS text)"
                  "                    || CAST (temp / 10 AS text)"
                  "                    || CAST (temp %% 10 as text)"
                  "             FROM (SELECT report_progress (id) AS temp)"
                  "                  AS temp_sub)"
                  "    END)"
                  " DESC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "status") == 0))
                g_string_append_printf
                 (order,
                  " ORDER BY"
                  "  (CASE WHEN target = 0"
                  "    THEN 'Container'"
                  "    ELSE run_status_name (run_status)"
                  "         || (SELECT CAST (temp / 100 AS text)"
                  "                    || CAST (temp / 10 AS text)"
                  "                    || CAST (temp %% 10 as text)"
                  "             FROM (SELECT report_progress (id) AS temp"
                  "                   FROM reports"
                  "                   WHERE task = tasks.id"
                  "                   ORDER BY creation_time DESC LIMIT 1)"
                  "                  AS temp_sub)"
                  "    END)"
                  " DESC");
              else if ((strcmp (type, "task") == 0)
                       && (strcmp (keyword->string, "threat") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_threat (%s) DESC",
                                          column);
                }
              else if (strcmp (keyword->string, "severity") == 0
                       || strcmp (keyword->string, "original_severity") == 0
                       || strcmp (keyword->string, "cvss") == 0
                       || strcmp (keyword->string, "cvss_base") == 0
                       || strcmp (keyword->string, "max_cvss") == 0
                       || strcmp (keyword->string, "fp_per_host") == 0
                       || strcmp (keyword->string, "log_per_host") == 0
                       || strcmp (keyword->string, "low_per_host") == 0
                       || strcmp (keyword->string, "medium_per_host") == 0
                       || strcmp (keyword->string, "high_per_host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  g_string_append_printf (order,
                                          " ORDER BY CASE CAST (%s AS text)"
                                          " WHEN '' THEN '-Infinity'::real"
                                          " ELSE coalesce(%s::real,"
                                          "               '-Infinity'::real)"
                                          " END DESC",
                                          column,
                                          column);
                }
              else if (strcmp (keyword->string, "roles") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY"
                                          " CASE WHEN %s %s 'Admin.*'"
                                          " THEN '0' || %s"
                                          " ELSE '1' || %s END DESC",
                                          column,
                                          sql_regexp_op (),
                                          column,
                                          column);
                }
              else if ((strcmp (keyword->string, "created") == 0)
                       || (strcmp (keyword->string, "modified") == 0)
                       || (strcmp (keyword->string, "published") == 0)
                       || (strcmp (keyword->string, "qod") == 0)
                       || (strcmp (keyword->string, "cves") == 0)
                       || (strcmp (keyword->string, "high") == 0)
                       || (strcmp (keyword->string, "medium") == 0)
                       || (strcmp (keyword->string, "low") == 0)
                       || (strcmp (keyword->string, "log") == 0)
                       || (strcmp (keyword->string, "false_positive") == 0)
                       || (strcmp (keyword->string, "hosts") == 0)
                       || (strcmp (keyword->string, "result_hosts") == 0)
                       || (strcmp (keyword->string, "results") == 0)
                       || (strcmp (keyword->string, "latest_severity") == 0)
                       || (strcmp (keyword->string, "highest_severity") == 0)
                       || (strcmp (keyword->string, "average_severity") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY %s DESC",
                                          column);
                }
              else if ((strcmp (keyword->string, "ips") == 0)
                       || (strcmp (keyword->string, "total") == 0)
                       || (strcmp (keyword->string, "tcp") == 0)
                       || (strcmp (keyword->string, "udp") == 0))
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY CAST (%s AS INTEGER) DESC",
                                          column);
                }
              else if (strcmp (keyword->string, "ip") == 0
                       || strcmp (keyword->string, "host") == 0)
                {
                  gchar *column;
                  column = columns_select_column (select_columns,
                                                  where_columns,
                                                  keyword->string);
                  assert (column);
                  g_string_append_printf (order,
                                          " ORDER BY order_inet (%s) DESC",
                                          column);
                }
              else if ((strcmp (type, "note")
                        && strcmp (type, "override"))
                       || (strcmp (keyword->string, "nvt")
                           && strcmp (keyword->string, "name")))
                {
                  gchar *column;
                  keyword_type_t column_type;
                  column = columns_select_column_with_type (select_columns,
                                                            where_columns,
                                                            keyword->string,
                                                            &column_type);
                  assert (column);
                  if (column_type == KEYWORD_TYPE_INTEGER)
                    g_string_append_printf (order,
                                            " ORDER BY"
                                            " cast (%s AS bigint) DESC",
                                            column);
                  else if (column_type == KEYWORD_TYPE_DOUBLE)
                    g_string_append_printf (order,
                                            " ORDER BY"
                                            " cast (%s AS real) DESC",
                                            column);
                  else
                    g_string_append_printf (order, " ORDER BY lower (%s) DESC",
                                            column);
                }
              else
                /* Special case for notes text sorting. */
                g_string_append_printf (order,
                                        " ORDER BY nvt DESC,"
                                        "          lower (%ss%s.text) DESC",
                                        type,
                                        trash ? "_trash" : "");
              first_order = 0;
            }
          else
            /* To help the client split_filter restricts the filter to one
             * sorting term, preventing this from happening. */
            g_string_append_printf (order, ", %s DESC",
                                    keyword->string);
          point++;
          continue;
        }
      else if (keyword->column
               && (strcasecmp (keyword->column, "first") == 0))
        {
          if (first_return)
            {
              /* Subtract 1 to switch from 1 to 0 indexing. */
              *first_return = atoi (keyword->string) - 1;
              if (*first_return < 0)
                *first_return = 0;
            }

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

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

          point++;
          continue;
        }
      /* Add tag criteria to clause: tag name with optional value */
      else if (keyword->column
               && (strcasecmp (keyword->column, "tag") == 0))
        {
          quoted_keyword = NULL;

          filter_clause_append_tag (clause, keyword, type,
                                    first_keyword, last_was_and, last_was_not);

          first_keyword = 0;
          last_was_and = 0;
          last_was_not = 0;

          point++;
          continue;
        }
      /* Add criteria for tag_id to clause */
      else if (keyword->column
               && (strcasecmp (keyword->column, "tag_id") == 0))
        {
          quoted_keyword = NULL;

          filter_clause_append_tag_id (clause, keyword, type, first_keyword,
                                       last_was_and, last_was_not);

          first_keyword = 0;
          last_was_and = 0;
          last_was_not = 0;

          point++;
          continue;
        }

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

      quoted_keyword = NULL;

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

          if (keyword->column
              && (strlen (keyword->column) > 3)
              && (strcmp (keyword->column + strlen (keyword->column) - 3, "_id")
                  == 0)
              && strcasecmp (keyword->column, "nvt_id")
              /* Tickets have a custom result_id column. */
              && strcasecmp (keyword->column, "result_id"))
            {
              gchar *type_term;

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

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

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

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

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

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

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

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

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

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

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

          /* Keyword like "=example". */

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

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

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

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

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

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

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

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

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

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

                if (keyword_applies_to_column (keyword, filter_column)
                    && select_column && column_type_matches)
                  {
                    if (last_was_re)
                      g_string_append_printf (clause,
                                              "%s"
                                              "(%s IS NULL"
                                              " OR NOT (CAST (%s AS TEXT)"
                                              "         %s '%s'))",
                                              (index ? " AND " : ""),
                                              select_column,
                                              select_column,
                                              sql_regexp_op (),
                                              quoted_keyword);
                    else
                      g_string_append_printf (clause,
                                              "%s"
                                              "(%s IS NULL"
                                              " OR CAST (%s AS TEXT)"
                                              "    NOT %s '%%%s%%')",
                                              (index ? " AND " : ""),
                                              select_column,
                                              select_column,
                                              sql_ilike_op (),
                                              quoted_keyword);
                  }
                else
                  g_string_append_printf (clause,
                                          "%s t ()",
                                          (index ? " AND " : ""));
              }
          else
            for (index = 0;
                 (filter_column = filter_columns[index]) != NULL;
                 index++)
              {
                gchar *select_column;
                keyword_type_t column_type;
                int column_type_matches = 0;

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

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

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

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

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

      *max_return = manage_max_rows (*max_return);
    }

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


/* Resources. */

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

/**
 * @brief Check whether a resource subtype name is valid.
 *
 * @param[in]  subtype  Subtype of resource.
 *
 * @return 1 yes, 0 no.
 */
int
valid_subtype (const char* type)
{
  #if COMPLIANCE_REPORTS == 1
    return (strcasecmp (type, "audit_report") == 0)
          || (strcasecmp (type, "audit") == 0)
          || (strcasecmp (type, "policy") == 0);
  #else
    return (strcasecmp (type, "audit") == 0)
          || (strcasecmp (type, "policy") == 0);
  #endif
}

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

  if (valid_type (type))
    return type;

  if (strcasecmp (type, "Alert") == 0)
    return "alert";
  if (strcasecmp (type, "Asset") == 0)
    return "asset";
  if (strcasecmp (type, "Config") == 0)
    return "config";
  if (strcasecmp (type, "Credential") == 0)
    return "credential";
  if (strcasecmp (type, "Filter") == 0)
    return "filter";
  if (strcasecmp (type, "Note") == 0)
    return "note";
  if (strcasecmp (type, "Override") == 0)
    return "override";
  if (strcasecmp (type, "Permission") == 0)
    return "permission";
  if (strcasecmp (type, "Port List") == 0)
    return "port_list";
  if (strcasecmp (type, "Report") == 0)
    return "report";
  if (strcasecmp (type, "Report Config") == 0)
    return "report_config";
  if (strcasecmp (type, "Report Format") == 0)
    return "report_format";
  if (strcasecmp (type, "Result") == 0)
    return "result";
  if (strcasecmp (type, "Role") == 0)
    return "role";
  if (strcasecmp (type, "Scanner") == 0)
    return "scanner";
  if (strcasecmp (type, "Schedule") == 0)
    return "schedule";
  if (strcasecmp (type, "Tag") == 0)
    return "tag";
  if (strcasecmp (type, "Target") == 0)
    return "target";
  if (strcasecmp (type, "Task") == 0)
    return "task";
  if (strcasecmp (type, "Ticket") == 0)
    return "ticket";
  if (strcasecmp (type, "TLS Certificate") == 0)
    return "tls_certificate";
  if (strcasecmp (type, "SecInfo") == 0)
    return "info";
  return NULL;
}

/**
 * @brief Check whether a resource type is an asset subtype.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_is_asset_subtype (const char *type)
{
  return (strcasecmp (type, "host")
          && strcasecmp (type, "os"))
         == 0;
}

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

/**
 * @brief Check whether a resource type is a report subtype.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_is_report_subtype (const char *type)
{
  return (strcasecmp (type, "audit_report") == 0);
}

/**
 * @brief Check whether a resource type is a task subtype.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_is_task_subtype (const char *type)
{
  return (strcasecmp (type, "audit") == 0);
}

/**
 * @brief Check whether a resource type is a config subtype.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_is_config_subtype (const char *type)
{
  return (strcasecmp (type, "policy") == 0);
}

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

/**
 * @brief Check whether a type must have globally unique names.
 *
 * @param[in]  type          Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_globally_unique (const char *type)
{
  if (strcasecmp (type, "user") == 0)
    return 1;
  else
    return 0;
}

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

/**
 * @brief Check whether a resource type uses the trashcan.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_has_trash (const char *type)
{
  return strcasecmp (type, "report")
         && strcasecmp (type, "result")
         && strcasecmp (type, "info")
         && type_is_info_subtype (type) == 0
         && strcasecmp (type, "vuln")
         && strcasecmp (type, "user")
         && strcasecmp (type, "tls_certificate");
}

/**
 * @brief Check whether a resource type has an owner.
 *
 * @param[in]  type  Type of resource.
 *
 * @return 1 yes, 0 no.
 */
static int
type_owned (const char* type)
{
  return strcasecmp (type, "info")
         && type_is_info_subtype (type) == 0
         && strcasecmp (type, "vuln");
}

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

/* 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.
 */
static gboolean
find_resource_by_name (const char* type, const char* name, resource_t *resource)
{
  gchar *quoted_name;
  quoted_name = sql_quote (name);
  // TODO should really check type
  switch (sql_int64 (resource,
                     "SELECT id FROM %ss WHERE name = '%s'"
                     " ORDER BY id DESC;",
                     type,
                     quoted_name))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        *resource = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_name);
        return TRUE;
        break;
    }

  g_free (quoted_name);
  return FALSE;
}

/**
 * @brief Find a resource given a UUID and a permission.
 *
 * @param[in]   type        Type of resource.
 * @param[in]   name        Name of resource.
 * @param[out]  resource    Resource return, 0 if successfully failed to find
 *                          resource.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find resource), TRUE on
 *         error.
 */
static 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;

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

  return 0;
}

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

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

/**
 * @brief 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.
 */
static 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, &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.
 */
static int
init_get_iterator2 (iterator_t* iterator, const char *type,
                    const get_data_t *get, column_t *select_columns,
                    column_t *trash_select_columns,
                    column_t *where_columns,
                    column_t *trash_where_columns,
                    const char **filter_columns, int distinct,
                    const char *extra_tables,
                    const char *extra_where, 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.
 */
static int
count2 (const char *type, const get_data_t *get, column_t *select_columns,
        column_t *trash_select_columns, column_t *where_columns,
        column_t *trash_where_columns, const char **filter_columns,
        int distinct, const char *extra_tables, const char *extra_where,
        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, 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 ("DELETE FROM %s.meta WHERE name = 'database_version';",
       sql_schema ());
  sql ("INSERT INTO %s.meta (name, value)"
       " VALUES ('database_version', '%i');",
       sql_schema (),
       version);
}

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

  init_iterator (&iterator,
                 "SELECT id,"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'secret'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'password'),"
                 " (SELECT value FROM credentials_data"
                 "  WHERE credential = credentials.id"
                 "  AND type = 'private_key'),"
                 " (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);
  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);
  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);
}



/* Collation. */

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

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

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

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

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

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

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

      return 0;
    }

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

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


/* Events and Alerts. */

/**
 * @brief Check if any SecInfo alerts are due.
 */
void
check_alerts ()
{
  if (manage_scap_loaded ())
    {
      int max_time;

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

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

  if (manage_cert_loaded ())
    {
      int max_time;

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

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

/**
 * @brief Check if any SecInfo alerts are due.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 *
 * @return 0 success, -1 error,
 *         -2 database is too old, -3 database needs to be initialised
 *         from server, -5 database is too new.
 */
int
manage_check_alerts (GSList *log_config, const db_conn_info_t *database)
{
  int ret;

  g_info ("   Checking alerts.");

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

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

  check_alerts ();

  current_credentials.uuid = NULL;

  manage_option_cleanup ();

  return ret;
}

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

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

  assert (address);

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

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

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

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

  g_strfreev (split);
  return 0;
}

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

  assert (list);

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

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

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

  g_strfreev (split);
  return 0;
}

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

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

      if (g_regex_match_simple ("^(increased|decreased|changed)$",
                                data ? data : "",
                                0,
                                0)
          == 0)
        return 2;
    }
  else if (condition == ALERT_CONDITION_FILTER_COUNT_AT_LEAST)
    {
      if (strcmp (name, "filter_id") == 0)
        {
          filter_t filter;
          if (data == NULL)
            return 3;
          filter = 0;
          if (find_filter_with_permission (data, &filter, "get_filters"))
            return -1;
          if (filter == 0)
            return 3;
          return 0;
        }

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

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

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


  return 0;
}

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

      if (data == NULL)
        return 2;

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

/**
 * @brief Validate method data for the email method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 * @param[in]  for_modify      Whether to return error codes for modify_alert.
 *
 * @return 0 valid, 2 or 6: validation of email address failed,
 *         7 or 9 subject too long, 8 or 10 message too long,
 *         60 recipient credential not found, 61 invalid recipient credential
 *         type, -1 error. When for_modify is 0, the first code is returned,
 *         otherwise the second one.
 */
int
validate_email_data (alert_method_t method, const gchar *name, gchar **data,
                     int for_modify)
{
  if (method == ALERT_METHOD_EMAIL
      && strcmp (name, "to_address") == 0
      && validate_email_list (*data))
    return for_modify ? 6 : 2;

  if (method == ALERT_METHOD_EMAIL
      && strcmp (name, "from_address") == 0
      && validate_email (*data))
    return for_modify ? 6 : 2;

  if (method == ALERT_METHOD_EMAIL
      && strcmp (name, "subject") == 0
      && strlen (*data) > 80)
    return for_modify ? 9 : 7;

  if (method == ALERT_METHOD_EMAIL
      && strcmp (name, "message") == 0
      && strlen (*data) > max_email_message_length)
    return for_modify ? 10 : 8;

  if (method == ALERT_METHOD_EMAIL
      && strcmp (name, "recipient_credential") == 0
      && *data && strcmp (*data, ""))
    {
      credential_t credential;
      char *type;

      if (find_credential_with_permission (*data, &credential, NULL))
        return -1;
      else if (credential == 0)
        return 60;

      type = credential_type (credential);
      if (strcmp (type, "pgp")
          && strcmp (type, "smime"))
        {
          free (type);
          return 61;
        }
      free (type);
    }

  return 0;
}

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

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

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

          g_free (username);
        }
    }

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

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

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

  if (method == ALERT_METHOD_SCP
      && strcmp (name, "scp_port") == 0)
    {
      int port;

      port = atoi (*data);
      if (port <= 0 || port > 65535)
        return 16;
    }

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

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

  return 0;
}

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

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

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

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

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

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

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

  return 0;
}

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

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

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

              g_free (username);
            }
        }

      if (strcmp (name, "smb_share_path") == 0)
        {
          /* Check if share path has the correct format
           *  "\\<host>\<share>" */
          if (g_regex_match_simple ("^(?>\\\\\\\\|\\/\\/)[^:?<>|]+"
                                    "(?>\\\\|\\/)[^:?<>|]+$", *data, 0, 0)
              == FALSE)
            {
              return 41;
            }
        }

      if (strcmp (name, "smb_file_path") == 0)
        {
          /* Check if file path contains invalid characters:
           *  ":", "?", "<", ">", "|" */
          if (g_regex_match_simple ("^[^:?<>|]+$", *data, 0, 0)
              == FALSE)
            {
              return 42;
            }
          /* Check if a file or directory name ends with a dot,
           *  e.g. "../a", "abc/../xyz" or "abc/..". */
          else if (g_regex_match_simple ("^(?:.*\\.)(?:[\\/\\\\].*)*$",
                                         *data, 0, 0))
            {
              return 43;
            }
        }

    }

  return 0;
}

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

            }
        }

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

      if (strcmp (name, "tp_sms_tls_certificate") == 0)
        {
          // Check certificate, return 52 on failure
          int ret;
          gnutls_x509_crt_fmt_t crt_fmt;

          ret = get_certificate_info (*data, strlen(*data), FALSE,
                                      NULL, NULL, NULL,
                                      NULL, NULL, NULL, NULL, &crt_fmt);
          if (ret || crt_fmt != GNUTLS_X509_FMT_PEM)
            {
              return 52;
            }
        }

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

  return 0;
}

/**
 * @brief Validate method data for the vFire alert method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 70 credential not found, 71 invalid credential type
 */
static int
validate_vfire_data (alert_method_t method, const gchar *name,
                     gchar **data)
{
  if (method == ALERT_METHOD_VFIRE)
    {
      if (strcmp (name, "vfire_credential") == 0)
        {
          credential_t credential;
          if (find_credential_with_permission (*data, &credential,
                                               "get_credentials"))
            return -1;
          else if (credential == 0)
            return 70;
          else
            {
              char *cred_type = credential_type (credential);
              if (strcmp (cred_type, "up"))
                {
                  free (cred_type);
                  return 71;
                }
              free (cred_type);
            }
        }
    }
  return 0;
}

/**
 * @brief Validate method data for the Sourcefire method.
 *
 * @param[in]  method          Method that data corresponds to.
 * @param[in]  name            Name of data.
 * @param[in]  data            The data.
 *
 * @return 0 valid, 80 credential not found, 81 invalid credential type
 */
static int
validate_sourcefire_data (alert_method_t method, const gchar *name,
                          gchar **data)
{
  if (method == ALERT_METHOD_SOURCEFIRE)
    {
      if (strcmp (name, "pkcs12_credential") == 0)
        {
          credential_t credential;
          if (find_credential_with_permission (*data, &credential,
                                               "get_credentials"))
            return -1;
          else if (credential == 0)
            return 80;
          else
            {
              char *sourcefire_credential_type;
              sourcefire_credential_type = credential_type (credential);
              if (strcmp (sourcefire_credential_type, "up")
                  && strcmp (sourcefire_credential_type, "pw"))
                {
                  free (sourcefire_credential_type);
                  return 81;
                }
              free (sourcefire_credential_type);
            }
        }
    }

  return 0;
}

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

      if (condition == ALERT_CONDITION_SEVERITY_AT_LEAST
          || condition == ALERT_CONDITION_SEVERITY_CHANGED
          || condition == ALERT_CONDITION_FILTER_COUNT_CHANGED)
        return 21;
    }
  return 0;
}

/**
 * @brief Create an alert.
 *
 * @param[in]  name            Name of alert.
 * @param[in]  comment         Comment on alert.
 * @param[in]  filter_id       Filter.
 * @param[in]  active          Whether the alert is active.
 * @param[in]  event           Type of event.
 * @param[in]  event_data      Type-specific event data.
 * @param[in]  condition       Event condition.
 * @param[in]  condition_data  Condition-specific data.
 * @param[in]  method          Escalation method.
 * @param[in]  method_data     Data for escalation method.
 * @param[out] alert       Created alert on success.
 *
 * @return 0 success, 1 escalation exists already, 2 validation of email failed,
 *         3 failed to find filter, 4 type must be "result" if specified,
 *         5 unexpected condition data name, 6 syntax error in condition data,
 *         7 email subject too long, 8 email message too long, 9 failed to find
 *         filter for condition, 12 error in Send host, 13 error in Send port,
 *         14 failed to find report format for Send method,
 *         15 error in SCP host, 16 error in SCP port,
 *         17 failed to find report format for SCP method, 18 error
 *         in SCP credential, 19 error in SCP path, 20 method does not match
 *         event, 21 condition does not match event, 31 unexpected event data
 *         name, 32 syntax error in event data, 40 invalid SMB credential
 *       , 41 invalid SMB share path, 42 invalid SMB file path,
 *         43 SMB file path contains dot,
 *         50 invalid TippingPoint credential, 51 invalid TippingPoint hostname,
 *         52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS
 *         workaround setting, 60 recipient credential not found, 61 invalid
 *         recipient credential type, 70 vFire credential not found,
 *         71 invalid vFire credential type,
 *         99 permission denied, -1 error.
 */
int
create_alert (const char* name, const char* comment, const char* filter_id,
              const char* active, event_t event, GPtrArray* event_data,
              alert_condition_t condition, GPtrArray* condition_data,
              alert_method_t method, GPtrArray* method_data,
              alert_t *alert)
{
  int index, ret;
  gchar *item, *quoted_comment;
  gchar *quoted_name;
  filter_t filter;

  assert (current_credentials.uuid);

  sql_begin_immediate ();

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

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

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

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

      if (filter == 0)
        {
          sql_rollback ();
          return 3;
        }

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

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

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

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

  g_free (quoted_comment);
  g_free (quoted_name);

  *alert = sql_last_insert_id ();

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

      validation_result = validate_alert_condition_data (data_name,
                                                         data,
                                                         condition);

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

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

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

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

      validation_result = validate_alert_event_data (data_name, data, event);

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

          switch (validation_result)
            {
              case 1:
                return 31;
              case 2:
                return 32;
              default:
                return -1;
            }
        }

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

  index = 0;
  while ((item = (gchar*) g_ptr_array_index (method_data, index++)))
    {
      gchar *data_name, *data;

      data_name = sql_quote (item);
      data = sql_quote (item + strlen (item) + 1);

      ret = validate_email_data (method, data_name, &data, 0);
      if (ret)
        {
          g_free (data_name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

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

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

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

      ret = validate_sourcefire_data (method, data_name, &data);
      if (ret)
        {
          g_free (data_name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

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

      ret = validate_vfire_data (method, data_name, &data);
      if (ret)
        {
          g_free (data_name);
          g_free (data);
          sql_rollback ();
          return ret;
        }

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

  sql_commit ();

  return 0;
}

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

  assert (current_credentials.uuid);

  if (alert_id == NULL)
    return -1;

  sql_begin_immediate ();

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

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

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

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

  sql_commit ();
  if (new_alert) *new_alert = new;
  return 0;
}

/**
 * @brief Modify an alert.
 *
 * @param[in]   alert_id        UUID of alert.
 * @param[in]   name            Name of alert.
 * @param[in]   comment         Comment on alert.
 * @param[in]   filter_id       Filter.
 * @param[in]   active          Whether the alert is active.  NULL to leave it
 *                              at the current value.
 * @param[in]   event           Type of event.
 * @param[in]   event_data      Type-specific event data.
 * @param[in]   condition       Event condition.
 * @param[in]   condition_data  Condition-specific data.
 * @param[in]   method          Escalation method.
 * @param[in]   method_data     Data for escalation method.
 *
 * @return 0 success, 1 failed to find alert, 2 alert with new name exists,
 *         3 alert_id required, 4 failed to find filter, 5 filter type must be
 *         result if specified, 6 Provided email address not valid,
 *         7 unexpected condition data name, 8 syntax error in condition data,
 *         9 email subject too long, 10 email message too long, 11 failed to
 *         find filter for condition, 12 error in Send host, 13 error in Send
 *         port, 14 failed to find report format for Send method,
 *         15 error in SCP host, 16 error in SCP port,
 *         17 failed to find report format for SCP method, 18 error
 *         in SCP credential, 19 error in SCP path, 20 method does not match
 *         event, 21 condition does not match event, 31 unexpected event data
 *         name, 32 syntax error in event data, 40 invalid SMB credential
 *       , 41 invalid SMB share path, 42 invalid SMB file path,
 *         43 SMB file path contains dot,
 *         50 invalid TippingPoint credential, 51 invalid TippingPoint hostname,
 *         52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS
 *         workaround setting, 60 recipient credential not found, 61 invalid
 *         recipient credential type, 70 vFire credential not found,
 *         71 invalid vFire credential type,
 *         99 permission denied, -1 internal error.
 */
int
modify_alert (const char *alert_id, const char *name, const char *comment,
              const char *filter_id, const char *active, event_t event,
              GPtrArray *event_data, alert_condition_t condition,
              GPtrArray *condition_data, alert_method_t method,
              GPtrArray *method_data)
{
  int index, ret;
  gchar *quoted_name, *quoted_comment, *item;
  alert_t alert;
  filter_t filter;

  if (alert_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

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

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

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

  if (alert == 0)
    {
      sql_rollback ();
      return 1;
    }

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

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

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

      if (filter == 0)
        {
          sql_rollback ();
          return 4;
        }

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

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

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

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

  g_free (quoted_comment);
  g_free (quoted_name);

  /* Modify alert event */
  if (event != EVENT_ERROR)
    {
      sql ("UPDATE alerts set event = %i WHERE id = %llu", event, alert);
      sql ("DELETE FROM alert_event_data WHERE alert = %llu", alert);
      index = 0;
      while ((item = (gchar*) g_ptr_array_index (event_data, index++)))
        {
          int validation_result;
          gchar *data_name = sql_quote (item);
          gchar *data = sql_quote (item + strlen (item) + 1);

          validation_result = validate_alert_event_data (data_name,
                                                         data,
                                                         event);

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

              switch (validation_result)
                {
                  case 1:
                    return 31;
                  case 2:
                    return 32;
                  default:
                    return -1;
                }
            }

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

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

          validation_result = validate_alert_condition_data (data_name, data,
                                                             condition);

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

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

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

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

          data_name = sql_quote (item);
          data = sql_quote (item + strlen (item) + 1);

          ret = validate_email_data (method, data_name, &data, 1);
          if (ret)
            {
              g_free (data_name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

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

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

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

          ret = validate_sourcefire_data (method, data_name, &data);
          if (ret)
            {
              g_free (data_name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

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

          ret = validate_vfire_data (method, data_name, &data);
          if (ret)
            {
              g_free (data_name);
              g_free (data);
              sql_rollback ();
              return ret;
            }

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

  sql_commit ();

  return 0;
}

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

  sql_begin_immediate ();

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

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

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

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

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

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

  if (ultimate == 0)
    {
      alert_t trash_alert;

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

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

      trash_alert = sql_last_insert_id ();

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

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

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

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

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

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

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

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

/**
 * @brief Return the owner of an alert.
 *
 * @param[in]  alert  Alert.
 *
 * @return Owner.
 */
static user_t
alert_owner (alert_t alert)
{
  return sql_int64_0 ("SELECT owner FROM alerts WHERE id = %llu;",
                      alert);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  if (iterator->done) return NULL;

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

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

  if (iterator->done) return NULL;

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

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

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

  if (iterator->done) return 0;

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

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

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

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

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

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

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

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

/**
 * @brief Check whether an alert applies to a task.
 *
 * @param[in]  alert  Alert.
 * @param[in]  task   Task.
 *
 * @return 1 if applies, else 0.
 */
static int
alert_applies_to_task (alert_t alert, task_t task)
{
  return sql_int ("SELECT EXISTS (SELECT * FROM task_alerts"
                  "               WHERE task = %llu"
                  "               AND alert = %llu);",
                  task,
                  alert);
}

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

  assert (task);

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

  init_iterator (iterator,
                 "%s"
                 " SELECT alerts.id, alerts.uuid, alerts.name"
                 " FROM alerts, task_alerts"
                 " WHERE task_alerts.task = %llu"
                 " AND task_alerts.alert = alerts.id"
                 " AND %s;",
                 with_clause ? with_clause : "",
                 task,
                 owned_clause);

  g_free (with_clause);
  g_free (owned_clause);
}

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

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

/**
 * @brief Initialise an event alert iterator.
 *
 * @param[in]  iterator  Iterator.
 * @param[in]  event     Event.
 */
static void
init_event_alert_iterator (iterator_t* iterator, event_t event)
{
  gchar *owned_clause, *with_clause;
  get_data_t get;
  array_t *permissions;

  assert (event);

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

  init_iterator (iterator,
                 "%s"
                 " SELECT alerts.id, alerts.active"
                 " FROM alerts"
                 " WHERE event = %i"
                 " AND %s;",
                 with_clause ? with_clause : "",
                 event,
                 owned_clause);

  g_free (with_clause);
  g_free (owned_clause);
}

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

/**
 * @brief Get the active state from an event alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Active state.
 */
static int
event_alert_iterator_active (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 1);
  return ret;
}

/**
 * @brief Write the content of a plain text email to a stream.
 *
 * @param[in]  content_file  Stream to write the email content to.
 * @param[in]  to_address    Address to send to.
 * @param[in]  from_address  Address to send to.
 * @param[in]  subject       Subject of email.
 * @param[in]  body          Body of email.
 * @param[in]  attachment    Attachment in line broken base64, or NULL.
 * @param[in]  attachment_type  Attachment MIME type, or NULL.
 * @param[in]  attachment_name  Base file name of the attachment, or NULL.
 * @param[in]  attachment_extension  Attachment file extension, or NULL.
 *
 * @return 0 success, -1 error.
 */
static int
email_write_content (FILE *content_file,
                     const char *to_address, const char *from_address,
                     const char *subject, const char *body,
                     const gchar *attachment, const char *attachment_type,
                     const char *attachment_name,
                     const char *attachment_extension)
{
  if (fprintf (content_file,
               "To: %s\n"
               "From: %s\n"
               "Subject: %s\n"
               "%s%s%s"
               "\n"
               "%s"
               "%s\n",
               to_address,
               from_address ? from_address
                            : "automated@openvas.org",
               subject,
               (attachment
                 ? "MIME-Version: 1.0\n"
                   "Content-Type: multipart/mixed;"
                   " boundary=\""
                 : "Content-Type: text/plain; charset=utf-8\n"
                   "Content-Transfer-Encoding: 8bit\n"),
               /* @todo Future callers may give email containing this string. */
               (attachment ? "=-=-=-=-=" : ""),
               (attachment ? "\"\n" : ""),
               (attachment ? "--=-=-=-=-=\n"
                             "Content-Type: text/plain; charset=utf-8\n"
                             "Content-Transfer-Encoding: 8bit\n"
                             "Content-Disposition: inline\n"
                             "\n"
                           : ""),
               body)
      < 0)
    {
      g_warning ("%s: output error", __func__);
      return -1;
    }

  if (attachment)
    {
      int len;

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

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

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

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

  return 0;
}

/**
 * @brief  Create a PGP encrypted email from a plain text one.
 *
 * @param[in]  plain_file     Stream to read the plain text email from.
 * @param[in]  encrypted_file Stream to write the encrypted email to.
 * @param[in]  public_key     Recipient public key to use for encryption.
 * @param[in]  to_address     Email address to send to.
 * @param[in]  from_address   Email address to use as sender.
 * @param[in]  subject        Subject of email.
 *
 * @return 0 success, -1 error.
 */
static int
email_encrypt_gpg (FILE *plain_file, FILE *encrypted_file,
                   const char *public_key,
                   const char *to_address, const char *from_address,
                   const char *subject)
{
  // Headers and metadata parts
  if (fprintf (encrypted_file,
               "To: %s\n"
               "From: %s\n"
               "Subject: %s\n"
               "MIME-Version: 1.0\n"
               "Content-Type: multipart/encrypted;\n"
               " protocol=\"application/pgp-encrypted\";\n"
               " boundary=\"=-=-=-=-=\"\n"
               "\n"
               "--=-=-=-=-=\n"
               "Content-Type: application/pgp-encrypted\n"
               "Content-Description: PGP/MIME version identification\n"
               "\n"
               "Version: 1\n"
               "\n"
               "--=-=-=-=-=\n"
               "Content-Type: application/octet-stream\n"
               "Content-Description: OpenPGP encrypted message\n"
               "Content-Disposition: inline; filename=\"encrypted.asc\"\n"
               "\n",
               to_address,
               from_address ? from_address
                            : "automated@openvas.org",
               subject) < 0)
    {
      g_warning ("%s: output error at headers", __func__);
      return -1;
    }

  // Encrypted message
  if (gvm_pgp_pubkey_encrypt_stream (plain_file, encrypted_file, to_address,
                                     public_key, -1))
    {
      return -1;
    }

  // End of message
  if (fprintf (encrypted_file,
               "\n"
               "--=-=-=-=-=--\n") < 0)
    {
      g_warning ("%s: output error at end of message", __func__);
      return -1;
    }

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

  return 0;
}

/**
 * @brief  Create an S/MIME encrypted email from a plain text one.
 *
 * @param[in]  plain_file     Stream to read the plain text email from.
 * @param[in]  encrypted_file Stream to write the encrypted email to.
 * @param[in]  certificate    Recipient certificate chain for encryption.
 * @param[in]  to_address     Email address to send to.
 * @param[in]  from_address   Email address to use as sender.
 * @param[in]  subject        Subject of email.
 *
 * @return 0 success, -1 error.
 */
static int
email_encrypt_smime (FILE *plain_file, FILE *encrypted_file,
                     const char *certificate,
                     const char *to_address, const char *from_address,
                     const char *subject)
{
  // Headers and metadata parts
  if (fprintf (encrypted_file,
               "To: %s\n"
               "From: %s\n"
               "Subject: %s\n"
               "Content-Type: application/x-pkcs7-mime;"
               " smime-type=enveloped-data; name=\"smime.p7m\"\n"
               "Content-Disposition: attachment; filename=\"smime.p7m\"\n"
               "Content-Transfer-Encoding: base64\n"
               "\n",
               to_address,
               from_address ? from_address
                            : "automated@openvas.org",
               subject) < 0)
    {
      g_warning ("%s: output error at headers", __func__);
      return -1;
    }

  // Encrypted message
  if (gvm_smime_encrypt_stream (plain_file, encrypted_file, to_address,
                                certificate, -1))
    {
      g_warning ("%s: encryption failed", __func__);
      return -1;
    }

  // End of message
  if (fprintf (encrypted_file,
               "\n") < 0)
    {
      g_warning ("%s: output error at end of message", __func__);
      return -1;
    }

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

  return 0;
}

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

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

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

  content_file = fdopen (content_fd, "w");
  if (content_file == NULL)
    {
      g_warning ("%s: Could not open content file: %s",
                 __func__, strerror (errno));
      close (content_fd);
      return -1;
    }

  if (recipient_credential)
    {
      iterator_t iterator;
      init_credential_iterator_one (&iterator, recipient_credential);

      if (next (&iterator))
        {
          const char *type = credential_iterator_type (&iterator);
          const char *public_key = credential_iterator_public_key (&iterator);
          const char *certificate
            = credential_iterator_certificate (&iterator);
          char plain_file_name[] = "/tmp/gvmd-plain-XXXXXX";
          int plain_fd;
          FILE *plain_file;

          // Create plain text message
          plain_fd = mkstemp (plain_file_name);
          if (plain_fd == -1)
            {
              g_warning ("%s: mkstemp for plain text file: %s",
                         __func__, strerror (errno));
              fclose (content_file);
              unlink (content_file_name);
              cleanup_iterator (&iterator);
              return -1;
            }

          plain_file = fdopen (plain_fd, "w+");
          if (plain_file == NULL)
            {
              g_warning ("%s: Could not open plain text file: %s",
                         __func__, strerror (errno));
              fclose (content_file);
              unlink (content_file_name);
              close (plain_fd);
              unlink (plain_file_name);
              cleanup_iterator (&iterator);
              return -1;
            }

          if (email_write_content (plain_file,
                                   to_address, from_address,
                                   subject, body, attachment,
                                   attachment_type, attachment_name,
                                   attachment_extension))
            {
              fclose (content_file);
              unlink (content_file_name);
              fclose (plain_file);
              unlink (plain_file_name);
              cleanup_iterator (&iterator);
              return -1;
            }

          rewind (plain_file);

          // Create encrypted email
          if (strcmp (type, "pgp") == 0)
            {
              ret = email_encrypt_gpg (plain_file, content_file,
                                       public_key,
                                       to_address, from_address, subject);

              fclose (plain_file);
              unlink (plain_file_name);

              if (ret)
                {
                  g_warning ("%s: PGP encryption failed", __func__);
                  fclose (content_file);
                  unlink (content_file_name);
                  cleanup_iterator (&iterator);
                  return -1;
                }
            }
          else if (strcmp (type, "smime") == 0)
            {
              ret = email_encrypt_smime (plain_file, content_file,
                                         certificate,
                                         to_address, from_address, subject);

              fclose (plain_file);
              unlink (plain_file_name);

              if (ret)
                {
                  g_warning ("%s: S/MIME encryption failed", __func__);
                  fclose (content_file);
                  unlink (content_file_name);
                  cleanup_iterator (&iterator);
                  return -1;
                }
            }
          else
            {
              g_warning ("%s: Invalid recipient credential type",
                        __func__);
              fclose (content_file);
              unlink (content_file_name);
              fclose (plain_file);
              unlink (plain_file_name);
              cleanup_iterator (&iterator);
              return -1;
            }
        }

      cleanup_iterator (&iterator);
    }
  else
    {
      if (email_write_content (content_file,
                               to_address, from_address,
                               subject, body, attachment, attachment_type,
                               attachment_name, attachment_extension))
        {
          fclose (content_file);
          return -1;
        }
    }

  args_fd = mkstemp (args_file_name);
  if (args_fd == -1)
    {
      g_warning ("%s: mkstemp: %s", __func__, strerror (errno));
      fclose (content_file);
      return -1;
    }

  sendmail_args = g_strdup_printf ("%s %s",
                                   from_address,
                                   to_address);
  g_file_set_contents (args_file_name,
                       sendmail_args,
                       strlen (sendmail_args),
                       &error);
  g_free (sendmail_args);

  if (error)
    {
      g_warning ("%s", error->message);
      g_error_free (error);
      fclose (content_file);
      close (args_fd);
      return -1;
    }

  command = g_strdup_printf ("read FROM TO < %s;"
                             " /usr/sbin/sendmail -f \"$FROM\" \"$TO\" < %s"
                             " > /dev/null 2>&1",
                             args_file_name,
                             content_file_name);

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

  ret = system (command);
  if ((ret == -1) || WEXITSTATUS (ret))
    {
      g_warning ("%s: system failed with ret %i, %i, %s",
                 __func__,
                 ret,
                 WEXITSTATUS (ret),
                 command);
      g_free (command);
      fclose (content_file);
      close (args_fd);
      unlink (content_file_name);
      unlink (args_file_name);
      return -1;
    }
  g_free (command);
  fclose (content_file);
  close (args_fd);
  unlink (content_file_name);
  unlink (args_file_name);
  return 0;
}

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

  g_debug ("   HTTP_GET %s", url);

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

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

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

  /* Create temp directory */

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

  /* Create report file */

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

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

  /* Create error file */

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

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

  /* Create extra data file */

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

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

  return 0;
}

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

  /* Setup script file name. */
  script_dir = g_build_filename (GVMD_DATA_DIR,
                                 "global_alert_methods",
                                 alert_id,
                                 NULL);

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

  if (!gvm_file_is_readable (script))
    {
      g_warning ("%s: Failed to find alert script: %s",
           __func__,
           script);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

  /* Run the script */
  {
    gchar *command;
    char *previous_dir;
    int ret;

    /* Change into the script directory. */

    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);
        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);
        return -1;
      }
    g_free (script_dir);

    /* Call the script. */

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

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

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

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

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

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

                setproctitle ("Running alert script");

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

                ret = system (command);
                /*
                 * Check shell command exit status, assuming 0 means success.
                 */
                if (ret == -1)
                  {
                    g_warning ("%s (child):"
                               " system failed with ret %i, %i, %s",
                               __func__,
                               ret,
                               WEXITSTATUS (ret),
                               command);
                    gvm_close_sentry ();
                    exit (EXIT_FAILURE);
                  }
                else if (ret != 0)
                  {
                    GError *error;

                    if (g_file_get_contents (error_path, message,
                                             NULL, &error) == FALSE)
                      {
                        g_warning ("%s: failed to test error message: %s",
                                    __func__, error->message);
                        g_error_free (error);
                        if (message)
                          g_free (*message);
                        gvm_close_sentry ();
                        exit (EXIT_FAILURE);
                      }

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

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

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

                exit (EXIT_SUCCESS);
              }

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

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

            default:
              {
                int status;

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

                while (waitpid (pid, &status, 0) < 0)
                  {
                    if (errno == ECHILD)
                      {
                        g_warning ("%s: Failed to get child exit status",
                                   __func__);
                        if (chdir (previous_dir))
                          g_warning ("%s: and chdir failed",
                                     __func__);
                        g_free (previous_dir);
                        return -1;
                      }
                    if (errno == EINTR)
                      continue;
                    g_warning ("%s: wait: %s",
                               __func__,
                               strerror (errno));
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed",
                                 __func__);
                    g_free (previous_dir);
                    return -1;
                  }
                if (WIFEXITED (status))
                  switch (WEXITSTATUS (status))
                    {
                    case EXIT_SUCCESS:
                      break;
                    case 2: // script failed
                      if (message)
                        {
                          GError *error = NULL;
                          if (g_file_get_contents (error_path, message,
                                                   NULL, &error) == FALSE)
                            {
                              g_warning ("%s: failed to get error message: %s",
                                         __func__, error->message);
                              g_error_free (error);
                            }

                          if (strcmp (*message, "") == 0)
                            {
                              g_free (*message);
                              *message = NULL;
                            }
                        }
                      if (chdir (previous_dir))
                        g_warning ("%s: chdir failed",
                                   __func__);
                      return -5;
                    case EXIT_FAILURE:
                    default:
                      g_warning ("%s: child failed, %s",
                                 __func__,
                                 command);
                      if (chdir (previous_dir))
                        g_warning ("%s: and chdir failed",
                                   __func__);
                      g_free (previous_dir);
                      return -1;
                    }
                else
                  {
                    g_warning ("%s: child failed, %s",
                               __func__,
                               command);
                    if (chdir (previous_dir))
                      g_warning ("%s: and chdir failed",
                                 __func__);
                    g_free (previous_dir);
                    return -1;
                  }

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

                break;
              }
          }
      }
    else
      {
        /* Just run the command as the current user. */

        ret = system (command);
        /* Ignore the shell command exit status, because we've not
         * specified what it must be in the past. */
        if (ret == -1)
          {
            g_warning ("%s: system failed with ret %i, %i, %s",
                       __func__,
                       ret,
                       WEXITSTATUS (ret),
                       command);
            if (chdir (previous_dir))
              g_warning ("%s: and chdir failed",
                         __func__);
            g_free (previous_dir);
            g_free (command);
            return -1;
          }
        else if (ret)
          {
            if (message)
              {
                GError *error = NULL;
                if (g_file_get_contents (error_path, message, NULL, &error)
                      == FALSE)
                  {
                    g_warning ("%s: failed to get error message: %s",
                               __func__, error->message);
                    g_error_free (error);
                  }

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

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

    g_free (command);

    /* Change back to the previous directory. */

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

/**
 * @brief Write data to a file for use by an alert script.
 *
 * @param[in]  directory      Base directory to create the file in
 * @param[in]  filename       Filename without directory
 * @param[in]  content        The file content
 * @param[in]  content_size   Size of the file content
 * @param[in]  description    Short file description for error messages
 * @param[out] file_path      Return location of combined file path
 *
 * @return 0 success, -1 error
 */
static int
alert_write_data_file (const char *directory, const char *filename,
                       const char *content, gsize content_size,
                       const char *description, gchar **file_path)
{
  gchar *path;
  GError *error;

  if (file_path)
    *file_path = NULL;

  /* Setup extra data file */
  path = g_build_filename (directory, filename, NULL);
  error = NULL;
  if (g_file_set_contents (path, content, content_size, &error) == FALSE)
    {
      g_warning ("%s: Failed to write %s to file: %s",
                 __func__,
                 description ? description : "extra data",
                 error->message);
      g_free (path);
      return -1;
    }

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

      /* Set the owner for the extra data file like the other
       * files handled by alert_script_exec, to be able to
       * run the command with lower privileges in a fork. */

      nobody = getpwnam ("nobody");
      if ((nobody == NULL)
          || chown (path, nobody->pw_uid, nobody->pw_gid))
        {
          g_warning ("%s: Failed to set permissions for user nobody: %s",
                      __func__,
                      strerror (errno));
          g_free (path);
          return -1;
        }
    }

  if (file_path)
    *file_path = path;

  return 0;
}

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

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

  if (message)
    *message = NULL;

  if (report == NULL)
    return -1;

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

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

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

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

  return ret;
}

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

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

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

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

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

  g_free (command_args);
  return ret;
}

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

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

  if (host == NULL)
    return -1;

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

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

  g_free (command_args);
  return ret;
}

/**
 * @brief Send a report to a host via TCP.
 *
 * @param[in]  username     Username.
 * @param[in]  password     Password or passphrase of private key.
 * @param[in]  private_key  Private key or NULL for password-only auth.
 * @param[in]  host         Address of host.
 * @param[in]  port         SSH Port of host.
 * @param[in]  path         Destination filename with path.
 * @param[in]  known_hosts  Content for known_hosts file.
 * @param[in]  report       Report that should be sent.
 * @param[in]  report_size  Size of the report.
 * @param[out] script_message  Custom error message of the alert script.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
scp_to_host (const char *username, const char *password,
             const char *private_key,
             const char *host, int port,
             const char *path, const char *known_hosts,
             const char *report, int report_size, gchar **script_message)
{
  const char *alert_id = "2db07698-ec49-11e5-bcff-28d24461215b";
  char report_dir[] = "/tmp/gvmd_alert_XXXXXX";
  gchar *report_path, *error_path, *password_path, *private_key_path;
  gchar *clean_username, *clean_host, *clean_path, *clean_private_key_path;
  gchar *clean_known_hosts, *command_args;
  int ret;

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

  if (password == NULL || username == NULL || host == NULL || path == NULL
      || port <= 0 || port > 65535)
    return -1;

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

  /* Setup files, including password but not private key */
  ret = alert_script_init ("report", report, report_size,
                           password, strlen (password),
                           report_dir,
                           &report_path, &error_path, &password_path);
  if (ret)
    return -1;

  if (private_key)
    {
      /* Setup private key here because alert_script_init and alert_script_exec
       *  only handle one extra file. */
      if (alert_write_data_file (report_dir, "private_key",
                                 private_key, strlen (private_key),
                                 "private key", &private_key_path))
        {
          alert_script_cleanup (report_dir, report_path, error_path,
                                password_path);
          g_free (private_key_path);
          return -1;
        }
    }
  else
    private_key_path = g_strdup ("");

  /* Create arguments */
  clean_username = g_shell_quote (username);
  clean_host = g_shell_quote (host);
  clean_path = g_shell_quote (path);
  clean_known_hosts = g_shell_quote (known_hosts);
  clean_private_key_path = g_shell_quote (private_key_path);
  command_args = g_strdup_printf ("%s %s %d %s %s %s",
                                  clean_username,
                                  clean_host,
                                  port,
                                  clean_path,
                                  clean_known_hosts,
                                  clean_private_key_path);
  g_free (clean_username);
  g_free (clean_host);
  g_free (clean_path);
  g_free (clean_known_hosts);
  g_free (clean_private_key_path);

  /* Run script */
  ret = alert_script_exec (alert_id, command_args, report_path, report_dir,
                           error_path, password_path, script_message);
  g_free (command_args);
  if (ret)
    {
      alert_script_cleanup (report_dir, report_path, error_path,
                            password_path);
      g_free (private_key_path);
      return ret;
    }

  /* Remove the directory and free path strings. */
  ret = alert_script_cleanup (report_dir, report_path, error_path,
                              password_path);
  g_free (private_key_path);
  return ret;
}

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

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

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

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

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

  g_free (authfile_content);
  g_free (command_args);
  return ret;
}

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

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

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

  /* Setup files. */

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

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

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

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

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

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

  clean_password = g_shell_quote (pkcs12_password ? pkcs12_password : "");

  /* Setup file names. */

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

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

  if (!gvm_file_is_readable (script))
    {
      g_free (report_file);
      g_free (pkcs12_file);
      g_free (clean_password);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

  {
    gchar *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 (report_file);
        g_free (pkcs12_file);
        g_free (clean_password);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }

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

    /* Call the script. */

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

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

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

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

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

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

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

                setproctitle ("Sending to Sourcefire");

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

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

                gvm_close_sentry ();
                exit (EXIT_SUCCESS);
              }

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

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

          default:
              {
                int status;

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


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

                /* Child succeeded, continue to process result. */
                g_free (command);
                break;
              }
          }
      }
    else
      {
        /* Just run the command as the current user. */
        g_free (report_file);
        g_free (pkcs12_file);

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

        g_free (command);
      }

    /* Change back to the previous directory. */

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

    /* Remove the directory. */

    gvm_file_remove_recurse (report_dir);

    return 0;
  }
}

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

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

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

  /* Setup files. */

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

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

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

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

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

  if (!gvm_file_is_readable (script))
    {
      g_warning ("%s: Failed to find alert script: %s",
           __func__,
           script);
      g_free (archive_file);
      g_free (script);
      g_free (script_dir);
      return -1;
    }

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

    /* Change into the script directory. */

    previous_dir = getcwd (NULL, 0);
    if (previous_dir == NULL)
      {
        g_warning ("%s: Failed to getcwd: %s",
                   __func__,
                   strerror (errno));
        g_free (archive_file);
        g_free (previous_dir);
        g_free (script);
        g_free (script_dir);
        return -1;
      }

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

    /* Call the script. */

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

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

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

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

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

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

        pid = fork ();
        switch (pid)
          {
          case 0:
              {
                /* Child.  Drop privileges, run command, exit. */
                init_sentry ();
                setproctitle ("Sending to Verinice");

                cleanup_manage_process (FALSE);

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

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

                gvm_close_sentry ();
                exit (EXIT_SUCCESS);
              }

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

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

          default:
              {
                int status;

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

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

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

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

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

      }

    g_free (command);
    g_free (log_command);

    /* Change back to the previous directory. */

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

    /* Remove the directory. */

    gvm_file_remove_recurse (archive_dir);

    return 0;
  }
}

/**
 * @brief  Appends an XML fragment for vFire call input to a string buffer.
 *
 * @param[in]  key      The name of the key.
 * @param[in]  value    The value to add.
 * @param[in]  buffer   The string buffer to append to.
 *
 * @return  Always FALSE.
 */
gboolean
buffer_vfire_call_input (gchar *key, gchar *value, GString *buffer)
{
  xml_string_append (buffer,
                     "<%s>%s</%s>",
                     key, value, key);
  return FALSE;
}

/**
 * @brief  Checks a mandatory vFire parameter and adds it to the config XML.
 *
 * @param[in]  param  The parameter to check.
 */
#define APPEND_VFIRE_PARAM(param)                                             \
  if (param)                                                                  \
    xml_string_append (config_xml,                                            \
                       "<" G_STRINGIFY(param) ">%s</" G_STRINGIFY(param) ">", \
                       param);                                                \
  else                                                                        \
    {                                                                         \
      if (message)                                                            \
        *message = g_strdup ("Mandatory " G_STRINGIFY(param) " missing.");    \
      g_warning ("%s: Missing " G_STRINGIFY(param) ".", __func__);        \
      g_string_free (config_xml, TRUE);                                       \
      return -1;                                                              \
    }

/**
 * @brief Create a new call on an Alemba vFire server.
 *
 * @param[in]  base_url       Base url of the vFire server.
 * @param[in]  client_id      The Alemba API Client ID to authenticate with.
 * @param[in]  session_type   Alemba session type to use, e.g. "Analyst".
 * @param[in]  username       Username.
 * @param[in]  password       Password.
 * @param[in]  report_data    Data for vFire call report attachments.
 * @param[in]  call_data      Data for creating the vFire call.
 * @param[in]  description_template  Template for the description text.
 * @param[out] message        Error message.
 *
 * @return 0 success, -1 error, -5 alert script failed.
 */
static int
send_to_vfire (const char *base_url, const char *client_id,
               const char *session_type, const char *username,
               const char *password, GPtrArray *report_data,
               GTree *call_data, const char *description_template,
               gchar **message)
{
  const char *alert_id = "159f79a5-fce8-4ec5-aa49-7d17a77739a3";
  GString *config_xml;
  int index;
  char config_xml_filename[] = "/tmp/gvmd_vfire_data_XXXXXX.xml";
  int config_xml_fd;
  FILE *config_xml_file;
  gchar **cmd;
  gchar *alert_script;
  int ret, exit_status;
  GError *err;

  config_xml = g_string_new ("<alert_data>");

  // Mandatory parameters
  APPEND_VFIRE_PARAM (base_url)
  APPEND_VFIRE_PARAM (client_id)
  APPEND_VFIRE_PARAM (username)
  APPEND_VFIRE_PARAM (password)

  // Optional parameters
  xml_string_append (config_xml,
                     "<session_type>%s</session_type>",
                     session_type ? session_type : "Analyst");

  // Call input
  g_string_append (config_xml, "<call_input>");
  g_tree_foreach (call_data, ((GTraverseFunc) buffer_vfire_call_input),
                  config_xml);
  g_string_append (config_xml, "</call_input>");

  // Report data
  g_string_append (config_xml, "<attach_reports>");
  for (index = 0; index < report_data->len; index++)
    {
      alert_report_data_t *report_item;
      report_item = g_ptr_array_index (report_data, index);

      xml_string_append (config_xml,
                         "<report>"
                         "<src_path>%s</src_path>"
                         "<dest_filename>%s</dest_filename>"
                         "<content_type>%s</content_type>"
                         "<report_format>%s</report_format>"
                         "</report>",
                         report_item->local_filename,
                         report_item->remote_filename,
                         report_item->content_type,
                         report_item->report_format_name);

    }
  g_string_append (config_xml, "</attach_reports>");

  // End data XML and output to file
  g_string_append (config_xml, "</alert_data>");

  config_xml_fd = g_mkstemp (config_xml_filename);
  if (config_xml_fd == -1)
    {
      g_warning ("%s: Could not create alert script config file: %s",
                 __func__, strerror (errno));
      g_string_free (config_xml, TRUE);
      return -1;
    }

  config_xml_file = fdopen (config_xml_fd, "w");
  if (config_xml_file == NULL)
    {
      g_warning ("%s: Could not open alert script config file: %s",
                 __func__, strerror (errno));
      g_string_free (config_xml, TRUE);
      close (config_xml_fd);
      return -1;
    }

  if (fprintf (config_xml_file, "%s", config_xml->str) <= 0)
    {
      g_warning ("%s: Could not write alert script config file: %s",
                 __func__, strerror (errno));
      g_string_free (config_xml, TRUE);
      fclose (config_xml_file);
      return -1;
    }

  fflush (config_xml_file);
  fclose (config_xml_file);
  g_string_free (config_xml, TRUE);

  // Run the script
  alert_script = g_build_filename (GVMD_DATA_DIR,
                                   "global_alert_methods",
                                   alert_id,
                                   "alert",
                                   NULL);

  // TODO: Drop privileges when running as root

  cmd = (gchar **) g_malloc (3 * sizeof (gchar *));
  cmd[0] = alert_script;
  cmd[1] = config_xml_filename;
  cmd[2] = NULL;

  ret = 0;
  if (g_spawn_sync (NULL,
                    cmd,
                    NULL,
                    G_SPAWN_STDOUT_TO_DEV_NULL,
                    NULL,
                    NULL,
                    NULL,
                    message,
                    &exit_status,
                    &err) == FALSE)
    {
      g_warning ("%s: Failed to run alert script: %s",
                 __func__, err->message);
      ret = -1;
    }

  if (exit_status)
    {
      g_warning ("%s: Alert script exited with status %d",
                 __func__, exit_status);
      g_message ("%s: stderr: %s",
                 __func__, message ? *message: "");
      ret = -5;
    }

  // Cleanup
  g_free (cmd);
  g_free (alert_script);
  g_unlink (config_xml_filename);

  return ret;
}

/**
 * @brief Convert an XML report and send it to a TippingPoint SMS.
 *
 * @param[in]  report           Report to send.
 * @param[in]  report_size      Size of report.
 * @param[in]  username         Username.
 * @param[in]  password         Password.
 * @param[in]  hostname         Hostname.
 * @param[in]  certificate      Certificate.
 * @param[in]  cert_workaround  Whether to use cert workaround.
 * @param[out] message          Custom error message of the script.
 *
 * @return 0 success, -1 error.
 */
static int
send_to_tippingpoint (const char *report, size_t report_size,
                      const char *username, const char *password,
                      const char *hostname, const char *certificate,
                      int cert_workaround, gchar **message)
{
  const char *alert_id = "5b39c481-9137-4876-b734-263849dd96ce";
  char report_dir[] = "/tmp/gvmd_alert_XXXXXX";
  gchar *auth_config, *report_path, *error_path, *extra_path, *cert_path;
  gchar *command_args, *hostname_clean, *convert_script;
  GError *error = NULL;
  int ret;

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

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

  if (ret)
    return ret;

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

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

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

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

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

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

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

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

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

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

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

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

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

  assert (subject);

  new_subject = g_string_new ("");
  for (formatting = 0, point = subject, end = (subject + strlen (subject));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_subject, '$');
              break;
            case 'd':
              /* Date that the check was last performed. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  char time_string[100];
                  time_t date;
                  struct tm tm;

                  if (event_data && (strcasecmp (event_data, "nvt") == 0))
                    date = nvts_check_time ();
                  else if (type_is_scap (event_data))
                    date = scap_check_time ();
                  else
                    date = cert_check_time ();

                  if (localtime_r (&date, &tm) == NULL)
                    {
                      g_warning ("%s: localtime failed, aborting",
                                 __func__);
                      abort ();
                    }
                  if (strftime (time_string, 98, "%F", &tm) == 0)
                    break;
                  g_string_append (new_subject, time_string);
                }
              break;
            case 'e':
              {
                gchar *event_desc;
                event_desc = event_description (event, event_data,
                                                NULL);
                g_string_append (new_subject, event_desc);
                g_free (event_desc);
                break;
              }
            case 'n':
              {
                if (task)
                  {
                    char *name = task_name (task);
                    g_string_append (new_subject, name);
                    free (name);
                  }
                break;
              }
            case 'N':
              {
                /* Alert Name */
                char *name = alert_name (alert);
                g_string_append (new_subject, name);
                free (name);
                break;
              }
            case 'q':
              if (event == EVENT_NEW_SECINFO)
                g_string_append (new_subject, "New");
              else if (event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, "Updated");
              break;
            case 's':
              /* Type. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, type_name (event_data));
              break;
            case 'S':
              /* Type, plural. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_subject, type_name_plural (event_data));
              break;
            case 'T':
              g_string_append_printf (new_subject, "%i", total);
              break;
            case 'u':
              {
                /* Current user or owner of the Alert */
                if (current_credentials.username
                    && strcmp (current_credentials.username, ""))
                  {
                    g_string_append (new_subject, current_credentials.username);
                  }
                else
                  {
                    char *owner = alert_owner_uuid (alert);
                    gchar *name = user_name (owner);
                    g_string_append (new_subject, name);
                    free (owner);
                    g_free (name);
                  }
                break;
              }
            case 'U':
              {
                /* Alert UUID */
                char *uuid = alert_uuid (alert);
                g_string_append (new_subject, uuid);
                free (uuid);
                break;
              }
            default:
              g_string_append_c (new_subject, '$');
              g_string_append_c (new_subject, *point);
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_subject, *point);

  return g_string_free (new_subject, FALSE);
}

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

  assert (message);

  new_message = g_string_new ("");
  for (formatting = 0, point = message, end = (message + strlen (message));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_message, '$');
              break;
            case 'c':
              {
                gchar *condition_desc;
                condition_desc = alert_condition_description
                                  (condition, alert);
                g_string_append (new_message, condition_desc);
                g_free (condition_desc);
                break;
              }
            case 'd':
              /* Date that the check was last performed. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                {
                  char time_string[100];
                  time_t date;
                  struct tm tm;

                  if (event_data && (strcasecmp (event_data, "nvt") == 0))
                    date = nvts_check_time ();
                  else if (type_is_scap (event_data))
                    date = scap_check_time ();
                  else
                    date = cert_check_time ();

                  if (localtime_r (&date, &tm) == NULL)
                    {
                      g_warning ("%s: localtime failed, aborting",
                                 __func__);
                      abort ();
                    }
                  if (strftime (time_string, 98, "%F", &tm) == 0)
                    break;
                  g_string_append (new_message, time_string);
                }
              break;
            case 'e':
              {
                gchar *event_desc;
                event_desc = event_description (event, event_data,
                                                NULL);
                g_string_append (new_message, event_desc);
                g_free (event_desc);
                break;
              }
            case 'H':
              {
                /* Host summary. */

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

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

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

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

                g_string_append (new_message, term ? term : "N/A");
                break;
              }
            case 'q':
              {
                if (event == EVENT_NEW_SECINFO)
                  g_string_append (new_message, "New");
                else if (event == EVENT_UPDATED_SECINFO)
                  g_string_append (new_message, "Updated");
                break;
              }
            case 's':
              /* Type. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_message, type_name (event_data));
              break;
            case 'S':
              /* Type, plural. */
              if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO)
                g_string_append (new_message, type_name_plural (event_data));
              break;
            case 't':
              {
                if (truncated)
                  g_string_append_printf (new_message,
                                          "Note: This report exceeds the"
                                          " maximum length of %i characters"
                                          " and thus\n"
                                          "was truncated.\n",
                                          max_length);
                break;
              }
            case 'T':
              {
                g_string_append_printf (new_message, "%i", total);
                break;
              }
            case 'u':
              {
                /* Current user or owner of the Alert */
                if (current_credentials.username
                    && strcmp (current_credentials.username, ""))
                  {
                    g_string_append (new_message, current_credentials.username);
                  }
                else
                  {
                    char *owner = alert_owner_uuid (alert);
                    gchar *name = user_name (owner);
                    g_string_append (new_message, name);
                    free (owner);
                    g_free (name);
                  }
                break;
              }
            case 'U':
              {
                /* Alert UUID */
                char *uuid = alert_uuid (alert);
                g_string_append (new_message, uuid);
                free (uuid);
                break;
              }
            case 'z':
              {
                /* Timezone. */

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

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

  return g_string_free (new_message, FALSE);
}

/**
 * @brief Print an SCP alert file path.
 *
 * @param[in]  message      Format string for message.
 * @param[in]  task         Task.
 *
 * @return Freshly allocated message.
 */
static gchar *
scp_alert_path_print (const gchar *message, task_t task)
{
  int formatting;
  const gchar *point, *end;
  GString *new_message;

  assert (message);

  new_message = g_string_new ("");
  for (formatting = 0, point = message, end = (message + strlen (message));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_message, '$');
              break;
            case 'D':
            case 'T':
              {
                char time_string[9];
                time_t current_time;
                struct tm tm;
                const gchar *format_str;

                if (*point == 'T')
                  format_str = "%H%M%S";
                else
                  format_str = "%Y%m%d";

                memset(&time_string, 0, 9);
                current_time = time (NULL);

                if (localtime_r (&current_time, &tm) == NULL)
                  {
                    g_warning ("%s: localtime failed, aborting",
                                __func__);
                    abort ();
                  }
                if (strftime (time_string, 9, format_str, &tm))
                  g_string_append (new_message, time_string);
                break;
              }
            case 'n':
              if (task)
                {
                  char *name = task_name (task);
                  g_string_append (new_message, name);
                  free (name);
                }
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_message, *point);

  return g_string_free (new_message, FALSE);
}

/**
 * @brief Build and send email for a ticket alert.
 *
 * @param[in]  alert       Alert.
 * @param[in]  ticket      Ticket.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[in]  to_address    To address.
 * @param[in]  from_address  From address.
 * @param[in]  subject       Subject.
 *
 * @return 0 success, -1 error.
 */
static int
email_ticket (alert_t alert, ticket_t ticket, event_t event,
              const void* event_data, alert_method_t method,
              alert_condition_t condition, const gchar *to_address,
              const gchar *from_address, const gchar *subject)
{
  gchar *full_subject, *body;
  char *recipient_credential_id;
  credential_t recipient_credential;
  int ret;

  /* Setup subject. */

  full_subject = g_strdup_printf ("%s: %s (UUID: %s)",
                                  subject,
                                  ticket_nvt_name (ticket)
                                   ? ticket_nvt_name (ticket)
                                   : "[Orphan]",
                                  ticket_uuid (ticket));

  /* Setup body. */

  {
    gchar *event_desc, *condition_desc;

    event_desc = event_description (event, event_data, NULL);
    condition_desc = alert_condition_description
                      (condition, alert);
    body = g_strdup_printf (SIMPLE_NOTICE_FORMAT,
                            event_desc,
                            event_desc,
                            condition_desc);
    free (event_desc);
    free (condition_desc);
  }

  /* Get credential */
  recipient_credential_id = alert_data (alert, "method",
                                        "recipient_credential");
  recipient_credential = 0;
  if (recipient_credential_id)
    {
      find_credential_with_permission (recipient_credential_id,
                                       &recipient_credential, NULL);
    }

  /* Send email. */

  ret = email (to_address, from_address, full_subject,
               body, NULL, NULL, NULL, NULL,
               recipient_credential);
  g_free (body);
  g_free (full_subject);
  free (recipient_credential_id);
  return ret;
}

/**
 * @brief Build and send email for SecInfo alert.
 *
 * @param[in]  alert       Alert.
 * @param[in]  task        Task.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[in]  to_address    To address.
 * @param[in]  from_address  From address.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter.
 */
static int
email_secinfo (alert_t alert, task_t task, event_t event,
               const void* event_data, alert_method_t method,
               alert_condition_t condition, const gchar *to_address,
               const gchar *from_address)
{
  gchar *alert_subject, *message, *subject, *example, *list, *type, *base64;
  gchar *term, *body;
  char *notice, *recipient_credential_id, *condition_filter_id;
  filter_t condition_filter;
  credential_t recipient_credential;
  int ret, count;

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

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

  /* Setup subject. */

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

  /* Setup body. */

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

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

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

  condition_filter = 0;
  term = NULL;
  condition_filter_id = alert_data (alert, "condition", "filter_id");
  if (condition_filter_id)
    {
      gchar *quoted_filter_id;
      quoted_filter_id = sql_quote (condition_filter_id);
      sql_int64 (&condition_filter,
                 "SELECT id FROM filters WHERE uuid = '%s'",
                 quoted_filter_id);
      term = filter_term (condition_filter_id);
      g_free (quoted_filter_id);
    }
  free (condition_filter_id);

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

  g_free (term);
  g_free (message);
  g_free (list);

  /* Get credential */
  recipient_credential_id = alert_data (alert, "method",
                                        "recipient_credential");
  recipient_credential = 0;
  if (recipient_credential_id)
    {
      find_credential_with_permission (recipient_credential_id,
                                       &recipient_credential, NULL);
    }

  /* Send email. */

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

/**
 * @brief Get the delta report to be used for an alert.
 *
 * @param[in]  alert         Alert.
 * @param[in]  task          Task.
 * @param[in]  report        Report.
 *
 * @return Report to compare with if required, else 0.
 */
static report_t
get_delta_report (alert_t alert, task_t task, report_t report)
{
  char *delta_type;
  report_t delta_report;

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

  if (delta_type == NULL)
    return 0;

  delta_report = 0;
  if (strcmp (delta_type, "Previous") == 0)
    {
      if (task_report_previous (task, report, &delta_report))
        g_warning ("%s: failed to get previous report", __func__);
    }
  else if (strcmp (delta_type, "Report") == 0)
    {
      char *delta_report_id;

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

      if (delta_report_id
          && find_report_with_permission (delta_report_id,
                                          &delta_report,
                                          "get_reports"))
        g_warning ("%s: error while finding report", __func__);
    }
  free (delta_type);

  return delta_report;
}

/**
 * @brief  Generates report results get data for an alert.
 *
 * @param[in]  alert              The alert to try to get the filter data from.
 * @param[in]  base_get_data      The get data for fallback and other data.
 * @param[out] alert_filter_get   Pointer to the newly allocated get_data.
 * @param[out] filter_return      Pointer to the filter.
 *
 * @return  0 success, -1 error, -3 filter not found.
 */
static int
generate_alert_filter_get (alert_t alert, const get_data_t *base_get_data,
                           get_data_t **alert_filter_get,
                           filter_t *filter_return)
{
  char *ignore_pagination;
  char *filt_id;
  filter_t filter;

  if (alert_filter_get == NULL)
    return -1;

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

  if (filter_return)
    *filter_return = filter;

  (*alert_filter_get) = g_malloc0 (sizeof (get_data_t));
  (*alert_filter_get)->details = base_get_data->details;
  (*alert_filter_get)->ignore_pagination = base_get_data->ignore_pagination;
  (*alert_filter_get)->ignore_max_rows_per_page
    = base_get_data->ignore_max_rows_per_page;

  if (filter)
    {
      (*alert_filter_get)->filt_id = g_strdup (filt_id);
      (*alert_filter_get)->filter = filter_term (filt_id);
    }
  else
    {
      (*alert_filter_get)->filt_id = NULL;
      (*alert_filter_get)->filter = g_strdup (base_get_data->filter);
    }

  /* Adjust filter for report composer.
   *
   * As a first step towards a full composer we have two fields stored
   * on the alert for controlling visibility of notes and overrides.
   *
   * We simply use these fields to adjust the filter.  In the future we'll
   * remove the filter terms and extend the way we get the report. */

  gchar *include_notes, *include_overrides;

  ignore_pagination = alert_data (alert, "method",
                                  "composer_ignore_pagination");
  if (ignore_pagination)
    {
      (*alert_filter_get)->ignore_pagination = atoi (ignore_pagination);
      g_free (ignore_pagination);
    }

  include_notes = alert_data (alert, "method",
                              "composer_include_notes");
  if (include_notes)
    {
      gchar *new_filter;

      new_filter = g_strdup_printf ("notes=%i %s",
                                    atoi (include_notes),
                                    (*alert_filter_get)->filter);
      g_free ((*alert_filter_get)->filter);
      (*alert_filter_get)->filter = new_filter;
      (*alert_filter_get)->filt_id = NULL;
      g_free (include_notes);
    }

  include_overrides = alert_data (alert, "method",
                                  "composer_include_overrides");
  if (include_overrides)
    {
      gchar *new_filter;

      new_filter = g_strdup_printf ("overrides=%i %s",
                                    atoi (include_overrides),
                                    (*alert_filter_get)->filter);
      g_free ((*alert_filter_get)->filter);
      (*alert_filter_get)->filter = new_filter;
      (*alert_filter_get)->filt_id = NULL;
      g_free (include_overrides);
    }

  return 0;
}

/**
 * @brief Generate report content for alert
 *
 * @param[in]  alert  The alert the report is generated for.
 * @param[in]  report Report or NULL to get last report of task.
 * @param[in]  task   Task the report belongs to.
 * @param[in]  get    GET data for the report.
 * @param[in]  report_format_data_name  Name of alert data with report format,
 *                                      or NULL if not configurable.
 * @param[in]  report_format_lookup     Name of report format to lookup if
 *                                      lookup by name, or NULL if not required.
 *                                      Used if report_format_data_name is
 *                                      NULL or fails.
 * @param[in]  fallback_format_id       UUID of fallback report format.  Used
 *                                      if both report_format_data_name and
 *                                      report_format_lookup are NULL or fail.
 * @param[in]  notes_details     Whether to include details of notes in report.
 * @param[in]  overrides_details Whether to include override details in report.
 * @param[out] content              Report content location.
 * @param[out] content_length       Length of report content.
 * @param[out] extension            File extension of report format.
 * @param[out] content_type         Content type of report format.
 * @param[out] term                 Filter term.
 * @param[out] report_zone          Actual timezone used in report.
 * @param[out] host_summary         Summary of results per host.
 * @param[out] used_report_format   Report format used.
 * @param[out] filter_return        Filter used.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter.
 */
static int
report_content_for_alert (alert_t alert, report_t report, task_t task,
                          const get_data_t *get,
                          const char *report_format_data_name,
                          const char *report_format_lookup,
                          const char *fallback_format_id,
                          int notes_details, int overrides_details,
                          gchar **content, gsize *content_length,
                          gchar **extension, gchar **content_type,
                          gchar **term, gchar **report_zone,
                          gchar **host_summary,
                          report_format_t *used_report_format,
                          filter_t *filter_return)
{
  int ret;
  report_format_t report_format;
  gboolean report_format_is_fallback = FALSE;
  report_config_t report_config;
  get_data_t *alert_filter_get;
  gchar *report_content;
  filter_t filter;

  assert (content);

  // Get filter

  ret = generate_alert_filter_get (alert, get, &alert_filter_get, &filter);
  if (ret)
    return ret;

  // Get last report from task if no report is given

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

  // Get report format or use fallback.

  report_format = 0;

  if (report_format_data_name)
    {
      gchar *format_uuid;

      format_uuid = alert_data (alert,
                                "method",
                                report_format_data_name);

      if (format_uuid && strlen (format_uuid))
        {
          if (find_report_format_with_permission (format_uuid,
                                                  &report_format,
                                                  "get_report_formats")
              || (report_format == 0))
            {
              g_warning ("%s: Could not find report format '%s' for %s",
                         __func__, format_uuid,
                         alert_method_name (alert_method (alert)));
              g_free (format_uuid);
              if (alert_filter_get)
                {
                  get_data_reset (alert_filter_get);
                  g_free (alert_filter_get);
                }
              return -2;
            }
          g_free (format_uuid);
        }
    }

  if (report_format_lookup && (report_format == 0))
    {
      if (lookup_report_format (report_format_lookup, &report_format)
          || (report_format == 0))
        {
          g_warning ("%s: Could not find report format '%s' for %s",
                     __func__, report_format_lookup,
                     alert_method_name (alert_method (alert)));
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          return -2;
        }
    }

  if (report_format == 0)
    {
      if (fallback_format_id == NULL)
        {
          g_warning ("%s: No fallback report format for %s",
                     __func__,
                     alert_method_name (alert_method (alert)));
          if (alert_filter_get)
            {
              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
            }
          return -1;
        }

      report_format_is_fallback = TRUE;

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

  // Get report config

  if (report_format_is_fallback)
    {
      // Config would only be valid for the original report format
      report_config = 0;
    }
  else
    {
      // TODO: Get report config from alert
      report_config = 0;
    }

  // Generate report content

  report_content = manage_report (report,
                                  get_delta_report (alert, task, report),
                                  alert_filter_get ? alert_filter_get : get,
                                  report_format,
                                  report_config,
                                  notes_details,
                                  overrides_details,
                                  content_length,
                                  extension,
                                  content_type,
                                  term,
                                  report_zone,
                                  host_summary);

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

  if (report_content == NULL)
    return -1;

  *content = report_content;
  *used_report_format = report_format;

  return 0;
}

/**
 * @brief  Generates a filename or path for a report.
 *
 * If no custom_format is given, the setting "Report Export File Name"
 *  is used instead.
 *
 * @param[in]  report         The report to generate the filename for.
 * @param[in]  report_format  The report format to use.
 * @param[in]  custom_format  A custom format string to use for the filename.
 * @param[in]  add_extension  Whether to add the filename extension or not.
 *
 * @return  Newly allocated filename.
 */
static gchar *
generate_report_filename (report_t report, report_format_t report_format,
                          const char *custom_format, gboolean add_extension)
{
  task_t task;
  char *fname_format, *report_id, *creation_time, *modification_time;
  char *report_task_name, *rf_name;
  gchar *filename_base, *filename;

  if (custom_format && strcmp (custom_format, ""))
    fname_format = g_strdup (custom_format);
  else
    fname_format
      = sql_string ("SELECT value FROM settings"
                    " WHERE name"
                    "       = 'Report Export File Name'"
                    " AND " ACL_GLOBAL_OR_USER_OWNS ()
                    " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                    current_credentials.uuid);

  report_id = report_uuid (report);

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

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

  report_task (report, &task);
  report_task_name = task_name (task);

  rf_name = report_format ? report_format_name (report_format)
                          : g_strdup ("unknown");

  filename_base
    = gvm_export_file_name (fname_format,
                            current_credentials.username,
                            "report", report_id,
                            creation_time, modification_time,
                            report_task_name, rf_name);

  if (add_extension && report_format)
    {
      gchar *extension;
      extension = report_format_extension (report_format);
      filename = g_strdup_printf ("%s.%s", filename_base, extension);
      free (extension);
    }
  else
    filename = g_strdup (filename_base);

  free (fname_format);
  free (report_id);
  free (creation_time);
  free (modification_time);
  free (report_task_name);
  free (rf_name);
  g_free (filename_base);

  return filename;
}

/**
 * @brief Escalate an event.
 *
 * @param[in]  alert       Alert.
 * @param[in]  task        Task.
 * @param[in]  report      Report.  0 for most recent report.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[in]  get         GET data for report.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[out] script_message  Custom error message from the script.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter, -4 failed to find credential, -5 alert script failed.
 */
static int
escalate_to_vfire (alert_t alert, task_t task, report_t report, event_t event,
                   const void* event_data, alert_method_t method,
                   alert_condition_t condition, const get_data_t *get,
                   int notes_details, int overrides_details,
                   gchar **script_message)
{
  int ret;
  char *credential_id;
  get_data_t *alert_filter_get;
  filter_t filter;
  credential_t credential;
  char *base_url, *session_type, *client_id, *username, *password;
  char *report_formats_str;
  gchar **report_formats, **point;
  char reports_dir[] = "/tmp/gvmd_XXXXXX";
  gboolean is_first_report = TRUE;
  GString *format_names;
  GPtrArray *reports;
  char *report_zone;
  gchar *host_summary;
  iterator_t data_iterator;
  GTree *call_input;
  char *description_template;
  int name_offset;

  if ((event == EVENT_TICKET_RECEIVED)
      || (event == EVENT_ASSIGNED_TICKET_CHANGED)
      || (event == EVENT_OWNED_TICKET_CHANGED))
    {
      g_warning ("%s: Ticket events with method"
                 " \"Alemba vFire\" not support",
                 __func__);
      return -1;
    }

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

  // Get report results filter and corresponding get data
  alert_filter_get = NULL;
  ret = generate_alert_filter_get (alert, get, &alert_filter_get, &filter);
  if (ret)
    return ret;

  // Generate reports
  if (mkdtemp (reports_dir) == NULL)
    {
      g_warning ("%s: mkdtemp failed", __func__);
      get_data_reset (alert_filter_get);
      g_free (alert_filter_get);
      return -1;
    }

  reports = g_ptr_array_new_full (0, (GDestroyNotify) alert_report_data_free);
  report_formats_str = alert_data (alert, "method", "report_formats");
  report_formats = g_strsplit_set (report_formats_str, ",", 0);
  point = report_formats;
  free (report_formats_str);

  report_zone = NULL;
  host_summary = NULL;
  format_names = g_string_new ("");
  while (*point)
    {
      gchar *report_format_id;
      report_format_t report_format;
      report_config_t report_config;

      report_format_id = g_strstrip (*point);
      find_report_format_with_permission (report_format_id,
                                          &report_format,
                                          "get_report_formats");

      // TODO: Add option to set report configs
      report_config = 0;

      if (report_format)
        {
          alert_report_data_t *alert_report_item;
          size_t content_length;
          gchar *report_content;
          GError *error = NULL;

          alert_report_item = g_malloc0 (sizeof (alert_report_data_t));

          report_content = manage_report (report,
                                          get_delta_report
                                            (alert, task, report),
                                          alert_filter_get
                                            ? alert_filter_get
                                            : get,
                                          report_format,
                                          report_config,
                                          notes_details,
                                          overrides_details,
                                          &content_length,
                                          NULL /* extension */,
                                          &(alert_report_item->content_type),
                                          NULL /* term */,
                                          is_first_report
                                            ? &report_zone
                                            : NULL,
                                          is_first_report
                                            ? &host_summary
                                            : NULL);
          if (report_content == NULL)
            {
              g_warning ("%s: Failed to generate report", __func__);

              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
              alert_report_data_free (alert_report_item);
              g_strfreev (report_formats);
              return -1;
            }

          alert_report_item->report_format_name
            = report_format_name (report_format);

          if (is_first_report == FALSE)
            g_string_append (format_names, ", ");
          g_string_append (format_names,
                           alert_report_item->report_format_name);

          alert_report_item->remote_filename
            = generate_report_filename (report, report_format, NULL, TRUE);

          alert_report_item->local_filename
            = g_build_filename (reports_dir,
                                alert_report_item->remote_filename,
                                NULL);

          g_file_set_contents (alert_report_item->local_filename,
                               report_content, content_length,
                               &error);
          g_free (report_content);
          if (error)
            {
              g_warning ("%s: Failed to write report to %s: %s",
                         __func__,
                         alert_report_item->local_filename,
                         error->message);

              get_data_reset (alert_filter_get);
              g_free (alert_filter_get);
              alert_report_data_free (alert_report_item);
              g_strfreev (report_formats);
              return -1;
            }
          g_ptr_array_add (reports, alert_report_item);
          is_first_report = FALSE;
        }

      point ++;
    }
  g_strfreev (report_formats);

  // Find vFire credential
  credential_id = alert_data (alert, "method",
                              "vfire_credential");
  if (find_credential_with_permission (credential_id, &credential,
                                       "get_credentials"))
    {
      get_data_reset (alert_filter_get);
      g_free (alert_filter_get);
      free (credential_id);
      return -1;
    }
  else if (credential == 0)
    {
      get_data_reset (alert_filter_get);
      g_free (alert_filter_get);
      free (credential_id);
      return -4;
    }

  // vFire General data
  base_url = alert_data (alert, "method",
                          "vfire_base_url");

  // vFire Login data
  session_type = alert_data (alert, "method", "vfire_session_type");
  client_id = alert_data (alert, "method", "vfire_client_id");

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

  // Call input data
  call_input = g_tree_new_full ((GCompareDataFunc) g_strcmp0,
                                NULL, g_free, g_free);
  name_offset = strlen ("vfire_call_");
  init_iterator (&data_iterator,
                 "SELECT name, data"
                 " FROM alert_method_data"
                 " WHERE alert = %llu"
                 " AND name %s 'vfire_call_%%';",
                 alert, sql_ilike_op ());

  while (next (&data_iterator))
    {
      gchar *name, *value;
      name = g_strdup (iterator_string (&data_iterator, 0)
                        + name_offset);
      value = g_strdup (iterator_string (&data_iterator, 1));

      g_tree_replace (call_input, name, value);
    }
  cleanup_iterator (&data_iterator);

  // Special case for descriptionterm
  description_template = alert_data (alert, "method",
                                     "vfire_call_description");
  if (description_template == NULL)
    description_template = g_strdup (ALERT_VFIRE_CALL_DESCRIPTION);

  gchar *description;
  description = alert_message_print (description_template,
                                     event,
                                     event_data,
                                     task,
                                     alert,
                                     condition,
                                     format_names->str,
                                     filter,
                                     alert_filter_get
                                       ? alert_filter_get->filter
                                       : (get->filter ? get->filter : ""),
                                     report_zone,
                                     host_summary,
                                     NULL,
                                     0,
                                     0,
                                     0,
                                     max_attach_length);

  g_tree_replace (call_input,
                  g_strdup ("description"),
                  description);

  // Create vFire ticket
  ret = send_to_vfire (base_url, client_id, session_type, username,
                       password, reports, call_input, description_template,
                       script_message);

  // Cleanup
  gvm_file_remove_recurse (reports_dir);

  if (alert_filter_get)
    {
      get_data_reset (alert_filter_get);
      g_free (alert_filter_get);
    }
  free (base_url);
  free (session_type);
  free (client_id);
  free (username);
  free (password);
  g_ptr_array_free (reports, TRUE);
  g_tree_destroy (call_input);
  free (description_template);

  return ret;
}

/**
 * @brief Escalate an event.
 *
 * @param[in]  alert   Alert.
 * @param[in]  task        Task.
 * @param[in]  report      Report.  0 for most recent report.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[in]  get         GET data for report.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[out] script_message  Custom error message from the script.
 *
 * @return 0 success, -1 error, -2 failed to find report format, -3 failed to
 *         find filter, -4 failed to find credential, -5 alert script failed.
 */
static int
escalate_2 (alert_t alert, task_t task, report_t report, event_t event,
            const void* event_data, alert_method_t method,
            alert_condition_t condition,
            const get_data_t *get, int notes_details, int overrides_details,
            gchar **script_message)
{
  if (script_message)
    *script_message = NULL;

  {
    char *name_alert;
    gchar *event_desc, *alert_desc;

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

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

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

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

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

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

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

              if (event == EVENT_TICKET_RECEIVED)
                {
                  ret = email_ticket (alert, task, event, event_data, method,
                                      condition, to_address, from_address,
                                      "Ticket received");
                  free (to_address);
                  free (from_address);
                  return ret;
                }

              if (event == EVENT_ASSIGNED_TICKET_CHANGED)
                {
                  ret = email_ticket (alert, task, event, event_data, method,
                                      condition, to_address, from_address,
                                      "Assigned ticket changed");
                  free (to_address);
                  free (from_address);
                  return ret;
                }

              if (event == EVENT_OWNED_TICKET_CHANGED)
                {
                  ret = email_ticket (alert, task, event, event_data, method,
                                      condition, to_address, from_address,
                                      "Owned ticket changed");
                  free (to_address);
                  free (from_address);
                  return ret;
                }

              notice = alert_data (alert, "method", "notice");
              name = task_name (task);

              if (notice && strcmp (notice, "0") == 0)
                {
                  gchar *event_desc, *condition_desc, *report_content;
                  gchar *alert_subject, *message;
                  gchar *term, *report_zone, *host_summary;
                  report_format_t report_format = 0;
                  gsize content_length;
                  filter_t filter;

                  /* Message with inlined report. */

                  term = NULL;
                  report_zone = NULL;
                  host_summary = NULL;
                  /* report_content_for_alert always sets this, but init it
                   * anyway, to make it easier for the compiler to see. */
                  filter = 0;
                  ret = report_content_for_alert
                          (alert, report, task, get,
                           "notice_report_format",
                           NULL,
                           /* TXT fallback */
                           "a3810a62-1f62-11e1-9219-406186ea4fc5",
                           notes_details, overrides_details,
                           &report_content, &content_length, &extension,
                           NULL, &term, &report_zone, &host_summary,
                           &report_format, &filter);
                  if (ret || report_content == NULL)
                    {
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      g_free (term);
                      g_free (report_zone);
                      g_free (host_summary);
                      return -1;
                    }
                  format_name = report_format_name (report_format);
                  condition_desc = alert_condition_description (condition,
                                                                alert);
                  event_desc = event_description (event, event_data, NULL);
                  subject = g_strdup_printf ("[GVM] Task '%s': %s",
                                             name ? name : "Internal Error",
                                             event_desc);
                  g_free (event_desc);

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

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

                  /* Message with attached report. */

                  term = NULL;
                  report_zone = NULL;
                  host_summary = NULL;
                  /* report_content_for_alert always sets this, but init it
                   * anyway, to make it easier for the compiler to see. */
                  filter = 0;
                  ret = report_content_for_alert
                          (alert, report, task, get,
                           "notice_attach_format",
                           NULL,
                           /* TXT fallback */
                           "a3810a62-1f62-11e1-9219-406186ea4fc5",
                           notes_details, overrides_details,
                           &report_content, &content_length, &extension,
                           &type, &term, &report_zone, &host_summary,
                           &report_format, &filter);
                  if (ret || report_content == NULL)
                    {
                      free (notice);
                      free (name);
                      free (to_address);
                      free (from_address);
                      g_free (term);
                      g_free (report_zone);
                      g_free (host_summary);
                      return -1;
                    }
                  format_name = report_format_name (report_format);
                  condition_desc = alert_condition_description (condition,
                                                                    alert);
                  event_desc = event_description (event, event_data, NULL);
                  subject = g_strdup_printf ("[GVM] Task '%s': %s",
                                             name ? name : "Internal Error",
                                             event_desc);
                  g_free (event_desc);

                  alert_subject = alert_data (alert, "method", "subject");
                  if (alert_subject && strlen (alert_subject))
                    {
                      g_free (subject);
                      subject = alert_subject_print (alert_subject, event,
                                                     event_data,
                                                     alert, task, 0);
                    }
                  g_free (alert_subject);
                  if (max_attach_length <= 0
                      || content_length <= max_attach_length)
                    base64 = g_base64_encode ((guchar*) report_content,
                                              content_length);
                  g_free (report_content);
                  message = alert_data (alert, "method", "message");
                  if (message == NULL || strlen (message) == 0)
                    {
                      g_free (message);
                      message = g_strdup (ALERT_MESSAGE_ATTACH);
                    }
                  body = alert_message_print (message, event, event_data,
                                              task, alert, condition,
                                              format_name, filter,
                                              term, report_zone,
                                              host_summary, NULL, 0,
                                              base64 == NULL,
                                              0,
                                              max_attach_length);
                  g_free (message);
                  g_free (condition_desc);
                  g_free (term);
                  g_free (report_zone);
                  g_free (host_summary);
                }
              else
                {
                  gchar *event_desc, *generic_desc, *condition_desc;
                  gchar *alert_subject, *message;

                  /* Simple notice message. */

                  format_name = NULL;
                  event_desc = event_description (event, event_data, name);
                  generic_desc = event_description (event, event_data, NULL);
                  condition_desc = alert_condition_description (condition,
                                                                    alert);

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

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

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

              gchar *fname_format, *file_name;
              gchar *report_id, *creation_time, *modification_time;
              char *recipient_credential_id;
              credential_t recipient_credential;

              fname_format
                = sql_string ("SELECT value FROM settings"
                              " WHERE name"
                              "       = 'Report Export File Name'"
                              " AND " ACL_GLOBAL_OR_USER_OWNS ()
                              " ORDER BY coalesce (owner, 0) DESC LIMIT 1;",
                              current_credentials.uuid);

              report_id = report_uuid (report);

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

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

              file_name
                = gvm_export_file_name (fname_format,
                                        current_credentials.username,
                                        "report", report_id,
                                        creation_time, modification_time,
                                        name, format_name);

              /* Get credential */
              recipient_credential_id = alert_data (alert, "method",
                                                    "recipient_credential");
              recipient_credential = 0;
              if (recipient_credential_id)
                {
                  find_credential_with_permission (recipient_credential_id,
                                                  &recipient_credential, NULL);
                }

              ret = email (to_address, from_address, subject, body, base64,
                           type, file_name ? file_name : "openvas-report",
                           extension, recipient_credential);

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

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"HTTP Get\" not support",
                         __func__);
              return -1;
            }

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

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

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

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

              ret = http_get (new_url->str);
              g_string_free (new_url, TRUE);
              g_free (url);
              return ret;
            }
          return -1;
        }
      case ALERT_METHOD_SCP:
        {
          credential_t credential;
          char *credential_id;
          char *private_key, *password, *username, *host, *path, *known_hosts;
          char *port_str;
          int port;
          gchar *report_content, *alert_path;
          gsize content_length;
          report_format_t report_format;
          int ret;

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"SCP\" not support",
                         __func__);
              return -1;
            }

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

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

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

                  host = alert_data (alert, "method", "scp_host");
                  port_str = alert_data (alert, "method", "scp_port");
                  if (port_str)
                    port = atoi (port_str);
                  else
                    port = 22;
                  path = alert_data (alert, "method", "scp_path");
                  known_hosts = alert_data (alert, "method", "scp_known_hosts");

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

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

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

                  return ret;
                }
            }

          ret = report_content_for_alert
                  (alert, 0, task, get,
                   "scp_report_format",
                   NULL,
                   /* XML fallback. */
                   "a994b278-1f62-11e1-96ac-406186ea4fc5",
                   notes_details, overrides_details,
                   &report_content, &content_length, NULL,
                   NULL, NULL, NULL, NULL,
                   &report_format, NULL);
          if (ret || report_content == NULL)
            {
              g_warning ("%s: Empty Report", __func__);
              return -1;
            }

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


              host = alert_data (alert, "method", "scp_host");
              port_str = alert_data (alert, "method", "scp_port");
              if (port_str)
                port = atoi (port_str);
              else
                port = 22;
              path = alert_data (alert, "method", "scp_path");
              known_hosts = alert_data (alert, "method", "scp_known_hosts");

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

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

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

          return ret;
        }
      case ALERT_METHOD_SEND:
        {
          char *host, *port;
          gchar *report_content;
          gsize content_length;
          report_format_t report_format;
          int ret;

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"Send\" not support",
                         __func__);
              return -1;
            }

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

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

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

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

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

              return ret;
            }

          ret = report_content_for_alert
                  (alert, 0, task, get,
                   "send_report_format",
                   NULL,
                   /* XML fallback. */
                   "a994b278-1f62-11e1-96ac-406186ea4fc5",
                   notes_details, overrides_details,
                   &report_content, &content_length, NULL,
                   NULL, NULL, NULL, NULL,
                   &report_format, NULL);
          if (ret || report_content == NULL)
            {
              g_warning ("%s: Empty Report", __func__);
              return -1;
            }

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

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

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

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

          return ret;
        }
      case ALERT_METHOD_SMB:
        {
          char *credential_id, *username, *password;
          char *share_path, *file_path_format, *max_protocol;
          gboolean file_path_is_dir;
          report_format_t report_format;
          gchar *file_path, *report_content, *extension;
          gsize content_length;
          credential_t credential;
          int ret;

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"SMP\" not support",
                         __func__);
              return -1;
            }

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

          if (task == 0 && report)
            {
              ret = report_task (report, &task);
              if (ret)
                return ret;
            }

          credential_id = alert_data (alert, "method", "smb_credential");
          share_path = alert_data (alert, "method", "smb_share_path");
          max_protocol = alert_data (alert, "method", "smb_max_protocol");

          file_path_format
            = sql_string ("SELECT value FROM tags"
                          " WHERE name = 'smb-alert:file_path'"
                          "   AND EXISTS"
                          "         (SELECT * FROM tag_resources"
                          "           WHERE resource_type = 'task'"
                          "             AND resource = %llu"
                          "             AND tag = tags.id)"
                          " ORDER BY modification_time LIMIT 1;",
                          task);

          if (file_path_format == NULL)
            file_path_format = alert_data (alert, "method", "smb_file_path");

          file_path_is_dir = (g_str_has_suffix (file_path_format, "\\")
                              || g_str_has_suffix (file_path_format, "/"));

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

          g_debug ("smb_credential: %s", credential_id);
          g_debug ("smb_share_path: %s", share_path);
          g_debug ("smb_file_path: %s (%s)",
                   file_path_format, file_path_is_dir ? "dir" : "file");

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

          if (file_path_is_dir)
            {
              char *dirname, *filename;

              dirname = generate_report_filename (report, report_format,
                                                  file_path_format, FALSE);
              filename = generate_report_filename (report, report_format,
                                                   NULL, TRUE);

              file_path = g_strdup_printf ("%s\\%s", dirname, filename);

              free (dirname);
              free (filename);
            }
          else
            {
              file_path = generate_report_filename (report, report_format,
                                                    file_path_format, TRUE);
            }

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

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

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

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

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"SNMP\" not support",
                         __func__);
              return -1;
            }

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

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

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

                  type = g_strdup (event_data);

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

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

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

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

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

          free (agent);
          free (community);
          free (snmp_message);
          g_free (message);
          return ret;
        }
      case ALERT_METHOD_SOURCEFIRE:
        {
          char *ip, *port, *pkcs12, *pkcs12_credential_id;
          credential_t pkcs12_credential;
          gchar *pkcs12_password, *report_content;
          gsize content_length;
          report_format_t report_format;
          int ret;

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"Sourcefire\" not support",
                         __func__);
              return -1;
            }

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

          ret = report_content_for_alert
                  (alert, report, task, get,
                   NULL,
                   "Sourcefire",
                   NULL,
                   notes_details, overrides_details,
                   &report_content, &content_length, NULL,
                   NULL, NULL, NULL, NULL, &report_format, NULL);
          if (ret || report_content == NULL)
            {
              g_warning ("%s: Empty Report", __func__);
              return -1;
            }

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

          if (pkcs12_credential_id == NULL
              || strcmp (pkcs12_credential_id, "") == 0)
            {
              pkcs12_password = g_strdup ("");
            }
          else if (find_credential_with_permission (pkcs12_credential_id,
                                               &pkcs12_credential,
                                               "get_credentials"))
            {
              g_free (ip);
              g_free (port);
              g_free (pkcs12);
              g_free (pkcs12_credential_id);
              return -1;
            }
          else if (pkcs12_credential == 0)
            {
              g_free (ip);
              g_free (port);
              g_free (pkcs12);
              g_free (pkcs12_credential_id);
              return -4;
            }
          else
            {
              g_free (pkcs12_credential_id);
              pkcs12_password = credential_encrypted_value (pkcs12_credential,
                                                            "password");
            }

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

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

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

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

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

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

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

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

          g_free (level);
          g_free (message);

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

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"TippingPoint SMS\" not support",
                         __func__);
              return -1;
            }

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

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

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

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

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

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

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

          if ((event == EVENT_TICKET_RECEIVED)
              || (event == EVENT_ASSIGNED_TICKET_CHANGED)
              || (event == EVENT_OWNED_TICKET_CHANGED))
            {
              g_warning ("%s: Ticket events with method"
                         " \"Verinice\" not support",
                         __func__);
              return -1;
            }

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

          ret = report_content_for_alert
                  (alert, report, task, get,
                   "verinice_server_report_format",
                   "Verinice ISM",
                   NULL,
                   notes_details, overrides_details,
                   &report_content, &content_length, NULL,
                   NULL, NULL, NULL, NULL, &report_format, NULL);
          if (ret || report_content == NULL)
            {
              g_warning ("%s: Empty Report", __func__);
              return -1;
            }

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

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

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

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

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

              return ret;
            }
        }
      case ALERT_METHOD_VFIRE:
        {
          int ret;
          ret = escalate_to_vfire (alert, task, report, event,
                                   event_data, method, condition,
                                   get, notes_details, overrides_details,
                                   script_message);
          return ret;
        }
      case ALERT_METHOD_START_TASK:
        {
          gvm_connection_t connection;
          char *task_id, *report_id, *owner_id, *owner_name;
          gmp_authenticate_info_opts_t auth_opts;

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

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

          task_id = alert_data (alert, "method", "start_task_task");
          if (task_id == NULL || strcmp (task_id, "") == 0)
            {
              g_warning ("%s: start_task_task missing or empty", __func__);
              return -1;
            }

          owner_id = sql_string ("SELECT uuid FROM users"
                                 " WHERE id = (SELECT owner FROM alerts"
                                 "              WHERE id = %llu)",
                                 alert);
          owner_name = sql_string ("SELECT name FROM users"
                                   " WHERE id = (SELECT owner FROM alerts"
                                   "              WHERE id = %llu)",
                                   alert);

          if (owner_id == NULL)
            {
              g_warning ("%s: could not find alert owner",
                         __func__);
              free (owner_id);
              free (owner_name);
              return -1;
            }

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

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

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

          /* Start the task. */

          auth_opts = gmp_authenticate_info_opts_defaults;
          auth_opts.username = owner_name;
          auth_opts.password = "dummy";
          if (gmp_authenticate_info_ext_c (&connection, auth_opts))
            {
              g_free (task_id);
              free (owner_id);
              free (owner_name);
              gvm_connection_free (&connection);
              gvm_close_sentry ();
              exit (EXIT_FAILURE);
            }
          if (gmp_start_task_report_c (&connection, task_id, &report_id))
            {
              g_free (task_id);
              free (owner_id);
              free (owner_name);
              gvm_connection_free (&connection);
              gvm_close_sentry ();
              exit (EXIT_FAILURE);
            }

          g_free (task_id);
          g_free (report_id);
          free (owner_id);
          free (owner_name);
          gvm_connection_free (&connection);
          gvm_close_sentry ();
          exit (EXIT_SUCCESS);
        }
      case ALERT_METHOD_ERROR:
      default:
        break;
    }
  return -1;
}

/**
 * @brief Escalate an event with preset report filtering.
 *
 * @param[in]  alert       Alert.
 * @param[in]  task        Task.
 * @param[in]  report      Report.
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  method      Method from alert.
 * @param[in]  condition   Condition from alert, which was met by event.
 * @param[out] script_message  Custom error message from alert script.
 *
 * @return 0 success, -1 error, -2 failed to find report format for alert,
 *         -3 failed to find filter for alert, -4 failed to find credential,
 *         -5 alert script failed.
 */
static int
escalate_1 (alert_t alert, task_t task, report_t report, event_t event,
            const void* event_data, alert_method_t method,
            alert_condition_t condition, gchar **script_message)
{
  int ret;
  get_data_t get;
  char *results_filter;

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

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

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

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

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

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

  if (task_id == NULL || strcmp (task_id, "0") == 0)
    task = 0;
  else
    {
      if (find_task_with_permission (task_id, &task, NULL))
        return -1;
      if (task == 0)
        return 2;
    }

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

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

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

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

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

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

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

/**
 * @brief Test an alert.
 *
 * @param[in]  alert_id    Alert UUID.
 * @param[out] script_message  Custom message from the alert script.
 *
 * @return 0 success, 1 failed to find alert, 2 failed to find task,
 *         99 permission denied, -1 error, -2 failed to find report format
 *         for alert, -3 failed to find filter for alert, -4 failed to find
 *         credential for alert, -5 alert script failed.
 */
int
manage_test_alert (const char *alert_id, gchar **script_message)
{
  int ret;
  alert_t alert;
  task_t task;
  report_t report;
  result_t result;
  char *task_id, *report_id;
  time_t now;
  char now_string[26];
  gchar *clean;

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

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

  if (alert_event (alert) == EVENT_NEW_SECINFO
      || alert_event (alert) == EVENT_UPDATED_SECINFO)
    {
      char *alert_event_data;
      gchar *type;

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

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

      g_free (type);

      return ret;
    }

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

  report_id = gvm_uuid_make ();
  if (report_id == NULL)
    return -1;
  task_uuid (task, &task_id);
  report = make_report (task, report_id, TASK_STATUS_DONE);

  result = make_result (task, "127.0.0.1", "localhost", "23/tcp",
                        "1.3.6.1.4.1.25623.1.0.10330", "Alarm",
                        "A telnet server seems to be running on this port.",
                        NULL);
  if (result)
    report_add_result (report, result);
  

  result = make_result (
              task, "127.0.0.1", "localhost", "general/tcp",
              "1.3.6.1.4.1.25623.1.0.103823", "Alarm",
              "IP,Host,Port,SSL/TLS-Version,Ciphers,Application-CPE\n"
              "127.0.0.1,localhost,443,TLSv1.1;TLSv1.2",
              NULL);
  if (result)
    report_add_result (report, result);

  now = time (NULL);
  if (strlen (ctime_r (&now, now_string)) == 0)
    {
      ret = -1;
      goto exit;
    }
  clean = g_strdup (now_string);
  if (clean[strlen (clean) - 1] == '\n')
    clean[strlen (clean) - 1] = '\0';
  set_task_start_time_ctime (task, g_strdup (clean));
  set_scan_start_time_ctime (report, g_strdup (clean));
  set_scan_host_start_time_ctime (report, "127.0.0.1", clean);

  insert_report_host_detail (report,
                             "127.0.0.1",
                             "nvt",
                             "1.3.6.1.4.1.25623.1.0.108577",
                             "",
                             "App",
                             "cpe:/a:openbsd:openssh:8.9p1",
                             "0123456789ABCDEF0123456789ABCDEF");

  insert_report_host_detail (report,
                             "127.0.0.1",
                             "nvt",
                             "1.3.6.1.4.1.25623.1.0.10330",
                             "Host Details",
                             "best_os_cpe",
                             "cpe:/o:canonical:ubuntu_linux:22.04",
                             "123456789ABCDEF0123456789ABCDEF0");

  set_scan_host_end_time_ctime (report, "127.0.0.1", clean);
  set_scan_end_time_ctime (report, clean);
  g_free (clean);
  ret = manage_alert (alert_id,
                      task_id,
                      EVENT_TASK_RUN_STATUS_CHANGED,
                      (void*) TASK_STATUS_DONE,
                      script_message);
 exit:
  /* No one should be running this task, so we don't worry about the lock.  We
   * could guarantee that no one runs the task, but this is a very rare case. */
  delete_task (task, 1);
  free (task_id);
  free (report_id);
  return ret;
}

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

          if (alert_applies_to_task (alert, event_resource) == 0)
            return 0;

          alert_event_data = alert_data (alert, "event", "status");
          if (alert_event_data == NULL)
            return 0;
          ret = (task_run_status (event_resource) == (task_status_t) event_data)
                && (strcmp (alert_event_data,
                            run_status_name_internal ((task_status_t)
                                                      event_data))
                    == 0);
          free (alert_event_data);
          return ret;
        }
      case EVENT_NEW_SECINFO:
      case EVENT_UPDATED_SECINFO:
        {
          char *alert_event_data;

          alert_event_data = alert_data (alert, "event", "secinfo_type");
          if (alert_event_data == NULL)
            return 0;
          if (strcasecmp (alert_event_data, event_data) == 0)
            return 1;
          return 0;
        }
      case EVENT_TICKET_RECEIVED:
      case EVENT_ASSIGNED_TICKET_CHANGED:
        return ticket_assigned_to (event_resource) == alert_owner (alert);
      case EVENT_OWNED_TICKET_CHANGED:
        return ticket_owner (event_resource) == alert_owner (alert);
      default:
        return 0;
    }
}

/**
 * @brief Return the SecInfo count.
 *
 * @param[in]  alert      Alert.
 * @param[in]  filter_id  Condition filter id.
 *
 * @return 1 if met, else 0.
 */
static time_t
alert_secinfo_count (alert_t alert, char *filter_id)
{
  get_data_t get;
  int db_count, uuid_was_null;
  event_t event;
  gboolean get_modified;
  time_t feed_version_epoch;
  char *secinfo_type;

  event = alert_event (alert);
  get_modified = (event == EVENT_UPDATED_SECINFO);

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

  memset (&get, '\0', sizeof (get));
  if (filter_id && strlen (filter_id) && strcmp (filter_id, "0"))
    get.filt_id = filter_id;

  secinfo_type = alert_data (alert, "event", "secinfo_type");

  if (strcmp (secinfo_type, "nvt") == 0)
    {
      feed_version_epoch = nvts_feed_version_epoch ();
      db_count = nvt_info_count_after (&get,
                                       feed_version_epoch,
                                       get_modified);
    }
  else if (strcmp (secinfo_type, "cert_bund_adv") == 0
           || strcmp (secinfo_type, "dfn_cert_adv") == 0)
    {
      feed_version_epoch = cert_check_time ();
      db_count = secinfo_count_after (&get,
                                      secinfo_type,
                                      feed_version_epoch,
                                      get_modified);
    }
  else // assume SCAP data
    {
      feed_version_epoch = scap_check_time ();
      db_count = secinfo_count_after (&get,
                                      secinfo_type,
                                      feed_version_epoch,
                                      get_modified);
    }

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

  return db_count;
}

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

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

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

          if (task == 0)
            {
              int db_count;

              /* SecInfo event. */

              db_count = alert_secinfo_count (alert, filter_id);

              if (db_count >= count)
                return 1;
              break;
            }

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

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

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

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

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

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

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

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

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

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

              if (second_last_report)
                {
                  int cmp, second_last_count;

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

                  cmp = last_count - second_last_count;
                  g_debug ("cmp: %i (vs %i)", cmp, count);
                  g_debug ("direction: %s", direction);
                  g_debug ("last_count: %i", last_count);
                  g_debug ("second_last_count: %i", second_last_count);
                  if (count < 0)
                    {
                      count = -count;
                      if (direction == NULL
                          || strcasecmp (direction, "increased") == 0)
                        {
                          free (direction);
                          direction = g_strdup ("decreased");
                        }
                      else if (strcasecmp (direction, "decreased") == 0)
                        {
                          free (direction);
                          direction = g_strdup ("increased");
                        }
                    }
                  if (direction == NULL)
                    {
                      /* Same as "increased". */
                      if (cmp >= count)
                        {
                          free (filter_id);
                          return 1;
                        }
                    }
                  else if (((strcasecmp (direction, "changed") == 0)
                            && (abs (cmp) >= count))
                           || ((strcasecmp (direction, "increased") == 0)
                               && (cmp >= count))
                           || ((strcasecmp (direction, "decreased") == 0)
                               && (cmp <= count)))
                    {
                      free (direction);
                      free (filter_id);
                      return 1;
                    }
                }
              else
                {
                  g_debug ("direction: %s", direction);
                  g_debug ("last_count: %i", last_count);
                  g_debug ("second_last_count NULL");
                  if (direction == NULL)
                    {
                      /* Same as "increased". */
                      if (last_count > 0)
                        {
                          free (filter_id);
                          return 1;
                        }
                    }
                  else if (((strcasecmp (direction, "changed") == 0)
                       || (strcasecmp (direction, "increased") == 0))
                      && (last_count > 0))
                    {
                      free (direction);
                      free (filter_id);
                      return 1;
                    }
                }
            }
          free (direction);
          free (filter_id);
          break;
        }
      case ALERT_CONDITION_SEVERITY_AT_LEAST:
        {
          char *condition_severity_str;

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

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

          if (condition_severity_str)
            {
              double condition_severity_dbl, task_severity_dbl;

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

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

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

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

/**
 * @brief Produce an event.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event type specific details.
 * @param[in]  resource_1  Event type specific resource 1.  For example,
 *                         a task for EVENT_TASK_RUN_STATUS_CHANGED.
 * @param[in]  resource_2  Event type specific resource 2.
 */
void
event (event_t event, void* event_data, resource_t resource_1,
       resource_t resource_2)
{
  iterator_t alerts;
  GArray *alerts_triggered;
  guint index;

  g_debug ("   EVENT %i on resource %llu", event, resource_1);

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

  if ((event == EVENT_TASK_RUN_STATUS_CHANGED)
      && (((task_status_t) event_data) == TASK_STATUS_DONE))
    check_tickets (resource_1);

  init_event_alert_iterator (&alerts, event);
  while (next (&alerts))
    {
      alert_t alert = event_alert_iterator_alert (&alerts);
      if (event_alert_iterator_active (&alerts)
          && event_applies (event, event_data, resource_1, alert))
        {
          alert_condition_t condition;

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

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

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

  g_array_free (alerts_triggered, TRUE);
}

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

  assert (alert);

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

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

  g_free (with_clause);
  g_free (available);
}

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

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

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


/* Task functions. */

/**
 * @brief  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 Filter columns for task iterator.
 */
#define TASK_ITERATOR_FILTER_COLUMNS                                          \
 { GET_ITERATOR_FILTER_COLUMNS, "status", "total", "first_report",            \
   "last_report", "threat", "trend", "severity", "schedule", "next_due",      \
   "first", "last", "false_positive", "log", "low", "medium", "high",         \
   "hosts", "result_hosts", "fp_per_host", "log_per_host", "low_per_host",    \
   "medium_per_host", "high_per_host", "target", "usage_type",                \
   "first_report_created", "last_report_created", NULL }

/**
 * @brief Task iterator columns.
 */
#define TASK_ITERATOR_COLUMNS_INNER                                         \
   { "run_status", NULL, KEYWORD_TYPE_INTEGER },                            \
   {                                                                        \
     "(SELECT count(*) FROM reports"                                        \
     " WHERE task = tasks.id)",                                             \
     "total",                                                               \
     KEYWORD_TYPE_INTEGER                                                   \
   },                                                                       \
   {                                                                        \
     "(SELECT uuid FROM reports WHERE task = tasks.id"                      \
     /* TODO 1 == TASK_STATUS_DONE */                                       \
     " AND scan_run_status = 1"                                             \
     " ORDER BY 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 target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod,"                  \
     "                        'False Positive')"                             \
     " END",                                                                 \
     "false_positive",                                                       \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Log')"           \
     " END",                                                                 \
     "log",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Low')"           \
     " END",                                                                 \
     "low",                                                                  \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'Medium')"        \
     " END",                                                                 \
     "medium",                                                               \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_severity_count (task_last_report (id),"                        \
     "                        opts.override, opts.min_qod, 'High')"          \
     " END",                                                                 \
     "high",                                                                 \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_host_count (task_last_report (id))"                            \
     " END",                                                                 \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " report_result_host_count (task_last_report (id), opts.min_qod)"       \
     " END",                                                                 \
     "result_hosts",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'False Positive') * 1.0"              \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "fp_per_host",                                                          \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Log') * 1.0"                         \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "log_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Low') * 1.0"                         \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "low_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'Medium') * 1.0"                      \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "medium_per_host",                                                      \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE"     \
     " coalesce (report_severity_count (task_last_report (id),"              \
     "                                 opts.override, opts.min_qod,"         \
     "                                 'High') * 1.0"                        \
     "            / nullif (report_result_host_count (task_last_report (id),"\
     "                                                opts.min_qod), 0),"    \
     "          0)"                                                          \
     " END",                                                                 \
     "high_per_host",                                                        \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT name FROM targets WHERE id = target)",                         \
     "target",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "(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                                                    \
   }                                                                         \

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

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

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

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

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

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

  extra_tables = task_iterator_opts_table (0, MIN_QOD_DEFAULT,
                                           ignore_severity);

  columns = columns_build_select (select_columns);

  init_iterator (iterator,
                 "SELECT %s"
                 " FROM tasks%s"
                 " WHERE " ACL_USER_OWNS ()
                 "%s;",
                 columns,
                 extra_tables,
                 current_credentials.uuid,
                 trash ? " AND hidden = 2" : " AND hidden < 2");

  g_free (extra_tables);
  g_free (columns);
}

/**
 * @brief Initialise a task iterator.
 *
 * @param[in]  iterator    Task iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find target, 2 failed to find filter,
 *         -1 error.
 */
int
init_task_iterator (iterator_t* iterator, 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 (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"
                 " 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));

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

  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 = '6765549a-934e-11e3-b358-406186ea4fc5'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('6765549a-934e-11e3-b358-406186ea4fc5', NULL,"
         "  'User Interface Language',"
         "  'Preferred language to be used in client user interfaces.',"
         "  'Browser Language');");

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

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

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

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

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

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

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

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

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

  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_DELTA_REPORTS_VERSION "'"
               " AND " ACL_IS_GLOBAL () ";")
      == 0)
    sql ("INSERT into settings (uuid, owner, name, comment, value)"
         " VALUES"
         " ('" SETTING_UUID_DELTA_REPORTS_VERSION "', NULL,"
         "  'Delta Reports Version',"
         "  'Version of the generation of the Delta Reports.',"
         "  '2' );");

  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.',"
          "  '24' );");

  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.',"
          "  'wdmy' );");
}

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

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

/**
 * @brief Tries to migrate sensor type scanners to match the relays.
 *
 * @return A string describing the results or NULL on error.
 */
static gchar *
manage_migrate_relay_sensors ()
{
  iterator_t scanners;
  int gmp_successes, gmp_failures, osp_failures;

  gmp_successes = gmp_failures = osp_failures = 0;

  if (get_relay_mapper_path () == NULL)
    {
      g_warning ("%s: No relay mapper set", __func__);
      return NULL;
    }

  init_iterator (&scanners,
                 "SELECT id, uuid, type, host, port FROM scanners"
                 " WHERE type = %d",
                 SCANNER_TYPE_OSP_SENSOR);

  while (next (&scanners))
    {
      scanner_type_t type;
      const char *scanner_id, *host;
      int port;

      scanner_id = iterator_string (&scanners, 1);
      type = iterator_int (&scanners, 2);
      host = iterator_string (&scanners, 3);
      port = iterator_int (&scanners, 4);

      if (relay_supports_scanner_type (host, port, type) == FALSE)
        {
          if (type == SCANNER_TYPE_OSP_SENSOR)
            {
              g_message ("%s: No relay found for OSP Sensor %s (%s:%d).",
                         __func__, scanner_id, host, port);
              osp_failures++;
            }
          else
            g_warning ("%s: Unexpected type for scanner %s: %d",
                       __func__, scanner_id, type);
        }
    }
  cleanup_iterator (&scanners);

  if (gmp_successes == 0 && gmp_failures == 0 && osp_failures == 0)
    return g_strdup ("All GMP or OSP sensors up to date.");
  else
    {
      GString *message = g_string_new ("");
      g_string_append_printf (message,
                              "%d sensors(s) not matching:",
                              gmp_successes + gmp_failures + osp_failures);
      if (gmp_successes)
        g_string_append_printf (message,
                                " %d GMP scanner(s) migrated to OSP.",
                                gmp_successes);
      if (gmp_failures)
        g_string_append_printf (message,
                                " %d GMP scanner(s) not migrated.",
                                gmp_failures);
      if (osp_failures)
        g_string_append_printf (message,
                                " %d OSP sensor(s) not migrated.",
                                osp_failures);

      return g_string_free (message, FALSE);
    }
}

/**
 * @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.
 *
 * @return 0 success, -1 error.
 */
static int
check_db (int check_encryption_key)
{
  /* 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);
  check_db_roles ();
  check_db_nvt_selectors ();
  check_db_nvts ();
  check_db_port_lists ();
  clean_auth_cache ();
  if (check_db_scanners ())
    goto fail;
  if (check_db_report_formats ())
    goto fail;
  if (check_db_report_formats_trash ())
    goto fail;
  check_db_permissions ();
  check_db_settings ();
  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;

  /* 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.
 *
 * @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 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)
    max_attach_length = max_email_attachment_size;
  if (max_email_include_size)
    max_content_length = max_email_include_size;
  if (max_email_message_size)
    max_email_message_length = max_email_message_size;

  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);
      if (ret)
        return ret;

      cleanup_tables ();

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

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

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

  /* Load the NVT cache into memory. */

  if (nvti_cache == NULL)
    update_nvti_cache ();

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

  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;

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

/**
 * @brief Initialize the manage library.
 *
 * Check DB version, do startup database checks, load the NVT cache.
 *
 * Ensure all tasks are in a clean initial state.
 *
 * Beware that calling this function while tasks are running may lead to
 * problems.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  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. */
}

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

/**
 * @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.
 */
gchar *
manage_user_hash (const gchar *username)
{
  gchar *hash, *quoted_username;
  quoted_username = sql_quote (username);
  hash = sql_string ("SELECT password FROM users WHERE name = '%s';",
                     quoted_username);
  g_free (quoted_username);
  return hash;
}

/**
 * @brief Get user uuid.
 *
 * @param[in]  username  User name.
 * @param[in]  method    Authentication method.
 *
 * @return UUID.
 */
static gchar *
user_uuid_method (const gchar *username, auth_method_t method)
{
  gchar *uuid, *quoted_username, *quoted_method;
  quoted_username = sql_quote (username);
  quoted_method = sql_quote (auth_method_name (method));
  uuid = sql_string ("SELECT uuid FROM users"
                     " WHERE name = '%s' AND method = '%s';",
                     quoted_username,
                     quoted_method);
  g_free (quoted_username);
  g_free (quoted_method);
  return uuid;
}

/**
 * @brief Check whether LDAP is enabled.
 *
 * @return 0 no, else yes.
 */
static int
ldap_auth_enabled ()
{
  if (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.
 */
static 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 Check if user exists.
 *
 * @param[in]  name    User name.
 * @param[in]  method  Auth method.
 *
 * @return 1 yes, 0 no.
 */
static int
user_exists_method (const gchar *name, auth_method_t method)
{
  gchar *quoted_name, *quoted_method;
  int ret;

  quoted_name = sql_quote (name);
  quoted_method = sql_quote (auth_method_name (method));
  ret = sql_int ("SELECT count (*) FROM users"
                 " WHERE name = '%s' AND method = '%s';",
                 quoted_name,
                 quoted_method);
  g_free (quoted_name);
  g_free (quoted_method);

  return ret;
}

/**
 * @brief Get user uuid, trying all authentication methods.
 *
 * @param[in]  name    User name.
 *
 * @return UUID.
 */
static gchar *
user_uuid_any_method (const gchar *name)
{
  if (ldap_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT))
    return user_uuid_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT);
  if (radius_auth_enabled ()
      && user_exists_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT))
    return user_uuid_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT);
  if (user_exists_method (name, AUTHENTICATION_METHOD_FILE))
    return user_uuid_method (name, AUTHENTICATION_METHOD_FILE);
  return NULL;
}

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

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

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

  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 (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)
        {
          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 (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)
        {
          auth_cache_insert (username, password, 1);
          sql_commit ();
        }
      else
        {
          sql_rollback ();
        }
      return ret;
    }
  *auth_method = AUTHENTICATION_METHOD_FILE;
  if (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);
          auth_cache_insert (username, password, 2);
          ret = 0;
          break;
        case GMA_SUCCESS:
          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);
}

/**
 * @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.
 */
static 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.
 */
static int
task_last_report_any_status (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " ORDER BY creation_time DESC 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.
 */
static int
task_second_last_report (task_t task, report_t *report)
{
  switch (sql_int64 (report,
                     "SELECT id FROM reports WHERE task = %llu"
                     " AND scan_run_status = %u"
                     " ORDER BY 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 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.
 */
static double
task_severity_double (task_t task, int overrides, int min_qod, int offset)
{
  report_t report;

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

  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 Identify a host, given an identifier.
 *
 * Find a host which has an identifier of the same name and value, and
 * which has no identifiers of the same name and a different value.
 *
 * @param[in]  host_name         Host name.
 * @param[in]  identifier_name   Host identifier name.
 * @param[in]  identifier_value  Value of host identifier.
 * @param[in]  source_type       Source of identification: result.
 * @param[in]  source            Source identifier.
 *
 * @return Host if exists, else 0.
 */
static host_t
host_identify (const char *host_name, const char *identifier_name,
               const char *identifier_value, const char *source_type,
               const char *source)
{
  host_t host = 0;
  gchar *quoted_host_name, *quoted_identifier_name, *quoted_identifier_value;

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

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

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

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

  return host;
}

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

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

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

      host = sql_last_insert_id ();
    }

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

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

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

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

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

  return host;
}

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

  if (strcasecmp (type, "Alarm") == 0 && nvt_id)
    severity = sql_string ("SELECT coalesce(cvss_base, '0.0')"
                           " FROM nvts WHERE uuid = '%s';", nvt_id);
  else if (strcasecmp (type, "Alarm") == 0)
    g_warning ("%s result type requires an NVT", type);
  else if (strcasecmp (type, "Log Message") == 0)
    severity = g_strdup (G_STRINGIFY (SEVERITY_LOG));
  else if (strcasecmp (type, "Error Message") == 0)
    severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR));
  else
    g_warning ("Invalid result nvt type %s", type);
  return severity;
}

/**
 * @brief Make a result.
 *
 * @param[in]  task         The task associated with the result.
 * @param[in]  host         Host 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 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 Whether to ignore the Max Rows Per Page settings.
 */
int ignore_max_rows_per_page = 0;

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

      report_counts_id (report, &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 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);
  quoted_source_type = sql_quote (s_type);
  quoted_source_name = sql_quote (s_name);
  quoted_source_desc = sql_quote (s_desc);
  quoted_name = sql_quote (name);
  quoted_value = sql_quote (value);
  quoted_hash_value = sql_quote(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 values per insert, when uploading report.
 */
#define CREATE_REPORT_INSERT_SIZE 300

/**
 * @brief Number of results per transaction, when uploading report.
 */
#define CREATE_REPORT_CHUNK_SIZE 10

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

/**
 * @brief Create a report from an array of results.
 *
 * @param[in]   results       Array of create_report_result_t pointers.
 * @param[in]   task_id       UUID of container task, or NULL to create new one.
 * @param[in]   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
 *         container, -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, insert_count, first, rc;
  create_report_result_t *result, *end, *start;
  report_t report;
  user_t owner;
  task_t task;
  pid_t pid;
  host_detail_t *detail;
  GString *insert;

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

  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__);
  insert = g_string_new ("");
  index = 0;
  first = 1;
  insert_count = 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_quote (result->host ? result->host : "");
      quoted_hostname = sql_quote (result->hostname ? result->hostname : "");
      quoted_port = sql_quote (result->port ? result->port : "");
      quoted_nvt_oid = sql_quote (result->nvt_oid ? result->nvt_oid : "");
      quoted_description = sql_quote (result->description
                                       ? result->description
                                       : "");
      quoted_scan_nvt_version = sql_quote (result->scan_nvt_version
                                       ? result->scan_nvt_version
                                       : "");
      quoted_severity =  sql_quote (result->severity ? result->severity : "");
      if (result->qod && strcmp (result->qod, "") && strcmp (result->qod, "0"))
        quoted_qod = sql_quote (result->qod);
      else
        quoted_qod = g_strdup (G_STRINGIFY (QOD_DEFAULT));
      quoted_qod_type = sql_quote (result->qod_type ? result->qod_type : "");
      result_nvt_notice (quoted_nvt_oid);

      if (first)
        g_string_append (insert,
                         "INSERT INTO results"
                         " (uuid, owner, date, task, host, hostname, port,"
                         "  nvt, type, description,"
                         "  nvt_version, severity, qod, qod_type,"
                         "  result_nvt, report)"
                         " VALUES");
      else
        g_string_append (insert, ", ");
      first = 0;
      g_string_append_printf (insert,
                              " (make_uuid (), %llu, m_now (), %llu, '%s',"
                              "  '%s', '%s', '%s', '%s', '%s', '%s', '%s',"
                              "  '%s', '%s',"
                              "  (SELECT id FROM result_nvts WHERE nvt = '%s'),"
                              "  %llu)",
                              owner,
                              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,
                              quoted_nvt_oid,
                              report);

      /* Limit the number of results inserted at a time. */
      if (insert_count == CREATE_REPORT_INSERT_SIZE)
        {
          sql ("%s", insert->str);
          g_string_truncate (insert, 0);
          count++;
          insert_count = 0;
          first = 1;

          if (count == CREATE_REPORT_CHUNK_SIZE)
            {
              report_cache_counts (report, 1, 1, NULL);
              sql_commit ();
              gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
              sql_begin_immediate ();
              count = 0;
            }
        }
      insert_count++;

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

  if (first == 0)
    {
      sql ("%s", insert->str);
      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__);
  index = 0;
  first = 1;
  count = 0;
  insert_count = 0;
  g_string_truncate (insert, 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_quote (detail->ip);
        quoted_source_type = sql_quote (detail->source_type ?: "");
        quoted_source_name = sql_quote (detail->source_name ?: "");
        quoted_source_desc = sql_quote (detail->source_desc ?: "");
        quoted_name = sql_quote (detail->name);
        quoted_value = sql_quote (detail->value ?: "");

        if (first)
          g_string_append (insert,
                           "INSERT INTO report_host_details"
                           " (report_host, source_type, source_name,"
                           "  source_description, name, value)"
                           " VALUES");
        else
          g_string_append (insert, ", ");
        first = 0;

        g_string_append_printf (insert,
                                " ((SELECT id FROM report_hosts"
                                "   WHERE report = %llu AND host = '%s'),"
                                "  '%s', '%s', '%s', '%s', '%s')",
                                report, quoted_host, quoted_source_type,
                                quoted_source_name, quoted_source_desc,
                                quoted_name, quoted_value);

        g_free (quoted_host);
        g_free (quoted_source_type);
        g_free (quoted_source_name);
        g_free (quoted_source_desc);
        g_free (quoted_name);
        g_free (quoted_value);

        /* Limit the number of details inserted at a time. */
        if (insert_count == CREATE_REPORT_INSERT_SIZE)
          {
            sql ("%s", insert->str);
            g_string_truncate (insert, 0);
            count++;
            insert_count = 0;
            first = 1;

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

  sql_commit ();

  index = 0;
  sql_begin_immediate ();
  while ((end = (create_report_result_t*) g_ptr_array_index (host_ends,
                                                             index++)))
    if (end->host)
      {
        sql_commit ();
        gvm_usleep (CREATE_REPORT_CHUNK_SLEEP);
        add_assets_from_host_in_report (report, end->host);
        sql_begin_immediate ();
      }

  if (first == 0)
    sql ("%s", insert->str);

  sql_commit ();
  g_string_free (insert, TRUE);

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

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

  set_task_run_status (task, TASK_STATUS_DONE);
  current_scanner_task = 0;
  global_current_report = 0;
  gvm_close_sentry ();
  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 LIKE 'Compliant:%%YES%%';",
                   report);
    }

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

  if (compliance_incomplete)
    {
      *compliance_incomplete
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description LIKE 'Compliant:%%INCOMPLETE%%';",
                   report);
    }
  if (compliance_undefined)
    {
      *compliance_undefined
        = sql_int ("SELECT count(*) FROM results"
                   " WHERE report = %llu"
                   " AND description NOT LIKE 'Compliant:%%';",
                   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", "hosts", "result_hosts", "fp_per_host", "log_per_host",   \
   "low_per_host", "medium_per_host", "high_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                                                    \
   },                                                                        \
   {                                                                         \
     "(SELECT name FROM users WHERE users.id = reports.owner)",              \
     "_owner",                                                               \
     KEYWORD_TYPE_STRING                                                     \
   },                                                                        \
   {                                                                         \
     "report_host_count (id)",                                               \
     "hosts",                                                                \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "report_result_host_count (id, opts.min_qod)",                          \
     "result_hosts",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'False Positive') * 1.0"              \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "fp_per_host",                                                          \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Log') * 1.0"                         \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "log_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Low') * 1.0"                         \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "low_per_host",                                                         \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'Medium') * 1.0"                      \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "medium_per_host",                                                      \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "coalesce (report_severity_count (id, opts.override, opts.min_qod,"     \
     "                                 'High') * 1.0"                        \
     "            / nullif (report_result_host_count (id, opts.min_qod), 0),"\
     "          0)",                                                         \
     "high_per_host",                                                        \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(CASE WHEN (start_time IS NULL or end_time IS NULL)"                   \
     " THEN NULL ELSE end_time - start_time END)",                           \
     "duration",                                                             \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "(CASE WHEN (start_time IS NULL or end_time IS NULL"                    \
     "            or report_result_host_count (id, opts.min_qod) = 0)"       \
     " THEN NULL"                                                            \
     " ELSE (end_time - start_time)"                                         \
     "        / report_result_host_count (id, opts.min_qod) END)",           \
     "duration_per_host",                                                    \
     KEYWORD_TYPE_INTEGER                                                    \
   },                                                                        \
   {                                                                         \
     "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);
}

#if COMPLIANCE_REPORTS == 1
/**
 * @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);;
}
#endif

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

  #if COMPLIANCE_REPORTS == 1
    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);
  #endif

  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 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, '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 == 5)
    {
      /* 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.
 */
static 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 ("hmlgdf");
  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 ? levels : "hmlgdf",
                                     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->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 original type from a result iterator.
 *
 * This is the column 'type'.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The original type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
static
DEF_ACCESS (result_iterator_original_type, GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the type from a result iterator.
 *
 * This is the overridden type.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
static const char*
result_iterator_type (iterator_t *iterator)
{
  if (iterator->done) return NULL;
  /* new_type */
  return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the descr from a result iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The descr of the result.  Caller must only use before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (result_iterator_descr, GET_ITERATOR_COLUMN_COUNT + 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)
    {
      if (report_host)
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report),"
                       " (SELECT uuid FROM hosts"
                       "  WHERE id = (SELECT host FROM host_identifiers"
                       "              WHERE source_type = 'Report Host'"
                       "              AND name = 'ip'"
                       "              AND source_id = (SELECT uuid"
                       "                               FROM reports"
                       "                               WHERE id = report)"
                       "              AND value = report_hosts.host"
                       "              LIMIT 1))"
                       " FROM report_hosts WHERE id = %llu"
                       " AND report = %llu"
                       "%s%s%s"
                       " ORDER BY order_inet (host);",
                       report_host,
                       report,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
      else
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report),"
                       " (SELECT uuid FROM hosts"
                       "  WHERE id = (SELECT host FROM host_identifiers"
                       "              WHERE source_type = 'Report Host'"
                       "              AND name = 'ip'"
                       "              AND source_id = (SELECT uuid"
                       "                               FROM reports"
                       "                               WHERE id = report)"
                       "              AND value = report_hosts.host"
                       "              LIMIT 1))"
                       " FROM report_hosts WHERE report = %llu"
                       "%s%s%s"
                       " ORDER BY order_inet (host);",
                       report,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
    }
  else
    {
      if (report_host)
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report),"
                       " ''"
                       " FROM report_hosts WHERE id = %llu"
                       "%s%s%s"
                       " ORDER BY order_inet (host);",
                       report_host,
                       host ? " AND host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
      else
        init_iterator (iterator,
                       "SELECT id, host, iso_time (start_time),"
                       " iso_time (end_time), current_port, max_port, report,"
                       " (SELECT uuid FROM reports WHERE id = report),"
                       " ''"
                       " FROM report_hosts"
                       "%s%s%s"
                       " ORDER BY order_inet (host);",
                       host ? " WHERE host = '" : "",
                       host ? host : "",
                       host ? "'" : "");
    }
}


/**
 * @brief Get the report host from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Report host.
 */
static report_host_t
host_iterator_report_host (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return (report_host_t) iterator_int64 (iterator, 0);
}

/**
 * @brief Get the host from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The host of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_host, 1);

/**
 * @brief Get the start time from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The start time of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_start_time, 2);

/**
 * @brief Get the end time from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The end time of the host.  Caller must use only before calling
 *         cleanup_iterator.
 */
DEF_ACCESS (host_iterator_end_time, 3);

/**
 * @brief Get the current port from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Current port.
 */
int
host_iterator_current_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 4);
  return ret;
}

/**
 * @brief Get the max port from a host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Current port.
 */
int
host_iterator_max_port (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, 5);
  return ret;
}

/**
 * @brief Get the 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.type = 'Error Message'"
                   "  AND results.report = %llu",
                   report);
}

/**
 * @brief Get the host from a report error messages iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The host of the report error message.  Caller must use only before
 *         calling cleanup_iterator.
 */
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.
 */
static void
init_report_host_details_iterator (iterator_t* iterator,
                                   report_host_t report_host)
{
  /* The 'detected_at' and 'detected_by' entries are filtered out of the final
   * reports as they are only used internally for product detection. */
  init_iterator (iterator,
                 "SELECT id, name, value, source_type, source_name,"
                 "       source_description, NULL"
                 " FROM report_host_details WHERE report_host = %llu"
                 " AND NOT name IN ('detected_at', 'detected_by')"
                 " AND NOT name LIKE 'detected_by@%%'"
                 " UNION SELECT 0, 'Closed CVE', cve, 'openvasmd', 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.
 */
static
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.
 */
static
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.
 */
static
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 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 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);
      ignore_max_rows_per_page = 1;
      init_result_get_iterator_severity (&results, get_all, report, host, NULL);
      ignore_max_rows_per_page = 0;
      while (next (&results))
        {
          double severity;

          if (results.done)
            severity = 0.0;
          else
            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;

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

          if (results.done)
            severity = 0.0;
          else
            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]  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* 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, 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]  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_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* holes, int* infos,
                       int* logs, int* warnings, int* false_positives,
                       double* severity,
                       const get_data_t* get, const char* host,
                       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 = (holes || warnings || infos || logs || false_positives
                          || severity);
  filtered_requested = (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);
  severity_data_level_counts (&filtered_severity_data,
                              NULL, filtered_false_positives,
                              filtered_logs, filtered_infos,
                              filtered_warnings, filtered_holes);

  if (severity)
    *severity = severity_data.max;
  if (filtered_severity && filtered_requested)
    *filtered_severity = filtered_severity_data.max;

  if (filter_cacheable && !cache_exists)
    {
      if (unfiltered_requested)
        cache_report_counts (report, override, 0, &severity_data);
      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;
}

#if COMPLIANCE_REPORTS == 1
/**
 * @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;

  ignore_max_rows_per_page = 1;
  init_result_get_iterator (&results, &get_filtered, report, NULL,
                            NULL);
  ignore_max_rows_per_page = 0;
  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;
}
#endif


/**
 * @brief Get only the filtered message counts for a report.
 *
 * @param[in]   report    Report.
 * @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* 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,
                               get, host, 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, &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 Comparison returns.
 */
typedef enum
{
  COMPARE_RESULTS_CHANGED,
  COMPARE_RESULTS_ERROR,
  COMPARE_RESULTS_GONE,
  COMPARE_RESULTS_NEW,
  COMPARE_RESULTS_SAME
} compare_results_t;

/**
 * @brief Return the sort order of two results.
 *
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 *
 * @return < 0 if first comes before second, 0 if equal, > 0 if first comes
 *         after second.
 */
static compare_results_t
result_cmp (iterator_t *results, iterator_t *delta_results, int sort_order,
            const char* sort_field)
{
  const char *host, *delta_host, *port, *delta_port;
  const char *nvt, *delta_nvt, *name, *delta_name, *descr, *delta_descr;
  int ret;
  double severity, delta_severity;

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

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

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

  port = result_iterator_port (results);
  delta_port = result_iterator_port (delta_results);

  severity = result_iterator_severity_double (results);
  delta_severity = result_iterator_severity_double (delta_results);

  nvt = result_iterator_nvt_oid (results);
  delta_nvt = result_iterator_nvt_oid (delta_results);

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

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

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

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

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

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

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

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

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

  return 0;
}

/**
 * @brief Test if two strings are equal, ignoring whitespace.
 *
 * @param[in]  one  First string.
 * @param[in]  two  Second string.
 *
 * @return 1 if equal, else 0.
 */
static int
streq_ignore_ws (const gchar *one, const gchar *two)
{
  if (one == NULL)
    return two == NULL;
  if (two == NULL)
    return 0;

  while (1)
    {
      /* Skip space in both. */
      while (isspace (*one))
        one++;
      while (isspace (*two))
        two++;

      /* Check for end. */
      if (*one == '\0')
        break;
      if (*two == '\0')
        return 0;

      /* Compare. */
      if (*one != *two)
        return 0;

      /* Next. */
      one++;
      two++;
    }
  if (*two)
    return 0;
  return 1;
}

/**
 * @brief Compare two results.
 *
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 *
 * @return Result of comparison.
 */
static compare_results_t
compare_results (iterator_t *results, iterator_t *delta_results, int sort_order,
                 const char* sort_field)
{
  int ret;
  const char *descr, *delta_descr;

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

  ret = result_cmp (results, delta_results, sort_order, sort_field);
  if (ret > 0)
    /* The delta result sorts first, so it is new. */
    return COMPARE_RESULTS_NEW;
  if (ret < 0)
    /* The 'results' result sorts first, so it has gone. */
    return COMPARE_RESULTS_GONE;

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

  g_debug ("   delta: %s: descr: %s VS %s (%i)",
          __func__,
          descr ? descr : "NULL",
          delta_descr ? delta_descr : "NULL",
          (descr && delta_descr) ? strcmp (descr, delta_descr) : 0);

  /* This comparison ignores whitespace to match the diff output created by
   * strdiff in gmp.c.  The down side of this is that the comparison may be
   * affected by the locale.
   *
   * An alternate would be to use the strdiff result as the comparison, but
   * strdiff is only called for the results on the page (and not for the
   * rest of the results, which must also be compared for the counts).
   * Using strdiff for all results could also be slow, because it's running
   * the diff command. */
  if (descr && delta_descr && (streq_ignore_ws (descr, delta_descr) == 0))
    return COMPARE_RESULTS_CHANGED;

  return COMPARE_RESULTS_SAME;
}

/**
 * @brief Compare two results, optionally writing associated XML to a buffer.
 *
 * This is called with buffer NULL to compare results after the page limit
 * (filter keyword "max") is reached.  These results need to be compared to be
 * included in the counts.
 *
 * @param[in]  buffer         Buffer.  NULL to skip writing to buffer.
 * @param[in]  results        Iterator containing first result.
 * @param[in]  delta_results  Iterator containing second result.
 * @param[in]  task           Task associated with report.
 * @param[in]  notes              Whether to include notes.
 * @param[in]  notes_details      If notes, Whether to include details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  If overrides, Whether to include details.
 * @param[in]  sort_order     Whether to sort ascending or descending.
 * @param[in]  sort_field     Field to sort on, or NULL for "type".
 * @param[in]  changed        Whether to include changed results.
 * @param[in]  gone           Whether to include gone results.
 * @param[in]  new            Whether to include new results.
 * @param[in]  same           Whether to include same results.
 * @param[in]  max_results    Value to decrement if result is buffered.
 * @param[in]  first_result   Skip result and decrement if positive.
 * @param[in]  used           0 if used, 1 if skipped.
 * @param[in]  would_use      0 if would use (first_result aside), 1 if skipped.
 *
 * @return Result of comparison.
 */
static compare_results_t
compare_and_buffer_results (GString *buffer, iterator_t *results,
                            iterator_t *delta_results, task_t task, int notes,
                            int notes_details, int overrides,
                            int overrides_details, int sort_order,
                            const char* sort_field, int changed, int gone,
                            int new, int same, int *max_results,
                            int *first_result, int *used, int *would_use)
{
  compare_results_t state;
  state = compare_results (results, delta_results, sort_order, sort_field);
  *used = 0;
  *would_use = 0;
  switch (state)
    {
      case COMPARE_RESULTS_CHANGED:
        if (changed)
          {
            *would_use = 1;
            if (*first_result)
              {
                g_debug ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "changed",
                                  delta_results,
                                  /* This is the only case that uses 1. */
                                  1,  /* Whether result is "changed". */
                                  -1,
                                  0,  /* Lean. */
                                  0); /* Delta fields. */
          }
        break;

      case COMPARE_RESULTS_GONE:
        if (gone)
          {
            *would_use = 1;
            if (*first_result)
              {
                g_debug ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "gone",
                                  delta_results,
                                  0,
                                  -1,
                                  0,  /* Lean. */
                                  0); /* Delta fields. */
          }
        break;

      case COMPARE_RESULTS_NEW:
        if (new)
          {
            *would_use = 1;
            if (*first_result)
              {
                g_debug ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  delta_results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "new",
                                  delta_results,
                                  0,
                                  -1,
                                  0,  /* Lean. */
                                  0); /* Delta fields. */
          }
        break;

      case COMPARE_RESULTS_SAME:
        if (same)
          {
            *would_use = 1;
            if (*first_result)
              {
                g_debug ("   delta: skip");
                (*first_result)--;
                break;
              }
            *used = 1;
            (*max_results)--;
            if (buffer)
              buffer_results_xml (buffer,
                                  results,
                                  task,
                                  notes,
                                  notes_details,
                                  overrides,
                                  overrides_details,
                                  0,
                                  0,
                                  0,
                                  "same",
                                  delta_results,
                                  0,
                                  -1,
                                  0,  /* Lean. */
                                  0); /* Delta fields. */
          }
        break;

      default:
        return COMPARE_RESULTS_ERROR;
    }

  return state;
}

/**
 * @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 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__);
              g_free (tz);
              g_free (zone);
              free (old_tz_override);
              return -1;
            }
        }
      else
        unsetenv ("TZ");

      quoted_old_tz_override = sql_insert (old_tz_override);
      sql ("SET SESSION \"gvmd.tz_override\" = %s;",
           quoted_old_tz_override);
      g_free (quoted_old_tz_override);

      free (old_tz_override);
      g_free (tz);
    }
  g_free (zone);
  return 0;
}

/**
 * @brief Print the XML for a report to a file.
 *
 * @param[in]  host_summary_buffer  Summary.
 * @param[in]  host                 Host.
 * @param[in]  start_iso            Start time, in ISO format.
 * @param[in]  end_iso              End time, in ISO format.
 */
static void
host_summary_append (GString *host_summary_buffer, const char *host,
                     const char *start_iso, const char *end_iso)
{
  if (host_summary_buffer)
    {
      char start[200], end[200];

      if (start_iso)
        {
          struct tm start_tm;

          memset (&start_tm, 0, sizeof (struct tm));
          #if !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_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_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 COMPLIANCE_REPORTS == 1
    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
  #endif
    {
      int holes_count, warnings_count, infos_count;
      int logs_count, false_positives_count;

      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>"
            "<hole><page>%d</page></hole>"
            "<warning><page>%d</page></warning>"
            "<info><page>%d</page></info>"
            "<log><page>%d</page></log>"
            "<false_positive><page>%d</page></false_positive>"
            "</result_count>",
            host_iterator_start_time (hosts),
            host_iterator_end_time (hosts)
              ? host_iterator_end_time (hosts)
              : "",
            ports_count,
            (holes_count + warnings_count + infos_count
              + logs_count + false_positives_count),
            holes_count,
            warnings_count,
            infos_count,
            logs_count,
            false_positives_count);
    }

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

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

  return 0;
}

/**
 * @brief Init delta iterators for print_report_xml.
 *
 * @param[in]  report         The report.
 * @param[in]  results        Report result iterator.
 * @param[in]  delta          Delta report.
 * @param[in]  delta_results  Delta report result iterator.
 * @param[in]  get            GET command data.
 * @param[in]  term           Filter term.
 * @param[out] sort_field     Sort field.
 *
 * @return 0 on success, -1 error.
 */
static int
init_delta_iterators (report_t report, iterator_t *results, report_t delta,
                      iterator_t *delta_results, const get_data_t *get,
                      const char *term, const char *sort_field)
{
  int res;
  gchar *order;
  get_data_t delta_get;

  /*
   * Order must be the same as in result_cmp, except for description
   *  which isn't checked there.
   */
  if ((strcmp (sort_field, "name") == 0)
      || (strcmp (sort_field, "vulnerability") == 0))
    order = g_strdup (", host, port, severity, nvt, description");
  else if (strcmp (sort_field, "host") == 0)
    order = g_strdup (", port, severity, nvt, description");
  else if ((strcmp (sort_field, "port") == 0)
           || (strcmp (sort_field, "location") == 0))
    order = g_strdup (", host, severity, nvt, description");
  else if (strcmp (sort_field, "severity") == 0)
    order = g_strdup (", host, port, nvt, description");
  else if (strcmp (sort_field, "nvt") == 0)
    order = g_strdup (", host, port, severity, description");
  else
    order = g_strdup (", host, port, severity, nvt, description");

  delta_get = *get;
  delta_get.filt_id = NULL;
  delta_get.filter = g_strdup_printf ("rows=-1 first=1 sort=%s %s",
                                      sort_field, term);
  ignore_max_rows_per_page = 1;

#if 0
  /* For debugging. */

  iterator_t results2;

  res = init_result_get_iterator (results, &delta_get, report, NULL, order);
  if (res)
    return -1;

  res = init_result_get_iterator (&results2, &delta_get, delta, NULL, order);
  if (res)
    return -1;

  g_debug ("   delta: %s: REPORT 1:", __func__);
  while (next (results))
    g_debug ("   delta: %s: %s   %s   %s   %s   %.30s",
            __func__,
            result_iterator_nvt_name (results),
            result_iterator_host (results),
            result_iterator_type (results),
            result_iterator_port (results),
            result_iterator_descr (results));
  cleanup_iterator (results);
  g_debug ("   delta: %s: REPORT 1 END", __func__);

  g_debug ("   delta: %s: REPORT 2:", __func__);
  while (next (&results2))
    g_debug ("   delta: %s: %s   %s   %s   %s   %.30s",
            __func__,
            result_iterator_nvt_name (&results2),
            result_iterator_host (&results2),
            result_iterator_type (&results2),
            result_iterator_port (&results2),
            result_iterator_descr (&results2));
  cleanup_iterator (&results2);
  g_debug ("   delta: %s: REPORT 2 END", __func__);
#endif

  res = init_result_get_iterator (results, &delta_get, report, NULL, order);
  if (res)
    {
      ignore_max_rows_per_page = 0;
      g_free (order);
      return -1;
    }

  res = init_result_get_iterator (delta_results, &delta_get, delta, NULL, order);
  if (res)
    {
      ignore_max_rows_per_page = 0;
      g_free (order);
      return -1;
    }

  g_free (delta_get.filter);
  ignore_max_rows_per_page = 0;
  g_free (order);

  return 0;
}

/**
 * @brief Init v2 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_v2_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 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 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_results  Delta report result iterator.
 * @param[in]  delta_states   String describing delta states to include in count
 *                            (for example, "sngc" Same, New, Gone and Changed).
 *                            All levels if NULL.
 * @param[in]  first_result   First result.
 * @param[in]  max_results    Max results.
 * @param[in]  task           The task.
 * @param[in]  notes          Whether to include notes.
 * @param[in]  notes_details  Whether to include note details.
 * @param[in]  overrides          Whether to include overrides.
 * @param[in]  overrides_details  Whether to include override details.
 * @param[in]  sort_order         Sort order.
 * @param[in]  sort_field         Sort field.
 * @param[in]  result_hosts_only  Whether to only include hosts with results.
 * @param[in]  orig_filtered_result_count  Result count.
 * @param[in]  filtered_result_count       Result count.
 * @param[in]  orig_f_holes   Result count.
 * @param[in]  f_holes        Result count.
 * @param[in]  orig_f_infos   Result count.
 * @param[in]  f_infos        Result count.
 * @param[in]  orig_f_logs    Result count.
 * @param[in]  f_logs         Result count.
 * @param[in]  orig_f_warnings  Result count.
 * @param[in]  f_warnings       Result count.
 * @param[in]  orig_f_false_positives  Result count.
 * @param[in]  f_false_positives       Result count.
 * @param[in]  result_hosts   Result hosts.
 *
 * @return 0 on success, -1 error.
 */
static int
print_report_delta_xml (FILE *out, iterator_t *results,
                        iterator_t *delta_results, const char *delta_states,
                        int first_result, int max_results, task_t task,
                        int notes, int notes_details, int overrides,
                        int overrides_details, int sort_order,
                        const char *sort_field, int result_hosts_only,
                        int *orig_filtered_result_count,
                        int *filtered_result_count,
                        int *orig_f_holes, int *f_holes,
                        int *orig_f_infos, int *f_infos,
                        int *orig_f_logs, int *f_logs,
                        int *orig_f_warnings, int *f_warnings,
                        int *orig_f_false_positives, int *f_false_positives,
                        array_t *result_hosts)
{
  gboolean done, delta_done;
  int changed, gone, new, same;
  /* A tree of host, tree pairs, where the inner tree is a sorted tree
   * of port, threat pairs. */
  GTree *ports;
  gchar *msg;

  *orig_f_holes = *f_holes;
  *orig_f_infos = *f_infos;
  *orig_f_logs = *f_logs;
  *orig_f_warnings = *f_warnings;
  *orig_f_false_positives = *f_false_positives;
  *orig_filtered_result_count = *filtered_result_count;

  changed = (strchr (delta_states, 'c') != NULL);
  gone = (strchr (delta_states, 'g') != NULL);
  new = (strchr (delta_states, 'n') != NULL);
  same = (strchr (delta_states, 's') != NULL);

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

  /* Compare the results in the two iterators, which are sorted. */

  g_debug ("   delta: %s: start", __func__);
  g_debug ("   delta: %s: sort_field: %s", __func__, sort_field);
  g_debug ("   delta: %s: sort_order: %i", __func__, sort_order);
  g_debug ("   delta: %s: max_results: %i", __func__, max_results);
  done = !next (results);
  delta_done = !next (delta_results);
  while (1)
    {
      GString *buffer;
      compare_results_t state;
      int used, would_use;

      if (max_results == 0)
        break;

      if (done)
        {
          if (delta_done)
            break;
          if (new)
            /* Extra results in 'delta_results'. */
            do
              {
                const char *level;

                g_debug ("   delta: %s: extra from report 2: %s",
                        __func__,
                        result_iterator_nvt_oid (results));

                if (first_result)
                  {
                    g_debug ("   delta: skip");
                    first_result--;
                    continue;
                  }

                /* Increase the result count. */
                level = result_iterator_level (delta_results);
                (*orig_filtered_result_count)++;
                (*filtered_result_count)++;
                if (strcmp (level, "High") == 0)
                  {
                    (*orig_f_holes)++;
                    (*f_holes)++;
                  }
                else if (strcmp (level, "Medium") == 0)
                  {
                    (*orig_f_warnings)++;
                    (*f_warnings)++;
                  }
                else if (strcmp (level, "Low") == 0)
                  {
                    (*orig_f_infos)++;
                    (*f_infos)++;
                  }
                else if (strcmp (level, "Log") == 0)
                  {
                    (*orig_f_logs)++;
                    (*f_logs)++;
                  }
                else if (strcmp (level, "False Positive") == 0)
                  {
                    (*orig_f_false_positives)++;
                    (*f_false_positives)++;
                  }

                g_debug ("   delta: %s: extra from report 2: %s",
                        __func__,
                        result_iterator_nvt_oid (delta_results));
                buffer = g_string_new ("");
                buffer_results_xml (buffer,
                                    delta_results,
                                    task,
                                    notes,
                                    notes_details,
                                    overrides,
                                    overrides_details,
                                    0,
                                    0,
                                    0,
                                    "new",
                                    NULL,
                                    0,
                                    -1,
                                    0,  /* Lean. */
                                    0); /* Delta fields. */
                if (fprintf (out, "%s", buffer->str) < 0)
                  return -1;
                g_string_free (buffer, TRUE);
                if (result_hosts_only)
                  array_add_new_string (result_hosts,
                                        result_iterator_host (delta_results));
                add_port (ports, delta_results);
                max_results--;
                if (max_results == 0)
                  break;
              }
            while (next (delta_results));
          delta_done = TRUE;
          break;
        }

      if (delta_done)
        {
          /* Extra results in 'results'. */
          if (gone)
            do
              {
                g_debug ("   delta: %s: extra from report 1: %s",
                        __func__,
                        result_iterator_nvt_oid (results));
                if (first_result)
                  {
                    g_debug ("   delta: skip");
                    first_result--;
                    continue;
                  }
                buffer = g_string_new ("");
                buffer_results_xml (buffer,
                                    results,
                                    task,
                                    notes,
                                    notes_details,
                                    overrides,
                                    overrides_details,
                                    0,
                                    0,
                                    0,
                                    "gone",
                                    NULL,
                                    0,
                                    -1,
                                    0,  /* Lean. */
                                    0); /* Delta fields. */
                if (fprintf (out, "%s", buffer->str) < 0)
                  return -1;
                g_string_free (buffer, TRUE);
                if (result_hosts_only)
                  array_add_new_string (result_hosts,
                                        result_iterator_host (results));
                add_port (ports, results);
                max_results--;
                if (max_results == 0)
                  break;
              }
            while (next (results));
          else
            do
              {
                const char *level;

                /* Decrease the result count. */
                level = result_iterator_level (results);
                (*orig_filtered_result_count)--;
                (*filtered_result_count)--;
                if (strcmp (level, "High") == 0)
                  {
                    (*orig_f_holes)--;
                    (*f_holes)--;
                  }
                else if (strcmp (level, "Medium") == 0)
                  {
                    (*orig_f_warnings)--;
                    (*f_warnings)--;
                  }
                else if (strcmp (level, "Low") == 0)
                  {
                    (*orig_f_infos)--;
                    (*f_infos)--;
                  }
                else if (strcmp (level, "Log") == 0)
                  {
                    (*orig_f_logs)--;
                    (*f_logs)--;
                  }
                else if (strcmp (level, "False Positive") == 0)
                  {
                    (*orig_f_false_positives)--;
                    (*f_false_positives)--;
                  }
              }
            while (next (results));
          done = TRUE;
          break;
        }

      /* Compare the two results. */

      buffer = g_string_new ("");
      state = compare_and_buffer_results (buffer,
                                          results,
                                          delta_results,
                                          task,
                                          notes,
                                          notes_details,
                                          overrides,
                                          overrides_details,
                                          sort_order,
                                          sort_field,
                                          changed,
                                          gone,
                                          new,
                                          same,
                                          &max_results,
                                          &first_result,
                                          &used,
                                          &would_use);
      if (state == COMPARE_RESULTS_ERROR)
        {
          g_warning ("%s: compare_and_buffer_results failed",
                     __func__);
          return -1;
        }
      if (fprintf (out, "%s", buffer->str) < 0)
        return -1;
      g_string_free (buffer, TRUE);

      if ((used == 0)
          && ((state == COMPARE_RESULTS_GONE)
              || (state == COMPARE_RESULTS_SAME)
              || (state == COMPARE_RESULTS_CHANGED)))
        {
          const char *level;

          /* Decrease the result count. */
          level = result_iterator_level (results);
          (*filtered_result_count)--;
          if (strcmp (level, "High") == 0)
            {
              (*f_holes)--;
            }
          else if (strcmp (level, "Medium") == 0)
            {
              (*f_warnings)--;
            }
          else if (strcmp (level, "Low") == 0)
            {
              (*f_infos)--;
            }
          else if (strcmp (level, "Log") == 0)
            {
              (*f_logs)--;
            }
          else if (strcmp (level, "False Positive") == 0)
            {
              (*f_false_positives)--;
            }
        }

      if ((would_use == 0)
          && ((state == COMPARE_RESULTS_GONE)
              || (state == COMPARE_RESULTS_SAME)
              || (state == COMPARE_RESULTS_CHANGED)))
        {
          const char *level;

          /* Decrease the result count. */
          level = result_iterator_level (results);
          (*orig_filtered_result_count)--;
          if (strcmp (level, "High") == 0)
            {
              (*orig_f_holes)--;
            }
          else if (strcmp (level, "Medium") == 0)
            {
              (*orig_f_warnings)--;
            }
          else if (strcmp (level, "Low") == 0)
            {
              (*orig_f_infos)--;
            }
          else if (strcmp (level, "Log") == 0)
            {
              (*orig_f_logs)--;
            }
          else if (strcmp (level, "False Positive") == 0)
            {
              (*orig_f_false_positives)--;
            }
        }

      /* Move on to the next. */

      if (state == COMPARE_RESULTS_GONE)
        {
          /* "Used" just the 'results' result. */
          if (used)
            {
              if (result_hosts_only)
                array_add_new_string (result_hosts,
                                      result_iterator_host (results));
              add_port (ports, results);
            }
          done = !next (results);
        }
      else if ((state == COMPARE_RESULTS_SAME)
               || (state == COMPARE_RESULTS_CHANGED))
        {
          /* "Used" both results. */
          if (used)
            {
              if (result_hosts_only)
                array_add_new_string (result_hosts,
                                      result_iterator_host (results));
              add_port (ports, results);
            }
          done = !next (results);
          delta_done = !next (delta_results);
        }
      else if (state == COMPARE_RESULTS_NEW)
        {
          if (would_use)
            {
              const char *level;

              /* Would have "used" just the 'delta_results' result, on
               * an earlier page. */

              /* Increase the result count. */
              level = result_iterator_level (delta_results);
              (*orig_filtered_result_count)++;
              if (strcmp (level, "High") == 0)
                {
                  (*orig_f_holes)++;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  (*orig_f_warnings)++;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  (*orig_f_infos)++;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  (*orig_f_logs)++;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  (*orig_f_false_positives)++;
                }
            }

          if (used)
            {
              const char *level;

              /* "Used" just the 'delta_results' result. */

              /* Increase the result count. */
              level = result_iterator_level (delta_results);
              (*filtered_result_count)++;
              if (strcmp (level, "High") == 0)
                {
                  (*f_holes)++;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  (*f_warnings)++;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  (*f_infos)++;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  (*f_logs)++;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  (*f_false_positives)++;
                }

              if (result_hosts_only)
                array_add_new_string (result_hosts,
                                      result_iterator_host
                                       (delta_results));

              add_port (ports, delta_results);
            }
          delta_done = !next (delta_results);
        }
      else
        assert (0);
    }

  /* Compare remaining results, for the filtered report counts. */

  g_debug ("   delta: %s: counting rest", __func__);
  while (1)
    {
      compare_results_t state;
      int used, would_use;

      if (done)
        {
          if (delta_done)
            break;
          if (new)
            /* Extra results in 'delta_results'. */
            do
              {
                const char *level;

                g_debug ("   delta: %s: extra from report 2: %s",
                        __func__,
                        result_iterator_nvt_oid (delta_results));

                /* Increase the result count. */
                level = result_iterator_level (delta_results);
                (*orig_filtered_result_count)++;
                if (strcmp (level, "High") == 0)
                  {
                    (*orig_f_holes)++;
                  }
                else if (strcmp (level, "Medium") == 0)
                  {
                    (*orig_f_warnings)++;
                  }
                else if (strcmp (level, "Low") == 0)
                  {
                    (*orig_f_infos)++;
                  }
                else if (strcmp (level, "Log") == 0)
                  {
                    (*orig_f_logs)++;
                  }
                else if (strcmp (level, "False Positive") == 0)
                  {
                    (*orig_f_false_positives)++;
                  }
              }
            while (next (delta_results));
          break;
        }

      if (delta_done)
        {
          /* Extra results in 'results'. */
          if (gone)
            do
              {
                g_debug ("   delta: %s: extra from report 1: %s",
                        __func__,
                        result_iterator_nvt_oid (results));

                /* It's in the count already. */
              }
            while (next (results));
          else
            do
              {
                const char *level;

                /* Decrease the result count. */
                level = result_iterator_level (results);
                (*orig_filtered_result_count)--;
                if (strcmp (level, "High") == 0)
                  {
                    (*orig_f_holes)--;
                  }
                else if (strcmp (level, "Medium") == 0)
                  {
                    (*orig_f_warnings)--;
                  }
                else if (strcmp (level, "Low") == 0)
                  {
                    (*orig_f_infos)--;
                  }
                else if (strcmp (level, "Log") == 0)
                  {
                    (*orig_f_logs)--;
                  }
                else if (strcmp (level, "False Positive") == 0)
                  {
                    (*orig_f_false_positives)--;
                  }
              }
            while (next (results));
          break;
        }

      /* Compare the two results. */

      state = compare_and_buffer_results (NULL,
                                          results,
                                          delta_results,
                                          task,
                                          notes,
                                          notes_details,
                                          overrides,
                                          overrides_details,
                                          sort_order,
                                          sort_field,
                                          changed,
                                          gone,
                                          new,
                                          same,
                                          &max_results,
                                          &first_result,
                                          &used,
                                          &would_use);
      if (state == COMPARE_RESULTS_ERROR)
        {
          g_warning ("%s: compare_and_buffer_results failed",
                     __func__);
          return -1;
        }

      if (state == COMPARE_RESULTS_NEW)
        {
          if (used)
            {
              const char *level;

              /* "Used" just the 'delta_results' result. */

              /* Increase the result count. */
              level = result_iterator_level (delta_results);
              (*orig_filtered_result_count)++;
              if (strcmp (level, "High") == 0)
                {
                  (*orig_f_holes)++;
                }
              else if (strcmp (level, "Medium") == 0)
                {
                  (*orig_f_warnings)++;
                }
              else if (strcmp (level, "Low") == 0)
                {
                  (*orig_f_infos)++;
                }
              else if (strcmp (level, "Log") == 0)
                {
                  (*orig_f_logs)++;
                }
              else if (strcmp (level, "False Positive") == 0)
                {
                  (*orig_f_false_positives)++;
                }
            }
        }
      else if (used)
        {
          /* It's in the count already. */
        }
      else
        {
          const char *level;

          /* Decrease the result count. */
          level = result_iterator_level (results);
          (*orig_filtered_result_count)--;
          if (strcmp (level, "High") == 0)
            {
              (*orig_f_holes)--;
            }
          else if (strcmp (level, "Medium") == 0)
            {
              (*orig_f_warnings)--;
            }
          else if (strcmp (level, "Low") == 0)
            {
              (*orig_f_infos)--;
            }
          else if (strcmp (level, "Log") == 0)
            {
              (*orig_f_logs)--;
            }
          else if (strcmp (level, "False Positive") == 0)
            {
              (*orig_f_false_positives)--;
            }
        }

      /* Move on to the next. */

      if (state == COMPARE_RESULTS_GONE)
        {
          /* "Used" just the 'results' result. */
          done = !next (results);
        }
      else if ((state == COMPARE_RESULTS_SAME)
               || (state == COMPARE_RESULTS_CHANGED))
        {
          /* "Used" both results. */
          done = !next (results);
          delta_done = !next (delta_results);
        }
      else if (state == COMPARE_RESULTS_NEW)
        {
          /* "Used" just the 'delta_results' result. */
          delta_done = !next (delta_results);
        }
      else
        assert (0);
    }
  msg = g_markup_printf_escaped ("</results>");
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);

  /* Write ports to file. */

  msg = g_markup_printf_escaped ("<ports"
                                 " start=\"%i\""
                                 " max=\"%i\">",
                                 /* Add 1 for 1 indexing. */
                                 first_result + 1,
                                 max_results);
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);
  if (sort_field == NULL || strcmp (sort_field, "port"))
    {
      if (sort_order)
        g_tree_foreach (ports, print_host_ports_by_severity_asc, out);
      else
        g_tree_foreach (ports, print_host_ports_by_severity_desc, out);
    }
  else if (sort_order)
    g_tree_foreach (ports, print_host_ports, out);
  else
    g_tree_foreach (ports, print_host_ports_desc, out);
  g_tree_destroy (ports);
  msg = g_markup_printf_escaped ("</ports>");
  if (fprintf (out, "%s", msg) < 0)
    {
      g_free (msg);
      fclose (out);
      return -1;
    }
  g_free (msg);

  return 0;
}

/**
 * @brief Print v2 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_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_v2_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_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_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 COMPLIANCE_REPORTS == 1
      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
    #endif
      {
        const char *level;
        /* Increase the result count. */
        level = result_iterator_level (results);
        (*orig_filtered_result_count)++;
        (*filtered_result_count)++;
        if (strcmp (level, "High") == 0)
          {
            (*orig_f_holes)++;
            (*f_holes)++;
          }
        else if (strcmp (level, "Medium") == 0)
          {
            (*orig_f_warnings)++;
            (*f_warnings)++;
          }
        else if (strcmp (level, "Low") == 0)
          {
            (*orig_f_infos)++;
            (*f_infos)++;
          }
        else if (strcmp (level, "Log") == 0)
          {
            (*orig_f_logs)++;
            (*f_logs)++;
          }
        else if (strcmp (level, "False Positive") == 0)
          {
            (*orig_f_false_positives)++;
            (*f_false_positives)++;
          }
      }

    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 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 *clean, *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;
  int total_result_count, filtered_result_count;
  array_t *result_hosts;
  int reuse_result_iterator;
  iterator_t results, delta_results;
  int holes, infos, logs, warnings, false_positives;
  int f_holes, f_infos, f_logs, f_warnings, f_false_positives;
  int 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, count_filtered;
  double severity, f_severity;
  gchar *tz, *zone;
  char *old_tz_override;
  GString *filters_buffer, *filters_extra_buffer, *host_summary_buffer;
  gchar *term_value;
  GHashTable *f_host_ports;
  GHashTable *f_host_holes, *f_host_warnings, *f_host_infos;
  GHashTable *f_host_logs, *f_host_false_positives;
  GHashTable *f_host_compliant, *f_host_notcompliant;
  GHashTable  *f_host_incomplete, *f_host_undefined;
  task_status_t run_status;
  gchar *tsk_usage_type = NULL;
  int f_compliance_yes, f_compliance_no;
  int f_compliance_incomplete, f_compliance_undefined;
  int f_compliance_count;

  int delta_reports_version = 0;

  /* Init some vars to prevent warnings from older compilers. */
  max_results = -1;
  levels = NULL;
  compliance_levels = NULL;
  zone = NULL;
  delta_states = NULL;
  min_qod = NULL;
  search_phrase = NULL;
  total_result_count = filtered_result_count = 0;
  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 = 0;
  f_host_ports = NULL;
  f_host_holes = NULL;
  f_host_warnings = NULL;
  f_host_infos = NULL;
  f_host_logs = NULL;
  f_host_false_positives = NULL;
  f_host_compliant = NULL;
  f_host_notcompliant = NULL;
  f_host_incomplete = NULL;
  f_host_undefined = NULL;

  /** @todo Leaks on error in PRINT and PRINT_XML.  The process normally exits
   *        then anyway. */

  /* run_status is set by report_scan_run_status when either of "delta" and
   * "report" are true.  run_status is only used by run_status_name, only when
   * either of "delta" and "report" are true, and only after a
   * report_scan_run_status call.  Still GCC 4.4.5 (Debian 4.4.5-8) gives a
   * "may be used uninitialized" warning, so init it here to quiet the
   * warning. */
  run_status = TASK_STATUS_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, &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, &zone);
    }

  if (delta) {
    delta_reports_version = setting_delta_reports_version_int ();
    g_debug ("%s: delta reports version %d", __func__, delta_reports_version);
  }

  max_results = manage_max_rows (max_results);

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

  if (task && (task_uuid (task, &tsk_uuid) || task_usage_type(task, &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 (zone && strlen (zone))
    {
      gchar *quoted_zone;
      /* Store current TZ. */
      tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;

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

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

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

      tzset ();
    }
  else
    {
      /* Keep compiler quiet. */
      tz = NULL;
      old_tz_override = NULL;
    }

  if (delta && report)
    {
      uuid = report_uuid (report);
      PRINT (out, "<report type=\"delta\" id=\"%s\">", uuid);
      free (uuid);
    }
  else
    {
      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);
          tz_revert (zone, tz, old_tz_override);
          return -1;
        }
      PRINT (out,
             "<timestamp>%s</timestamp>",
             timestamp);
      g_free (timestamp);

      start_time = scan_start_time (delta);
      PRINT (out,
             "<scan_start>%s</scan_start>",
             start_time);
      free (start_time);

      end_time = scan_end_time (delta);
      PRINT (out,
             "<scan_end>%s</scan_end>",
             end_time);
      free (end_time);

      PRINT (out,
             "</report>"
             "</delta>");
    }

  count_filtered = (delta == 0 && ignore_pagination && get->details)
                   || (delta_reports_version == 2);

  if (report)
    {
      /* Get total counts of full results. */
      #if COMPLIANCE_REPORTS == 1
        if (strcmp (tsk_usage_type, "audit"))
      #endif
        {
          if (delta == 0)
            {         
              int 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 (report, &total_holes, &total_infos,
                                &total_logs, &total_warnings,
                                &total_false_positives, NULL, all_results_get,
                                NULL);
              total_result_count = 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 (count_filtered)
            {
              /* We're getting all the filtered results, so we can count them as we
              * print them, to save time. */

              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 (report, &holes, &infos, &logs, &warnings,
                                &false_positives, NULL, get, NULL);

              filtered_result_count = holes + infos + logs + warnings
                                      + false_positives;

            }          
        }
      /* Get report run status. */

      report_scan_run_status (report, &run_status);
    }

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

  term_value = filter_term_value (clean, "min_qod");
  if (term_value == NULL)
    {
      gchar *new_filter;
      new_filter = g_strdup_printf ("min_qod=%i %s",
                                    MIN_QOD_DEFAULT,
                                    clean);
      g_free (clean);
      clean = new_filter;
    }
  g_free (term_value);

  term_value = filter_term_value (clean, "apply_overrides");
  if (term_value == NULL)
    {
      gchar *new_filter;
      new_filter = g_strdup_printf ("apply_overrides=%i %s",
                                    APPLY_OVERRIDES_DEFAULT,
                                    clean);
      g_free (clean);
      clean = new_filter;
    }
  g_free (term_value);

  g_free (term);
  term = clean;

  if (delta
      && sort_field 
      && (delta_reports_version == 1)
      /* These are all checked in result_cmp. */
      && strcmp (sort_field, "name")
      && strcmp (sort_field, "vulnerability")
      && strcmp (sort_field, "host")
      && strcmp (sort_field, "port")
      && strcmp (sort_field, "location")
      && strcmp (sort_field, "severity")
      && strcmp (sort_field, "nvt")
      && strcmp (sort_field, "description")
      && strcmp (sort_field, "type")
      && strcmp (sort_field, "original_type"))
    {
      gchar *new_term;

      if ((strcmp (sort_field, "task") == 0)
          || (strcmp (sort_field, "task_id") == 0)
          || (strcmp (sort_field, "report_id") == 0))
        {
          /* These don't affect delta report, so sort by vulnerability. */
          g_free (sort_field);
          sort_field = g_strdup ("vulnerability");
        }
      else
        {
          /* The remaining filterable fields for the result iterator, all of
           * which may be used as a sort field.  These could be added to
           * result_cmp.  For now sort by vulnerability. */
#if 0
          "uuid", "comment", "created", "modified", "_owner",
          "cvss_base", "nvt_version", "original_severity", "date",
          "solution_type", "qod", "qod_type", "cve", "hostname", "path"
#endif
          g_free (sort_field);
          sort_field = g_strdup ("vulnerability");
        }

      /* Adjust "term" to match sort_field, because "term" will be used in the
       * REPORT XML FILTERS (sent by buffer_get_filter_xml below). */
      new_term = g_strdup_printf ("sort=%s %s",
                                  sort_field,
                                  term);
      g_free (term);
      term = new_term;
      /* Similarly, the order will now be ascending. */
      sort_order = 1;
    }

  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 COMPLIANCE_REPORTS == 1
    if (strcmp (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
  #endif
    {
      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)
    {
      int tag_count = resource_tag_count ("report", 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", 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;
      int task_tag_count = resource_tag_count ("task", 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>"
             "<progress>%s</progress>",
             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 : "",
             progress_xml);
      g_free (progress_xml);
      free (comment);
      free (tsk_name);
      free (tsk_uuid);
      free (task_target_uuid);
      free (task_target_name);
      free (task_target_comment);

      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);
      tz_revert (zone, tz, old_tz_override);
      return -1;
    }
  free (uuid);
  PRINT (out,
         "<timestamp>%s</timestamp>",
         timestamp);
  g_free (timestamp);

  start_time = scan_start_time (report);
  PRINT (out,
         "<scan_start>%s</scan_start>",
         start_time);
  free (start_time);

  {
    time_t start_time_epoch;
    const char *abbrev;
    gchar *report_zone;

    start_time_epoch = scan_start_time_epoch (report);
    abbrev = NULL;
    if (zone && strlen (zone))
      report_zone = g_strdup (zone);
    else
      report_zone = setting_timezone ();
    iso_time_tz (&start_time_epoch, report_zone, &abbrev);

    if (zone_return)
      *zone_return = g_strdup (report_zone ? report_zone : "");

    PRINT (out,
           "<timezone>%s</timezone>"
           "<timezone_abbrev>%s</timezone_abbrev>",
           report_zone
            ? report_zone
            : "Coordinated Universal Time",
           abbrev ? abbrev : "UTC");
    g_free (report_zone);
  }

  /* Port summary. */

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

  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);
          tz_revert (zone, tz, old_tz_override);
          g_hash_table_destroy (f_host_ports);
          return -1;
        }
    }

  /* Prepare result counts. */
  #if COMPLIANCE_REPORTS == 1
    int compliance_yes, compliance_no;
    int compliance_incomplete, compliance_undefined;
    int total_compliance_count = 0;

    if (strcmp (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 (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
  #endif
  {
    if (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, &holes, &infos, &logs,
                              &warnings, &false_positives, &severity,
                              get, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

        f_holes = f_infos = f_logs = f_warnings = 0;
        f_false_positives = f_severity = 0;
      }
    else
        report_counts_id_full (report, &holes, &infos, &logs,
                              &warnings, &false_positives, &severity,
                              get, NULL,
                              &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 (delta_reports_version == 1) 
        {
          if (init_delta_iterators (report, &results, delta, 
                                    &delta_results, get, 
                                    term, sort_field))
            {
              g_free (term);
              g_hash_table_destroy (f_host_ports);
              return -1;
            }
          g_free (term);
        } 
      else 
        {      
          if (init_v2_delta_iterator (report, &results, delta,
                                      get, term, sort_field))
            {
              g_free (term);
              g_hash_table_destroy (f_host_ports);
              return -1;
            }
        }
    }
  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);
              return -1;
            }
        }
    }
  else
    g_free (term);

  if (get->details)
    PRINT (out,
             "<results"
             " start=\"%i\""
             " max=\"%i\">",
             /* Add 1 for 1 indexing. */
             ignore_pagination ? 1 : first_result + 1,
             ignore_pagination ? -1 : max_results);
  if (get->details && result_hosts_only)
    result_hosts = make_array ();
  else
    /* Quiet erroneous compiler warning. */
    result_hosts = NULL;

  #if COMPLIANCE_REPORTS == 1
    if (strcmp (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
  #endif
  {
    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 (delta_reports_version == 1) 
        {
          if (print_report_delta_xml (out, &results, &delta_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,
                                      &filtered_result_count,
                                      &orig_f_holes, &f_holes,
                                      &orig_f_infos, &f_infos,
                                      &orig_f_logs, &f_logs,
                                      &orig_f_warnings, &f_warnings,
                                      &orig_f_false_positives, 
                                      &f_false_positives,
                                      result_hosts))
          goto failed_delta_report;
        } 
      else 
        {
          if (print_v2_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,
                                        &filtered_result_count,
                                        &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 COMPLIANCE_REPORTS == 1
            if (strcmp (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 (count_filtered)
                      f_compliance_yes++;
                  }
                else if (strcasecmp (compliance, "no") == 0)
                  {
                    f_host_result_counts = f_host_notcompliant;
                    if (count_filtered)
                      f_compliance_no++;
                  }
                else if (strcasecmp (compliance, "incomplete") == 0)
                  {
                    f_host_result_counts = f_host_incomplete;
                    if (count_filtered)
                      f_compliance_incomplete++;
                  }
                else if (strcasecmp (compliance, "undefined") == 0)
                  {
                    f_host_result_counts = f_host_undefined;
                    if (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
          #endif
          {
            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 (count_filtered)
                  f_logs++;
              }
            else if (strcasecmp (level, "high") == 0)
              {
                f_host_result_counts = f_host_holes;
                if (count_filtered)
                  f_holes++;
              }
            else if (strcasecmp (level, "medium") == 0)
              {
                f_host_result_counts = f_host_warnings;
                if (count_filtered)
                  f_warnings++;
              }
            else if (strcasecmp (level, "low") == 0)
              {
                f_host_result_counts = f_host_infos;
                if (count_filtered)
                  f_infos++;
              }
            else if (strcasecmp (level, "false positive") == 0)
              {
                f_host_result_counts = f_host_false_positives;
                if (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);
  if (delta && get->details && delta_reports_version == 1)
    cleanup_iterator (&delta_results);

  /* Print result counts and severity. */

  #if COMPLIANCE_REPORTS == 1
    if (strcmp (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 (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
  #endif
  {
    if (delta)
      /** @todo The f_holes, etc. vars are setup to give the page count. */
      PRINT (out,
            "<result_count>"
            "<filtered>%i</filtered>"
            "<hole><filtered>%i</filtered></hole>"
            "<info><filtered>%i</filtered></info>"
            "<log><filtered>%i</filtered></log>"
            "<warning><filtered>%i</filtered></warning>"
            "<false_positive>"
            "<filtered>%i</filtered>"
            "</false_positive>"
            "</result_count>",
            orig_filtered_result_count,
            (strchr (levels, 'h') ? orig_f_holes : 0),
            (strchr (levels, 'l') ? orig_f_infos : 0),
            (strchr (levels, 'g') ? orig_f_logs : 0),
            (strchr (levels, 'm') ? orig_f_warnings : 0),
            (strchr (levels, 'f') ? orig_f_false_positives : 0));
    else
      {
        if (count_filtered)
          filtered_result_count = f_holes + f_infos + f_logs
                                  + f_warnings + false_positives;

        PRINT (out,
              "<result_count>"
              "%i"
              "<full>%i</full>"
              "<filtered>%i</filtered>"
              "<hole><full>%i</full><filtered>%i</filtered></hole>"
              "<info><full>%i</full><filtered>%i</filtered></info>"
              "<log><full>%i</full><filtered>%i</filtered></log>"
              "<warning><full>%i</full><filtered>%i</filtered></warning>"
              "<false_positive>"
              "<full>%i</full>"
              "<filtered>%i</filtered>"
              "</false_positive>"
              "</result_count>",
              total_result_count,
              total_result_count,
              filtered_result_count,
              holes,
              (strchr (levels, 'h') ? f_holes : 0),
              infos,
              (strchr (levels, 'l') ? f_infos : 0),
              logs,
              (strchr (levels, 'g') ? f_logs : 0),
              warnings,
              (strchr (levels, 'm') ? f_warnings : 0),
              false_positives,
              (strchr (levels, 'f') ? f_false_positives : 0));

        PRINT (out,
              "<severity>"
              "<full>%1.1f</full>"
              "<filtered>%1.1f</filtered>"
              "</severity>",
              severity,
              f_severity);
      }
  }

  if (host_summary)
    {
      host_summary_buffer = g_string_new ("");
      g_string_append_printf (host_summary_buffer,
                              "   %-15s   %-16s   End\n",
                              "Host", "Start");
    }
  else
    host_summary_buffer = NULL;

  if (get->details && result_hosts_only)
    {
      gchar *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 (delta && (present == FALSE) && delta_reports_version == 1)
            {
              cleanup_iterator (&hosts);
              init_report_host_iterator (&hosts, delta, result_host, 0);
              present = next (&hosts);
            }
          if (present)
            {
                if (print_report_host_xml (out,
                                          &hosts,
                                          result_host,
                                          tsk_usage_type,
                                          lean,
                                          host_summary_buffer,
                                          f_host_ports,
                                          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,
                                    tsk_usage_type,
                                    lean,
                                    host_summary_buffer,
                                    f_host_ports,
                                    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 COMPLIANCE_REPORTS == 1
    if (strcmp (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
  #endif
  {
    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))
                {
                  fclose (out);
                  cleanup_iterator (&hosts);
                  tz_revert (zone, tz, old_tz_override);
                  return -1;
                }
            }
          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))
            {
              fclose (out);
              cleanup_iterator (&hosts);
              tz_revert (zone, tz, old_tz_override);
              return -1;
            }
        }
      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))
    {
      tz_revert (zone, tz, old_tz_override);
      if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
      return -1;
    }

  g_free (sort_field);
  g_free (levels);
  g_free (search_phrase);
  g_free (min_qod);
  g_free (delta_states);
  g_free (compliance_levels);
  g_free (tsk_usage_type);

  if (host_summary && host_summary_buffer)
    *host_summary = g_string_free (host_summary_buffer, FALSE);

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

  return 0;

  failed_delta_report:
     fclose (out);
     g_free (sort_field);
     g_free (levels);
     g_free (search_phrase);
     g_free (min_qod);
     g_free (delta_states);
     cleanup_iterator (&results);
     cleanup_iterator (&delta_results);
  failed_print_report_host:
    if (host_summary_buffer)
        g_string_free (host_summary_buffer, TRUE);
    tz_revert (zone, tz, old_tz_override);
    g_hash_table_destroy (f_host_ports);
    #if COMPLIANCE_REPORTS == 1
      g_free (compliance_levels);
      if (strcmp (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
    #endif
    {
      g_hash_table_destroy (f_host_holes);
      g_hash_table_destroy (f_host_warnings);
      g_hash_table_destroy (f_host_infos);
      g_hash_table_destroy (f_host_logs);
      g_hash_table_destroy (f_host_false_positives);
    }
    return -1;
}

/**
 * @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 escalate report with,
 *                                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;

  /* Escalate instead, if requested. */

  if (alert_id)
    {
      alert_t alert = 0;
      alert_condition_t condition;
      alert_method_t method;

      if (find_alert_with_permission (alert_id, &alert, "get_alerts"))
        return -1;

      if (alert == 0)
        return 1;

      condition = alert_condition (alert);
      method = alert_method (alert);

      ret = escalate_2 (alert, task, report, EVENT_TASK_RUN_STATUS_CHANGED,
                        (void*) TASK_STATUS_DONE, method, condition,
                        get, notes_details, overrides_details, NULL);
      if (ret == -3)
        return -4;
      if (ret == -1)
        return -3;
      if (ret)
        return -2;
      return 0;
    }

  /* Print the report as XML to a file. */

  if ((report_format > 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 Get the IP of a host, using the 'hostname' report host details.
 *
 * The most recent host detail takes preference.
 *
 * @param[in]  host  Host name or IP.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
gchar*
report_host_ip (const char *host)
{
  gchar *quoted_host, *ret;
  quoted_host = sql_quote (host);
  ret = sql_string ("SELECT host FROM report_hosts"
                    " WHERE id = (SELECT report_host FROM report_host_details"
                    "             WHERE name = 'hostname'"
                    "             AND value = '%s'"
                    "             ORDER BY id DESC LIMIT 1);",
                    quoted_host);
  g_free (quoted_host);
  return ret;
}

/**
 * @brief Get the hostname of a report_host.
 *
 * The most recent host detail takes preference.
 *
 * @param[in]  report_host  Report host.
 *
 * @return Newly allocated hostname if available, else NULL.
 */
gchar*
report_host_hostname (report_host_t report_host)
{
  return sql_string ("SELECT value FROM report_host_details"
                     " WHERE report_host = %llu"
                     " AND name = 'hostname'"
                     " ORDER BY id DESC LIMIT 1;",
                     report_host);
}

/**
 * @brief Get the best_os_cpe of a report_host.
 *
 * The most recent host detail takes preference.
 *
 * @param[in]  report_host  Report host.
 *
 * @return Newly allocated best_os_cpe if available, else NULL.
 */
gchar*
report_host_best_os_cpe (report_host_t report_host)
{
  return sql_string ("SELECT value FROM report_host_details"
                     " WHERE report_host = %llu"
                     " AND name = 'best_os_cpe'"
                     " ORDER BY id DESC LIMIT 1;",
                     report_host);
}

/**
 * @brief Get the best_os_txt of a report_host.
 *
 * The most recent host detail takes preference.
 *
 * @param[in]  report_host  Report host.
 *
 * @return Newly allocated best_os_txt if available, else NULL.
 */
gchar*
report_host_best_os_txt (report_host_t report_host)
{
  return sql_string ("SELECT value FROM report_host_details"
                     " WHERE report_host = %llu"
                     " AND name = 'best_os_txt'"
                     " ORDER BY id DESC LIMIT 1;",
                     report_host);
}

/**
 * @brief Check if a report host is alive and has at least one result.
 *
 * @param[in]  report  Report.
 * @param[in]  host    Host name or IP.
 *
 * @return 0 if dead, else alive.
 */
int
report_host_noticeable (report_t report, const gchar *host)
{
  report_host_t report_host = 0;

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

  return report_host
         && report_host_dead (report_host) == 0
         && report_host_result_count (report_host) > 0;
}

/**
 * @brief 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"
 */
static 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]  holes_a   Number of holes on earlier report.
 * @param[in]  warns_a   Number of warnings on earlier report.
 * @param[in]  infos_a   Number of infos on earlier report.
 * @param[in]  severity_a Severity of earlier report.
 * @param[in]  holes_b   Number of holes on later report.
 * @param[in]  warns_b   Number of warnings on later report.
 * @param[in]  infos_b   Number of infos on later report.
 * @param[in]  severity_b Severity of later report.
 *
 * @return "up", "down", "more", "less", "same" or if too few reports "".
 */
static const char *
task_trend_calc (int holes_a, int warns_a, int infos_a, double severity_a,
                 int holes_b, int warns_b, int infos_b, double severity_b)
{
  int threat_a, threat_b;

  /* Check if the severity score changed. */

  if (severity_a > severity_b)
    return "up";

  if (severity_a < severity_b)
    return "down";

  /* Calculate trend. */

  if (holes_a > 0)
    threat_a = 4;
  else if (warns_a > 0)
    threat_a = 3;
  else if (infos_a > 0)
    threat_a = 2;
  else
    threat_a = 1;

  if (holes_b > 0)
    threat_b = 4;
  else if (warns_b > 0)
    threat_b = 3;
  else if (infos_b > 0)
    threat_b = 2;
  else
    threat_b = 1;

  /* Check if the threat level changed. */

  if (threat_a > threat_b)
    return "up";

  if (threat_a < threat_b)
    return "down";

  /* Check if the threat count changed in the highest level. */

  if (holes_a)
    {
      if (holes_a > holes_b)
        return "more";
      if (holes_a < holes_b)
        return "less";
      return "same";
    }

  if (warns_a)
    {
      if (warns_a > warns_b)
        return "more";
      if (warns_a < warns_b)
        return "less";
      return "same";
    }

  if (infos_a)
    {
      if (infos_a > infos_b)
        return "more";
      if (infos_a < infos_b)
        return "less";
      return "same";
    }

  return "same";
}

/**
 * @brief Return the trend of a task, given counts.
 *
 * @param[in]  iterator  Task iterator.
 * @param[in]  holes_a   Number of holes on earlier report.
 * @param[in]  warns_a   Number of warnings on earlier report.
 * @param[in]  infos_a   Number of infos on earlier report.
 * @param[in]  severity_a Severity score of earlier report.
 * @param[in]  holes_b   Number of holes on later report.
 * @param[in]  warns_b   Number of warnings on later report.
 * @param[in]  infos_b   Number of infos on later report.
 * @param[in]  severity_b  Severity score of later report.
 *
 * @return "up", "down", "more", "less", "same" or if too few reports "".
 */
const char *
task_iterator_trend_counts (iterator_t *iterator, int holes_a, int warns_a,
                            int infos_a, double severity_a, int holes_b,
                            int warns_b, int infos_b, double severity_b)
{
  /* Ensure there are enough reports. */
  if (task_iterator_finished_reports (iterator) <= 1)
    return "";

  /* Skip running tasks. */

  if (task_iterator_run_status (iterator) == TASK_STATUS_RUNNING)
    return "";

  return task_trend_calc (holes_a, warns_a, infos_a, severity_a,
                          holes_b, warns_b, infos_b, severity_b);
}

/**
 * @brief 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, schedule, schedule_periods,"
                            " scanner, schedule_next_time,"
                            " config_location, target_location,"
                            " schedule_location, scanner_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[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 container tasks only
 *         certain fields may be edited, -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,
             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)
      && (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))
    {
      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 (preferences)
    switch (set_task_preferences (task, preferences))
      {
        case 0:
          break;
        case 1:
          return 13;
        case 2:
          return 14;
        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.
 */
static 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 name to alive test bitfield.
 *
 * @param[in]  alive_tests  Name of alive test.
 *
 * @return Alive test, or -1 on error.
 */
static int
alive_test_from_string (const char* alive_tests)
{
  alive_test_t alive_test;
  if (alive_tests == NULL
      || strcmp (alive_tests, "") == 0
      || strcmp (alive_tests, "Scan Config Default") == 0)
    alive_test = 0;
  else if (strcmp (alive_tests, "ICMP, TCP-ACK Service & ARP Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ICMP | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "TCP-ACK Service & ARP Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "ICMP & ARP Ping") == 0)
    alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "ICMP & TCP-ACK Service Ping") == 0)
    alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_TCP_ACK_SERVICE;
  else if (strcmp (alive_tests, "ARP Ping") == 0)
    alive_test = ALIVE_TEST_ARP;
  else if (strcmp (alive_tests, "TCP-ACK Service Ping") == 0)
    alive_test = ALIVE_TEST_TCP_ACK_SERVICE;
  else if (strcmp (alive_tests, "TCP-SYN Service Ping") == 0)
    alive_test = ALIVE_TEST_TCP_SYN_SERVICE;
  else if (strcmp (alive_tests, "ICMP Ping") == 0)
    alive_test = ALIVE_TEST_ICMP;
  else if (strcmp (alive_tests, "Consider Alive") == 0)
    alive_test = ALIVE_TEST_CONSIDER_ALIVE;
  else
    return -1;
  return alive_test;
}

/**
 * @brief Set login data for a target.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 * @param[in]  credential     The credential or 0 to remove.
 * @param[in]  port           The port to authenticate at with credential.
 *
 * @return  0 on success, -1 on error, 1 target not found, 99 permission denied.
 */
static int
set_target_login_data (target_t target, const char* type,
                       credential_t credential, int port)
{
  gchar *quoted_type;

  if (current_credentials.uuid
      && (acl_user_may ("modify_target") == 0))
    return 99;

  if (type == NULL)
    return -1;

  if (target == 0)
    return 1;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT count (*) FROM targets_login_data"
               " WHERE target = %llu AND type = '%s';",
               target, quoted_type))
    {
      if (credential == 0)
        {
          sql ("DELETE FROM targets_login_data"
               " WHERE target = '%llu' AND type = '%s';",
               target, quoted_type);
        }
      else
        {
          sql ("UPDATE targets_login_data"
               " SET credential = %llu, port = %d"
               " WHERE target = %llu AND type = '%s';",
               credential, port, target, quoted_type);
        }
    }
  else if (credential)
    {
      sql ("INSERT INTO targets_login_data (target, type, credential, port)"
            " VALUES (%llu, '%s', %llu, %i)",
            target, quoted_type, credential, port);
    }

  g_free (quoted_type);
  return 0;
}

/**
 * @brief Get a credential from a target.
 *
 * @param[in]  target         The target.
 * @param[in]  type           The credential type (e.g. "ssh" or "smb").
 *
 * @return  0 on success, -1 on error, 1 credential not found, 99 permission
 *          denied.
 */
credential_t
target_credential (target_t target, const char* type)
{
  gchar *quoted_type;
  credential_t credential;

  if (target == 0 || type == NULL)
    return 0;

  quoted_type = sql_quote (type);

  if (sql_int ("SELECT NOT EXISTS"
               " (SELECT * FROM targets_login_data"
               "  WHERE target = %llu and type = '%s');",
               target, quoted_type))
    {
      g_free (quoted_type);
      return 0;
    }

  sql_int64 (&credential,
             "SELECT credential FROM targets_login_data"
             " WHERE target = %llu AND type = '%s';",
             target, quoted_type);

  g_free (quoted_type);

  return credential;
}

/**
 * @brief Get a 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]   reverse_lookup_only   Scanner preference reverse_lookup_only.
 * @param[in]   reverse_lookup_unify  Scanner preference reverse_lookup_unify.
 * @param[in]   alive_tests     Alive tests.
 * @param[in]   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,
 *         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,
               const char *reverse_lookup_only,
               const char *reverse_lookup_unify, const char *alive_tests,
               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;

  alive_test = alive_test_from_string (alive_tests);
  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"))
        {
          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"))
        {
          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"))
        {
          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"))
        {
          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"))
        {
          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");
    }

  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]   reverse_lookup_only   Scanner preference reverse_lookup_only.
 * @param[in]   reverse_lookup_unify  Scanner preference reverse_lookup_unify.
 * @param[in]   alive_tests     Alive tests.
 * @param[in]   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,
 *         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 *reverse_lookup_only,
               const char *reverse_lookup_unify, const char *alive_tests,
               const char *allow_simultaneous_ips)
{
  target_t target;
  credential_t ssh_credential = 0;
  credential_t ssh_elevate_credential = 0;

  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)
    {
      int alive_test;

      alive_test = alive_test_from_string (alive_tests);
      if (alive_test <= -1)
        {
          sql_rollback ();
          return 10;
        }
      sql ("UPDATE targets SET"
           " alive_test = '%i',"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           alive_test,
           target);
    }

  if (port_list_id)
    {
      port_list_t port_list;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      port_list = 0;
      if (find_port_list_with_permission (port_list_id, &port_list,
                                          "get_port_lists"))
        {
          sql_rollback ();
          return -1;
        }

      if (port_list == 0)
        {
          sql_rollback ();
          return 6;
        }

      sql ("UPDATE targets SET"
           " port_list = %llu,"
           " modification_time = m_now ()"
           " WHERE id = %llu;",
           port_list,
           target);
    }

  if (ssh_credential_id)
    {
      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      ssh_credential = 0;
      if (strcmp (ssh_credential_id, "0"))
        {
          int port_int;
          gchar *type;

          if (find_credential_with_permission (ssh_credential_id,
                                               &ssh_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (ssh_credential == 0)
            {
              sql_rollback ();
              return 7;
            }

          if (ssh_port && strcmp (ssh_port, "0") && strcmp (ssh_port, ""))
            {
              if (validate_port (ssh_port))
                {
                  sql_rollback ();
                  return 5;
                }
              port_int = atoi (ssh_port);
            }
          else
            port_int = 22;

          type = credential_type (ssh_credential);
          if (strcmp (type, "up") && strcmp (type, "usk"))
            {
              sql_rollback ();
              return 18;
            }
          g_free (type);

          set_target_login_data (target, "ssh", ssh_credential, port_int);
        }
      else
        set_target_login_data (target, "ssh", 0, 0);
    }

  if (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"))
            {
              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)
    {
      credential_t smb_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      smb_credential = 0;
      if (strcmp (smb_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (smb_credential_id,
                                               &smb_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (smb_credential == 0)
            {
              sql_rollback ();
              return 7;
            }

          type = credential_type (smb_credential);
          if (strcmp (type, "up"))
            {
              sql_rollback ();
              return 19;
            }
          g_free (type);

          set_target_login_data (target, "smb", smb_credential, 0);
        }
      else
        set_target_login_data (target, "smb", 0, 0);
    }

  if (esxi_credential_id)
    {
      credential_t esxi_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      esxi_credential = 0;
      if (strcmp (esxi_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (esxi_credential_id,
                                               &esxi_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (esxi_credential == 0)
            {
              sql_rollback ();
              return 16;
            }

          type = credential_type (esxi_credential);
          if (strcmp (type, "up"))
            {
              sql_rollback ();
              return 20;
            }
          g_free (type);

          set_target_login_data (target, "esxi", esxi_credential, 0);
        }
      else
        set_target_login_data (target, "esxi", 0, 0);
    }

  if (snmp_credential_id)
    {
      credential_t snmp_credential;

      if (target_in_use (target))
        {
          sql_rollback ();
          return 15;
        }

      snmp_credential = 0;
      if (strcmp (snmp_credential_id, "0"))
        {
          gchar *type;
          if (find_credential_with_permission (snmp_credential_id,
                                               &snmp_credential,
                                               "get_credentials"))
            {
              sql_rollback ();
              return -1;
            }

          if (snmp_credential == 0)
            {
              sql_rollback ();
              return 17;
            }

          type = credential_type (snmp_credential);
          if (strcmp (type, "snmp"))
            {
              sql_rollback ();
              return 21;
            }
          g_free (type);

          set_target_login_data (target, "snmp", snmp_credential, 0);
        }
      else
        set_target_login_data (target, "snmp", 0, 0);
    }

  if (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 (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 },                        \
   { "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                                       \
   },                                                          \
   { "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 },                                            \
   { "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 test description from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Reverse lookup unify of the target or NULL if iteration is complete.
 */
const char*
target_iterator_alive_tests (iterator_t* iterator)
{
  int tests;
  if (iterator->done) return "";
  tests = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 13);
  if ((tests & ALIVE_TEST_TCP_ACK_SERVICE)
      && (tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_ARP))
    return "ICMP, TCP-ACK Service & ARP Ping";
  if ((tests & ALIVE_TEST_TCP_ACK_SERVICE)
      && (tests & ALIVE_TEST_ARP))
    return "TCP-ACK Service & ARP Ping";
  if ((tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_ARP))
    return "ICMP & ARP Ping";
  if ((tests & ALIVE_TEST_ICMP)
      && (tests & ALIVE_TEST_TCP_ACK_SERVICE))
    return "ICMP & TCP-ACK Service Ping";
  if (tests & ALIVE_TEST_ARP)
    return "ARP Ping";
  if (tests & ALIVE_TEST_TCP_ACK_SERVICE)
    return "TCP-ACK Service Ping";
  if (tests & ALIVE_TEST_TCP_SYN_SERVICE)
    return "TCP-SYN Service Ping";
  if (tests & ALIVE_TEST_ICMP)
    return "ICMP Ping";
  if (tests & ALIVE_TEST_CONSIDER_ALIVE)
    return "Consider Alive";
  return "Scan Config Default";
}

/**
 * @brief Get the ESXi LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_esxi_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 14);
  return ret;
}

/**
 * @brief Get the ESXi LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_esxi_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 15);
  return ret;
}

/**
 * @brief Get the SNMP LSC credential from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_snmp_credential (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 16);
  return ret;
}

/**
 * @brief Get the SNMP LSC credential location from a target iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return ESXi LSC credential.
 */
int
target_iterator_snmp_trash (iterator_t* iterator)
{
  int ret;
  if (iterator->done) return -1;
  ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 17);
  return ret;
}

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

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


/* SecInfo Alerts. */

/**
 * @brief Check for new SCAP SecInfo after an update.
 */
static void
check_for_new_scap ()
{
  if (manage_scap_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM scap.cves"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (EVENT_NEW_SECINFO, "cve", 0, 0);

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM scap.cpes"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (EVENT_NEW_SECINFO, "cpe", 0, 0);
    }
}

/**
 * @brief Check for new CERT SecInfo after an update.
 */
static void
check_for_new_cert ()
{
  if (manage_cert_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert.cert_bund_advs"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (EVENT_NEW_SECINFO, "cert_bund_adv", 0, 0);

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert.dfn_cert_advs"
                   "  WHERE creation_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0));"))
        event (EVENT_NEW_SECINFO, "dfn_cert_adv", 0, 0);
    }
}

/**
 * @brief Print an URL for a New NVTs alert.
 *
 * @param[in]  url      Format string for url.
 * @param[in]  oid      SecInfo ID.
 * @param[in]  type     SecInfo Type.
 *
 * @return Freshly allocated url.
 */
static gchar *
alert_url_print (const gchar *url, const gchar *oid, const gchar *type)
{
  int formatting;
  const gchar *point, *end;
  GString *new_url;

  assert (url);

  new_url = g_string_new ("");
  for (formatting = 0, point = url, end = (url + strlen (url));
       point < end;
       point++)
    if (formatting)
      {
        switch (*point)
          {
            case '$':
              g_string_append_c (new_url, '$');
              break;
            case 'o':
              {
                g_string_append (new_url, oid);
                break;
              }
            case 't':
              {
                g_string_append (new_url, type);
                break;
              }
            default:
              g_string_append_c (new_url, '$');
              g_string_append_c (new_url, *point);
              break;
          }
        formatting = 0;
      }
    else if (*point == '$')
      formatting = 1;
    else
      g_string_append_c (new_url, *point);

  return g_string_free (new_url, FALSE);
}

/**
 * @brief Create list for New NVTs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_nvts_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;
  time_t feed_version_epoch;

  feed_version_epoch = nvts_feed_version_epoch();

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  if (details_url && strlen (details_url))
    buffer = g_string_new (NEW_NVTS_HEADER);
  else
    buffer = g_string_new (NEW_NVTS_HEADER_OID);

  count = 0;
  // TODO This should use an iterator provided by manage_sql_nvts.c.
  if (example)
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " WHERE creation_time > %ld"
                   " ORDER BY creation_time DESC;",
                   feed_version_epoch);
  else
    init_iterator (&rows,
                   "SELECT oid, name, solution_type, cvss_base, qod FROM nvts"
                   " WHERE modification_time > %ld"
                   "   AND creation_time <= %ld"
                   " ORDER BY modification_time DESC;",
                   feed_version_epoch,
                   feed_version_epoch);

  while (next (&rows))
    {
      gchar *url;
      const char *name;

      name = iterator_string (&rows, 1);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-57.57s%-3s  %13s  %8s %3s%%%s%s%s",
                              name,
                              strlen (name) > 60
                               ? "..."
                               : (strlen (name) > 57 ? name + 57 : "   "),
                              iterator_string (&rows, 2),
                              iterator_string (&rows, 3),
                              iterator_string (&rows, 4),
                              url ? "\n  " : "  ",
                              url ? url : iterator_string (&rows, 0),
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for New CVEs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated message.
 */
static gchar *
new_cves_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CVES_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, severity, description FROM cves"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, severity, description FROM cves"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, severity, description FROM cves"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'scap_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *desc;

      name = iterator_string (&rows, 1);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      desc = iterator_string (&rows, 3);
      g_string_append_printf (buffer,
                              "%-15.15s  %8s  %50.50s%s%s%s%s",
                              name,
                              iterator_string (&rows, 2),
                              desc,
                              strlen (desc) > 53
                               ? "..."
                               : (strlen (desc) > 50 ? desc + 50 : "   "),
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for New CPEs event.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_cpes_list (event_t event, const void* event_data, alert_t alert,
               int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CPES_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cpes"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'scap_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-57.57s%-3s  %-s%s%s%s",
                              name,
                              strlen (name) > 60
                               ? "..."
                               : (strlen (name) > 57 ? name + 57 : "   "),
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for "New CERT-Bund Advisories" event message.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event data.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated string.
 */
static gchar *
new_cert_bunds_list (event_t event, const void* event_data, alert_t alert,
                     int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_CERT_BUNDS_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM cert_bund_advs"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'cert_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-11s  %-s%s%s%s",
                              name,
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create list for "New DFN-CERT Advisories" event message.
 *
 * @param[in]  event         Event.
 * @param[in]  event_data    Event type specific details.
 * @param[in]  alert         Alert.
 * @param[in]  example       Whether the message is an example only.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated string.
 */
static gchar *
new_dfn_certs_list (event_t event, const void* event_data, alert_t alert,
                    int example, int *count_return)
{
  iterator_t rows;
  GString *buffer;
  int count;
  char *details_url;
  const gchar *type;

  details_url = alert_data (alert, "method", "details_url");
  type = (gchar*) event_data;

  buffer = g_string_new (NEW_DFN_CERTS_HEADER);

  count = 0;
  if (example)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " LIMIT 4;");
  else if (event == EVENT_NEW_SECINFO)
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " WHERE creation_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " ORDER BY creation_time DESC;");
  else
    init_iterator (&rows,
                   "SELECT uuid, name, title FROM dfn_cert_advs"
                   " WHERE modification_time"
                   "       > coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0)"
                   " AND creation_time"
                   "     <= coalesce (CAST ((SELECT value FROM meta"
                   "                         WHERE name"
                   "                               = 'cert_check_time')"
                   "                        AS INTEGER),"
                   "                  0)"
                   " ORDER BY modification_time DESC;");

  while (next (&rows))
    {
      gchar *url;
      const char *name, *title;

      name = iterator_string (&rows, 1);
      title = iterator_string (&rows, 2);
      if (details_url && strlen (details_url))
        url = alert_url_print (details_url, iterator_string (&rows, 0), type);
      else
        url = NULL;
      g_string_append_printf (buffer,
                              "%-18s  %-s%s%s%s",
                              name,
                              title,
                              url ? "\n  " : "",
                              url ? url : "",
                              url ? "\n\n" : "\n");
      g_free (url);
      count++;
    }
  cleanup_iterator (&rows);

  if (count_return)
    *count_return = count;

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Create message for New NVTs event.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event data.
 * @param[in]  alert       Alert.
 * @param[out] count_return  NULL, or address for row count.
 *
 * @return Freshly allocated list.
 */
static gchar *
new_secinfo_list (event_t event, const void* event_data, alert_t alert,
                  int *count_return)
{
  g_debug ("%s: event_data: %s", __func__, (gchar*) event_data);

  if (strcasecmp (event_data, "nvt_example") == 0)
    return new_nvts_list (event, "nvt", alert, 1, count_return);
  if (strcasecmp (event_data, "nvt") == 0)
    return new_nvts_list (event, "nvt", alert, 0, count_return);

  if (strcasecmp (event_data, "cve_example") == 0)
    return new_cves_list (event, "cve", alert, 1, count_return);
  if (strcasecmp (event_data, "cve") == 0)
    return new_cves_list (event, "cve", alert, 0, count_return);

  if (strcasecmp (event_data, "cpe_example") == 0)
    return new_cpes_list (event, "cpe", alert, 1, count_return);
  if (strcasecmp (event_data, "cpe") == 0)
    return new_cpes_list (event, "cpe", alert, 0, count_return);

  if (strcasecmp (event_data, "cert_bund_adv_example") == 0)
    return new_cert_bunds_list (event, "cert_bund_adv", alert, 1, count_return);
  if (strcasecmp (event_data, "cert_bund_adv") == 0)
    return new_cert_bunds_list (event, "cert_bund_adv", alert, 0, count_return);

  if (strcasecmp (event_data, "dfn_cert_adv_example") == 0)
    return new_dfn_certs_list (event, "dfn_cert_adv", alert, 1, count_return);
  if (strcasecmp (event_data, "dfn_cert_adv") == 0)
    return new_dfn_certs_list (event, "dfn_cert_adv", alert, 0, count_return);

  if (count_return)
    {
      g_warning ("%s: Type error: %s", __func__, (char *) event_data);
      *count_return = 0;
    }

  return g_strdup ("ERROR generating list");
}

/**
 * @brief Create message for New NVTs event.
 *
 * @param[in]  event       Event.
 * @param[in]  event_data  Event type specific details.
 * @param[in]  alert       Alert.
 *
 * @return Freshly allocated message.
 */
static gchar *
new_secinfo_message (event_t event, const void* event_data, alert_t alert)
{
  gchar *type, *list, *message, *name, *point;
  int count, example;

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

  assert (count > 0);

  type = g_strdup (event_data);
  if (type && (point = strstr (type, "_example")))
    {
      example = 1;
      point[0] = '\0';
    }
  else
    example = 0;

  name = alert_name (alert);
  message = g_strdup_printf ("%s%i%s%s%s%s, according to the\nalert \"%s\":\n\n%s",
                             example
                              ? "Warning: This is an example alert only.\n\n"
                              : "",
                             count,
                             event == EVENT_NEW_SECINFO
                              ? " new "
                              : " ",
                             count == 1
                              ? type_name (type)
                              : type_name_plural (type),
                             event == EVENT_NEW_SECINFO
                              ? ""
                              : (count == 1 ? " was" : " were"),
                             event == EVENT_NEW_SECINFO
                              ? " appeared in the feed"
                              : " updated in the feed",
                             name,
                             list);
  free (name);
  g_free (list);
  return message;
}

/**
 * @brief Check for updated SCAP SecInfo after an update.
 */
static void
check_for_updated_scap ()
{
  if (manage_scap_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM scap.cves"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (EVENT_UPDATED_SECINFO, "cve", 0, 0);

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM scap.cpes"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'scap_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'scap_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (EVENT_UPDATED_SECINFO, "cpe", 0, 0);
    }
}

/**
 * @brief Check for updated CERT SecInfo after an update.
 */
static void
check_for_updated_cert ()
{
  if (manage_cert_loaded ())
    {
      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert.cert_bund_advs"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (EVENT_UPDATED_SECINFO, "cert_bund_adv", 0, 0);

      if (sql_int ("SELECT EXISTS"
                   " (SELECT * FROM cert.dfn_cert_advs"
                   "  WHERE modification_time"
                   "        > coalesce (CAST ((SELECT value FROM meta"
                   "                           WHERE name"
                   "                                 = 'cert_check_time')"
                   "                          AS INTEGER),"
                   "                    0)"
                   "  AND creation_time"
                   "      <= coalesce (CAST ((SELECT value FROM meta"
                   "                          WHERE name"
                   "                                = 'cert_check_time')"
                   "                         AS INTEGER),"
                   "                   0));"))
        event (EVENT_UPDATED_SECINFO, "dfn_cert_adv", 0, 0);
    }
}


/* Credentials. */

/**
 * @brief Ensure that there is an encryption key.
 *
 * This prevents contention problems that can happen when the key is
 * created on the fly during 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 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]  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,
 *         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* 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;

  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"))
        {
          sql_rollback ();
          return 4;
        }
      else
        quoted_type = g_strdup (given_type);
    }
  else if (community
           || (login && given_password && auth_algorithm))
    quoted_type = g_strdup ("snmp");
  else if (certificate && key_private)
    quoted_type = g_strdup ("cc");
  else if (login && key_private)
    quoted_type = g_strdup ("usk");
  else if (login && given_password)
    quoted_type = g_strdup ("up");
  else if (login && key_private == NULL && given_password == NULL)
    quoted_type = g_strdup ("usk"); /* auto-generate */
  else
    {
      g_warning ("%s: Cannot determine type of new credential", __func__);
      return 18;
    }

  /* Validate credential data */
  auto_generate = ((given_password == NULL) && (key_private == NULL)
                   && (key_public == NULL) && (certificate == NULL)
                   && (community == NULL));
  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))
    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"))
    ret = 5;
  else if (given_password == NULL && auto_generate == 0
           && (strcmp (quoted_type, "up") == 0
               || strcmp (quoted_type, "pw") == 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, "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;
    }

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

  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
        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);
          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]   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
 *         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* allow_insecure)
{
  credential_t credential;
  iterator_t iterator;
  int ret;

  if (credential_id == NULL)
    return 3;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

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

  credential = 0;
  if (find_credential_with_permission (credential_id, &credential,
                                       "modify_credential"))
    {
      sql_rollback ();
      return -1;
    }

  if (credential == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* Check whether a credential with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "credential", credential))
        {
          sql_rollback ();
          return 2;
        }
    }

  /* Update values */

  if (name)
    set_credential_name (credential, name);

  if (comment)
    set_credential_comment (credential, comment);

  if (allow_insecure)
    {
      if (strcmp (allow_insecure, "") && strcmp (allow_insecure, "0"))
        sql ("UPDATE credentials SET allow_insecure = 1 WHERE id = %llu;",
             credential);
      else
        sql ("UPDATE credentials SET allow_insecure = 0 WHERE id = %llu;",
             credential);
    }

  ret = 0;

  if (login && ret == 0)
    {
      /*
       * 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)
    {
      // 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 (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;

          if (strcmp (type, "cc") == 0)
            {
              generated_key_public
                  = gvm_ssh_public_from_private
                              (key_private_to_use,
                               NULL);
            }
          else if (strcmp (type, "usk") == 0)
            {
              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 = NULL;

      if (strcmp (type, "cc") == 0)
        {
          if (key_private_to_use)
            set_credential_private_key (credential,
                                        key_private_to_use,
                                        NULL);
        }
      else if (strcmp (type, "up") == 0
               || strcmp (type, "pw") == 0)
        {
          if (password)
            set_credential_password (credential, password);
        }
      else if (strcmp (type, "usk") == 0)
        {
          if (key_private_to_use || password)
            {
              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)
        {
          if (privacy_password)
            {
              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 must be empty if algorithm is empty
              if (strcmp (used_algorithm, "") == 0
                  && strcmp (privacy_password, ""))
                {
                  sql_rollback ();
                  cleanup_iterator (&iterator);
                  return 10;
                }
            }

          if (community || password || privacy_password)
            {
              set_credential_snmp_secret
                (credential,
                 community
                  ? community
                  : credential_iterator_community (&iterator),
                 password
                  ? password
                  : credential_iterator_password (&iterator),
                 privacy_password
                  ? privacy_password
                  : credential_iterator_privacy_password (&iterator));
            }
        }
      else if (strcmp (type, "pgp") == 0
               || strcmp (type, "smime") == 0)
        {
          set_credential_data (credential, "secret", "");
        }
      else
        {
          g_warning ("%s: Unknown credential type: %s", __func__, type);
          sql_rollback ();
          cleanup_iterator (&iterator);
          g_free (key_private_truncated);
          return -1;
        }

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

      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.
 */
#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 },                                                   \
   /* private data */                                                         \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'secret')",               \
     "secret",                                                                \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'password')",             \
     "password",                                                              \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'private_key')",          \
     "private_key",                                                           \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'community')",            \
     "community",                                                             \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_data"                                    \
     " WHERE credential = credentials.id AND type = 'privacy_password')",     \
     "privacy_password",                                                      \
     KEYWORD_TYPE_STRING },                                                   \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief LSC Credential iterator columns for trash case.
 */
#define CREDENTIAL_ITERATOR_TRASH_COLUMNS                                     \
 {                                                                            \
   GET_ITERATOR_COLUMNS (credentials_trash),                                  \
   /* public generic data */                                                  \
   { "type", NULL, KEYWORD_TYPE_STRING },                                     \
   { "allow_insecure", NULL, KEYWORD_TYPE_INTEGER },                          \
   /* public type specific data */                                            \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'username')",       \
     "login",                                                                 \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id AND type = 'certificate')",    \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id"                               \
     "   AND type = 'auth_algorithm')",                                       \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(SELECT value FROM credentials_trash_data"                              \
     " WHERE credential = credentials_trash.id"                               \
     "   AND type = 'privacy_algorithm')",                                    \
     NULL,                                                                    \
     KEYWORD_TYPE_STRING },                                                   \
   { "(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)
           || 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)
           || 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)
    {
      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,
                                          "private_key", private_key,
                                          "password", passphrase,
                                          NULL);
      if (!encrypted_blob)
        {
          g_critical ("%s: encryption failed", G_STRFUNC);
          lsc_crypt_release (crypt_ctx);
          return;
        }
      set_credential_data (credential, "secret", encrypted_blob);
      set_credential_data (credential, "password", NULL);
      set_credential_data (credential, "private_key", NULL);
      g_free (encrypted_blob);
    }
  else
    {
      crypt_ctx = NULL;
      set_credential_data (credential, "secret", NULL);
      set_credential_data (credential, "password", passphrase);
      set_credential_data (credential, "private_key", private_key);
    }

  sql ("UPDATE credentials SET modification_time = m_now ()"
       " WHERE id = %llu;",
       credential);
  lsc_crypt_release (crypt_ctx);
}

/**
 * @brief Set the 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 + 7);
  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 + 8);
  else if (strcmp (type, "private_key") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9);
  else if (strcmp (type, "community") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10);
  else if (strcmp (type, "privacy_password") == 0)
    unencrypted  = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11);
  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 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);
}

/**
 * @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, "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, "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, "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, "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, "High") && strcmp (threat, "Medium")
      && strcmp (threat, "Low") && strcmp (threat, "Log")
      && strcmp (threat, "Alarm") && strcmp (threat, ""))
    return -1;

  if (new_threat && strcmp (new_threat, "High") && strcmp (new_threat, "Medium")
      && strcmp (new_threat, "Low") && strcmp (new_threat, "Log")
      && strcmp (new_threat, "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, "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, "High") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "Medium") == 0)
        new_severity_dbl = 5.0;
      else if (strcmp (new_threat, "Low") == 0)
        new_severity_dbl = 2.0;
      else if (strcmp (new_threat, "Log") == 0)
        new_severity_dbl = SEVERITY_LOG;
      else
        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, "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, "High") == 0)
        new_severity_dbl = 10.0;
      else if (strcmp (new_threat, "Medium") == 0)
        new_severity_dbl = 5.0;
      else if (strcmp (new_threat, "Low") == 0)
        new_severity_dbl = 2.0;
      else if (strcmp (new_threat, "Log") == 0)
        new_severity_dbl = SEVERITY_LOG;
      else
        {
          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.
 *
 * @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_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)
{
  int ret;
  char *ca_pub, *key_pub, *key_priv;
  GError *error = NULL;
  credential_t new_credential;
  gchar *used_credential_id;
  gchar *name_for_credential;
  scanner_t scanner;

  g_info ("   Creating scanner.");

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

  current_credentials.uuid = "";

  if (!g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error))
    {
      fprintf (stderr, "%s.\n", error->message);
      g_error_free (error);
      manage_option_cleanup ();
      return -1;
    }

  if (credential_id)
    {
      key_pub = NULL;
      key_priv = NULL;
      used_credential_id = g_strdup (credential_id);
    }
  else
    {
      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);
    }
  ret = create_scanner (name, NULL, host, port, type, &scanner, ca_pub,
                        used_credential_id);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  g_free (used_credential_id);
  switch (ret)
    {
      case 0:
        {
          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 1:
        fprintf (stderr, "Scanner exists already.\n");
        break;
      case 2:
        fprintf (stderr, "Invalid value provided.\n");
        break;
      case 3:
        fprintf (stderr, "Credential not found.\n");
        break;
      case 4:
        fprintf (stderr, "Credential should be 'up'.\n");
        break;
      case 5:
        fprintf (stderr, "Credential should be 'cc'.\n");
        break;
      case 6:
        fprintf (stderr, "Credential required.\n");
        break;
      default:
        fprintf (stderr, "Failed to create scanner.\n");
        break;
    }

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Delete the given scanner.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  uuid        UUID of scanner.
 *
 * @return 0 success, 2 failed to find scanner, 3 scanner can't be deleted,
 *         -1 error.  -2 database is 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);
  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.
 *
 * @return 0 success, , 1 failed to find scanner, 2 scanner with new name
 *         exists, 3 scanner_id required, 4 invalid value, 99 permission
 *         denied, -1 error, -2 database is too old, -3 database needs
 *         to be initialised from server, -5 database is too new.
 */
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)
{
  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);
  if (ret)
    return ret;

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

  if (name)
    name_for_credential = sql_quote (name);
  else
    {
      gchar *current_scanner_name = scanner_name (scanner);
      name_for_credential = sql_quote (current_scanner_name);
      g_free (current_scanner_name);
    }

  if (ca_pub_path)
    {
      if (*ca_pub_path == '\0')
        ca_pub = g_strdup ("");
      else if (g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error) == 0)
        {
          fprintf (stderr, "%s.\n", error->message);
          g_error_free (error);
          manage_option_cleanup ();
          return -1;
        }
    }
  else
    ca_pub = NULL;

  if (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);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  g_free (used_credential_id);
  switch (ret)
    {
      case 0:
        printf ("Scanner modified.\n");
        break;
      case 2:
        fprintf (stderr, "Scanner with new name exists already.\n");
        break;
      case 3:
        fprintf (stderr, "Scanner ID required.\n");
        break;
      case 4:
        fprintf (stderr, "Invalid value.\n");
        break;
      case 5:
        fprintf (stderr, "Credential not found.\n");
        break;
      case 6:
        fprintf (stderr, "Credential should be 'cc'.\n");
        break;
      case 7:
        fprintf (stderr, "Credential should be 'up'.\n");
        break;
      case 8:
        fprintf (stderr, "Credential missing.\n");
        break;
      case 99:
        fprintf (stderr, "Permission denied.\n");
        break;
      default:
        fprintf (stderr, "Failed to modify scanner.\n");
        break;
    }

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Verify the given scanner.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  uuid        UUID of scanner.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to verify scanner,
 *         -1 error, -2 database is too old, -3 database needs to be
 *         initialised from server, -5 database is too new.
 */
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);
  if (ret)
    return ret;

  current_credentials.uuid = "";
  switch ((ret = verify_scanner (uuid, &version)))
    {
      case 0:
        printf ("Scanner version: %s.\n", version);
        g_free (version);
        break;
      case 1:
        fprintf (stderr, "Failed to find scanner.\n");
        break;
      case 2:
        fprintf (stderr, "Failed to verify scanner.\n");
        break;
      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;
}

/**
 * @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 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[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,
                scanner_t *new_scanner)
{
  char *quoted_name, *quoted_comment, *quoted_host, *quoted_ca_pub;

  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 ?: "");

  sql ("INSERT INTO scanners (uuid, name, owner, comment, host, port, type,"
       "                      ca_pub,creation_time, modification_time)"
       " VALUES (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', '%s', %d, %d, %s%s%s, m_now (), m_now ());",
       quoted_name, current_credentials.uuid, quoted_comment, quoted_host,
       iport, itype,
       ca_pub ? "'" : "",
       ca_pub ? quoted_ca_pub : "NULL",
       ca_pub ? "'" : "");

  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_ca_pub);

  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.
 *
 * @return 0 success, 1 scanner exists already, 2 Invalid value,
 *         3 credential not found, 4 credential should be 'up',
 *         5 credential should be 'cc', 6 credential required,
 *         99 permission denied.
 */
int
create_scanner (const char* name, const char *comment, const char *host,
                const char *port, const char *type, scanner_t *new_scanner,
                const char *ca_pub, const char *credential_id)
{
  int iport, itype, unix_socket = 0;
  credential_t credential;

  assert (name);

  sql_begin_immediate ();

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

  if (!host || !port || !type)
    return 2;
  if (*host == '/')
    {
      unix_socket = 1;
      ca_pub = NULL;
    }
  iport = atoi (port);
  itype = atoi (type);
  if (iport <= 0 || iport > 65535)
    return 2;
  if (scanner_type_valid (itype) == 0)
    return 2;
  /* XXX: Workaround for unix socket case. */
  if (gvm_get_host_type (host) == -1 && !unix_socket)
    return 2;
  if (resource_with_name_exists (name, "scanner", 0))
    {
      sql_rollback ();
      return 1;
    }

  if (unix_socket)
    insert_scanner (name, comment, host, ca_pub, iport, itype, 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 -1;
            }
          if (credential == 0)
            {
              sql_rollback ();
              return 3;
            }
          if (sql_int ("SELECT type != 'cc' FROM credentials"
                       " WHERE id = %llu;",
                       credential))
            {
              sql_rollback ();
              return 5;
            }
        }

      insert_scanner (name, comment, host, ca_pub, iport, itype, new_scanner);

      if (credential)
        {
          sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;",
              credential, sql_last_insert_id ());
        }
    }

  sql_commit ();
  return 0;
}

/**
 * @brief Create a scanner from an existing scanner.
 *
 * @param[in]  name         Name of new scanner. NULL to copy from existing.
 * @param[in]  comment      Comment on new scanner. NULL to copy from
 *                          existing.
 * @param[in]  scanner_id   UUID of existing scanner.
 * @param[out] new_scanner  New scanner.
 *
 * @return 0 success, 1 scanner exists already, 2 failed to find existing
 *         scanner, -1 error, 98 not allowed to copy cve scanner,
 *         99 permission denied.
 */
int
copy_scanner (const char* name, const char* comment, const char *scanner_id,
              scanner_t* new_scanner)
{
  if (strcmp (scanner_id, SCANNER_UUID_CVE) == 0)
    return 98;

  return copy_resource ("scanner", name, comment, scanner_id,
                        "host, port, type, ca_pub, credential", 1,
                        new_scanner, NULL);
}

/**
 * @brief Modify an scanner.
 *
 * @param[in]   scanner_id  UUID of scanner.
 * @param[in]   name        Name of scanner.
 * @param[in]   comment     Comment on scanner.
 * @param[in]   host        Host of scanner.
 * @param[in]   port        Port of scanner.
 * @param[in]   type        Type of scanner.
 * @param[in]   ca_pub      CA Certificate of scanner, or "" for default, or
 *                          to keep existing value.
 * @param[in]   credential_id  UUID of credential or NULL.
 *
 * @return 0 success, 1 failed to find scanner, 2 scanner with new name exists,
 *         3 scanner_id required, 4 invalid value, 5 credential not found,
 *         6 credential should be 'cc', 7 credential should be 'up',
 *         8 credential missing, 99 permission denied, -1 internal error.
 */
int
modify_scanner (const char *scanner_id, const char *name, const char *comment,
                const char *host, const char *port, const char *type,
                const char *ca_pub, const char *credential_id)
{
  gchar *quoted_name, *quoted_comment, *quoted_host, *new_port, *new_type;
  scanner_t scanner = 0;
  credential_t credential = 0;
  int iport, itype, unix_socket, credential_given;

  assert (current_credentials.uuid);

  if (scanner_id == NULL)
    return 3;

  if (port)
    {
      iport = atoi (port);
      if (iport <= 0 || iport > 65535)
        return 4;
    }
  else
    /* Keep compiler quiet. */
    iport = 0;

  if (type)
    {
      itype = atoi (type);
      if (scanner_type_valid (itype) == 0)
        return 4;
    }
  else
    itype = 0;

  sql_begin_immediate ();

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

  if (find_scanner_with_permission (scanner_id, &scanner, "modify_scanner"))
    {
      sql_rollback ();
      return -1;
    }
  if (scanner == 0)
    {
      sql_rollback ();
      return 1;
    }

  if (host)
    {
      unix_socket = (*host == '/');
      if ((unix_socket == 0) && (gvm_get_host_type (host) == -1))
        return 4;
    }
  else
    {
      char *old_host = scanner_host (scanner);
      unix_socket = (*old_host == '/');
      g_free (old_host);
    }

  if (itype == 0)
    itype = sql_int ("SELECT type FROM scanners WHERE id = %llu;", scanner);

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

      if (credential == 0)
        {
          sql_rollback ();
          return 5;
        }

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

  /* Check whether a scanner with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "scanner", scanner))
        {
          sql_rollback ();
          return 2;
        }
    }

  quoted_name = name ? sql_quote (name) : NULL;
  quoted_comment = sql_quote (comment ?: "");
  quoted_host = host ? sql_quote (host) : NULL;
  new_port = port ? g_strdup_printf ("%d", iport) : g_strdup ("port");
  new_type = type ? g_strdup_printf ("%d", itype) : g_strdup ("type");
  sql ("UPDATE scanners SET name = %s%s%s, comment = %s%s%s, host = %s%s%s,"
       " port = %s, type = %s, modification_time = m_now () WHERE id = %llu;",
       quoted_name ? "'" : "",
       quoted_name ? quoted_name : "name",
       quoted_name ? "'" : "",
       quoted_comment ? "'" : "",
       quoted_comment ? quoted_comment : "comment",
       quoted_comment ? "'" : "",
       quoted_host ? "'" : "",
       quoted_host ? quoted_host : "host",
       quoted_host ? "'" : "",
       new_port,
       new_type,
       scanner);
  g_free (new_type);
  g_free (new_port);
  g_free (quoted_host);
  g_free (quoted_comment);
  g_free (quoted_name);

  if (ca_pub && !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 (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);
    }

  sql ("DELETE FROM scanners WHERE id = %llu;", scanner);
  sql_commit ();
  return 0;
}

/**
 * @brief Filter columns for scanner iterator.
 */
#define SCANNER_ITERATOR_FILTER_COLUMNS                              \
 { GET_ITERATOR_FILTER_COLUMNS, "host", "port", "type", NULL }

/**
 * @brief Scanner iterator columns.
 */
#define SCANNER_ITERATOR_COLUMNS                                     \
 {                                                                   \
   GET_ITERATOR_COLUMNS (scanners),                                  \
   { "host", NULL, KEYWORD_TYPE_STRING },                            \
   { "port", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "ca_pub", NULL, KEYWORD_TYPE_STRING },                          \
   {                                                                 \
     "(SELECT name FROM credentials WHERE id = credential)",         \
     "credential",                                                   \
     KEYWORD_TYPE_STRING                                             \
   },                                                                \
   { "credential", NULL, KEYWORD_TYPE_INTEGER },                     \
   { "0", NULL, KEYWORD_TYPE_INTEGER },                              \
   { "credential_value (credential, 0, CAST ('certificate' AS TEXT))", \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   { "credential_value (credential, 0, CAST ('private_key' AS TEXT))", \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   { "credential_value (credential, 0, CAST ('secret' AS TEXT))",      \
     NULL,                                                             \
     KEYWORD_TYPE_STRING },                                            \
   {                                                                   \
     "(SELECT type FROM credentials WHERE id = credential)",           \
     "credential_type",                                                \
     KEYWORD_TYPE_STRING                                               \
   },                                                                  \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                \
 }

/**
 * @brief Scanner iterator columns for trash case.
 */
#define SCANNER_ITERATOR_TRASH_COLUMNS                               \
 {                                                                   \
   GET_ITERATOR_COLUMNS (scanners_trash),                            \
   { "host" , NULL, KEYWORD_TYPE_STRING },                           \
   { "port" , NULL, KEYWORD_TYPE_INTEGER },                          \
   { "type", NULL, KEYWORD_TYPE_INTEGER },                           \
   { "ca_pub", NULL, KEYWORD_TYPE_STRING },                          \
   {                                                                    \
     "(SELECT CASE"                                                     \
     " WHEN credential_location = " G_STRINGIFY (LOCATION_TABLE)        \
     " THEN (SELECT name FROM credentials WHERE id = credential)"       \
     " ELSE (SELECT name FROM credentials_trash WHERE id = credential)" \
     " END)",                                                           \
     "credential",                                                      \
     KEYWORD_TYPE_STRING                                                \
   },                                                                   \
   { "credential", NULL, KEYWORD_TYPE_INTEGER },                        \
   { "credential_location", NULL, KEYWORD_TYPE_INTEGER },               \
   { "credential_value (credential, 1, CAST ('certificate' AS TEXT))",  \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { "credential_value (credential, 1, CAST ('private_key' AS TEXT))",  \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { "credential_value (credential, 1, CAST ('secret' AS TEXT))",       \
     NULL,                                                              \
     KEYWORD_TYPE_STRING },                                             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                 \
 }

/**
 * @brief Initialise an scanner iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to find filter, -1 error.
 */
int
init_scanner_iterator (iterator_t* iterator, 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.
 */
static 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 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, %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 read permission status from a GET iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if may read, else 0.
 */
int
scanner_task_iterator_readable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, 3);
}

/**
 * @brief Check whether an scanner is in use.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
scanner_in_use (scanner_t scanner)
{
  return !!(sql_int ("SELECT count(*) FROM tasks WHERE scanner = %llu"
                     " AND hidden = 0;", scanner));
}

/**
 * @brief Check whether a trashcan scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
trash_scanner_in_use (scanner_t scanner)
{
  return !!(sql_int ("SELECT count(*) FROM tasks"
                     " WHERE scanner = %llu"
                     " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH),
                     scanner));
}

/**
 * @brief Check whether a scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
scanner_writable (scanner_t scanner)
{
  return 1;
}

/**
 * @brief Check whether a trashcan scanner is writable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 yes, 0 no.
 */
int
trash_scanner_writable (scanner_t scanner)
{
  return trash_scanner_in_use (scanner) == 0;
}

/**
 * @brief Return whether a trashcan scanner is readable.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return 1 if readable, else 0.
 */
int
trash_scanner_readable (scanner_t scanner)
{
  char *uuid;
  scanner_t found = 0;

  if (scanner == 0)
    return 0;
  uuid = scanner_uuid (scanner);
  if (find_trash ("scanner", uuid, &found))
    {
      g_free (uuid);
      return 0;
    }
  g_free (uuid);
  return found > 0;
}

/**
 * @brief Return the name of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated name if available, else NULL.
 */
char*
scanner_name (scanner_t scanner)
{
  return sql_string ("SELECT name FROM scanners WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Return the UUID of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated UUID.
 */
char *
scanner_uuid (scanner_t scanner)
{
  return sql_string ("SELECT uuid FROM scanners WHERE id = %llu;",
                     scanner);
}

/**
 * @brief Return the UUID of the default scanner.
 *
 * @return UUID.
 */
const char *
scanner_uuid_default ()
{
  return SCANNER_UUID_DEFAULT;
}

/**
 * @brief Return the host of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated host.
 */
char *
scanner_host (scanner_t scanner)
{
  return sql_string ("SELECT host FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Return the port of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Scanner port, -1 if not found;
 */
int
scanner_port (scanner_t scanner)
{
  int port;
  char *str;
  str = sql_string ("SELECT port FROM scanners WHERE id = %llu;", scanner);
  if (!str)
    return -1;
  port = atoi (str);
  g_free (str);
  return port;
}

/**
 * @brief Return the type of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Scanner type, -1 if not found;
 */
int
scanner_type (scanner_t scanner)
{
  int type;
  char *str;
  str = sql_string ("SELECT type FROM scanners WHERE id = %llu;", scanner);
  if (!str)
    return -1;
  type = atoi (str);
  g_free (str);
  return type;
}

/**
 * @brief Return the CA Certificate of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated CA Certificate.
 */
char *
scanner_ca_pub (scanner_t scanner)
{
  return sql_string ("SELECT ca_pub FROM scanners WHERE id = %llu;", scanner);
}

/**
 * @brief Return the Certificate of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated Certificate.
 */
char *
scanner_key_pub (scanner_t scanner)
{
  if (scanner == 0)
    return NULL;

  return sql_string ("SELECT value FROM credentials_data"
                     " WHERE credential = (SELECT credential FROM scanners"
                     "                     WHERE id = %llu)"
                     "   AND type = 'certificate';",
                     scanner);
}

/**
 * @brief Return the private key of a scanner.
 *
 * @param[in]  scanner  Scanner.
 *
 * @return Newly allocated private key.
 */
char *
scanner_key_priv (scanner_t scanner)
{
  gchar *key;

  key = sql_string ("SELECT value FROM credentials_data"
                    " WHERE credential = (SELECT credential FROM scanners"
                    "                     WHERE id = %llu)"
                    "   AND type = 'private_key';",
                    scanner);

  if (key == NULL)
    {
      gchar *secret;
      lsc_crypt_ctx_t crypt_ctx;
      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 Create a new connection to an OSP scanner relay.
 *
 * @param[in]   host     Original host name or IP address.
 * @param[in]   port     Original port.
 * @param[in]   ca_pub   Original CA certificate.
 * @param[in]   key_pub  Public key for authentication.
 * @param[in]   key_priv Private key for authentication.
 *
 * @return New connection if success, NULL otherwise.
 */
static osp_connection_t *
osp_scanner_relay_connect (const char *host, int port, const char *ca_pub,
                           const char *key_pub, const char *key_priv)
{
  int ret, new_port;
  gchar *new_host, *new_ca_pub;
  osp_connection_t *connection;

  new_host = NULL;
  new_ca_pub = NULL;
  new_port = 0;

  ret = slave_get_relay (host,
                         port,
                         ca_pub,
                         "OSP",
                         &new_host,
                         &new_port,
                         &new_ca_pub);

  switch (ret)
    {
      case 0:
        break;
      case 1:
        g_warning ("No relay found for Scanner at %s:%d", host, port);
        return NULL;
      default:
        g_warning ("%s: Error getting relay for Scanner at %s:%d",
                   __func__, host, port);
        return NULL;
    }

  connection
    = osp_connection_new (new_host, new_port, new_ca_pub, key_pub, key_priv);

  if (connection == NULL)
    {
      if (new_port)
        g_warning ("Could not connect to relay at %s:%d"
                    " for Scanner at %s:%d",
                    new_host, new_port, host, port);
      else
        g_warning ("Could not connect to relay at %s"
                    " for Scanner at %s:%d",
                    new_host, host, port);
    }

  g_free (new_host);
  g_free (new_ca_pub);

  return connection;
}

/**
 * @brief Create a new connection to an OSP scanner using the scanner data.
 *
 * @param[in]   host     Host name or IP address.
 * @param[in]   port     Port.
 * @param[in]   ca_pub   CA certificate.
 * @param[in]   key_pub  Public key.
 * @param[in]   key_priv Private key.
 *
 * @return New connection if success, NULL otherwise.
 */
osp_connection_t *
osp_connect_with_data (const char *host,
                       int port,
                       const char *ca_pub,
                       const char *key_pub,
                       const char *key_priv)
{
  osp_connection_t *connection;
  int is_unix_socket = (host && *host == '/') ? 1 : 0;

  if (is_unix_socket == 0
      && get_relay_mapper_path ())
    {
      connection
        = osp_scanner_relay_connect (host, port, ca_pub, key_pub, key_priv);
    }
  else
    {
      connection = osp_connection_new (host, port, ca_pub, key_pub, key_priv);

      if (connection == NULL)
        {
          if (is_unix_socket)
            g_warning ("Could not connect to Scanner at %s", host);
          else
            g_warning ("Could not connect to Scanner at %s:%d", host, port);
        }
    }
  return connection;
}

/**
 * @brief Create a new connection to an OSP scanner.
 *
 * @param[in]   scanner     Scanner.
 *
 * @return New connection if success, NULL otherwise.
 */
osp_connection_t *
osp_scanner_connect (scanner_t scanner)
{
  int port;
  osp_connection_t *connection;
  char *host, *ca_pub, *key_pub, *key_priv;

  assert (scanner);
  host = scanner_host (scanner);
  if (host && *host == '/')
    {
      port = 0;
      ca_pub = NULL;
      key_pub = NULL;
      key_priv = NULL;
    }
  else
    {
      port = scanner_port (scanner);
      ca_pub = scanner_ca_pub (scanner);
      key_pub = scanner_key_pub (scanner);
      key_priv = scanner_key_priv (scanner);
    }

  connection = osp_connect_with_data (host, port, ca_pub, key_pub, key_priv);

  g_free (host);
  g_free (ca_pub);
  g_free (key_pub);
  g_free (key_priv);
  return connection;
}

/**
 * @brief Get an OSP Scanner's get_version info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  s_name      Scanner name.
 * @param[out]  s_ver       Scanner version.
 * @param[out]  d_name      Daemon name.
 * @param[out]  d_ver       Daemon version.
 * @param[out]  p_name      Protocol name.
 * @param[out]  p_ver       Protocol version.
 *
 * @return 0 success, 1 for failure.
 */
int
osp_get_version_from_iterator (iterator_t *iterator, char **s_name,
                               char **s_ver, char **d_name, char **d_ver,
                               char **p_name, char **p_ver)
{
  osp_connection_t *connection;

  assert (iterator);
  connection = osp_connect_with_data (scanner_iterator_host (iterator),
                                      scanner_iterator_port (iterator),
                                      scanner_iterator_ca_pub (iterator),
                                      scanner_iterator_key_pub (iterator),
                                      scanner_iterator_key_priv (iterator));
  if (!connection)
    return 1;
  if (osp_get_version (connection, s_name, s_ver, d_name, d_ver, p_name, p_ver))
    return 1;
  osp_connection_close (connection);
  return 0;
}

/**
 * @brief Get an OSP Scanner's get_scanner_details info.
 *
 * @param[in]   iterator    Scanner object iterator.
 * @param[out]  desc        Scanner description.
 * @param[out]  params      Scanner parameters.
 *
 * @return 0 success, 1 for failure.
 */
int
osp_get_details_from_iterator (iterator_t *iterator, char **desc,
                               GSList **params)
{
  osp_connection_t *connection;

  assert (iterator);
  connection = osp_connect_with_data (scanner_iterator_host (iterator),
                                      scanner_iterator_port (iterator),
                                      scanner_iterator_ca_pub (iterator),
                                      scanner_iterator_key_pub (iterator),
                                      scanner_iterator_key_priv (iterator));
  if (!connection)
    return 1;
  if (osp_get_scanner_details (connection, desc, params))
    return 1;
  osp_connection_close (connection);
  return 0;
}

/**
 * @brief Verify a scanner.
 *
 * @param[in]   scanner_id  Scanner UUID.
 * @param[out]  version     Version returned by the scanner.
 *
 * @return 0 success, 1 failed to find scanner, 2 failed to get version,
 *         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_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);
  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;
          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;
}


/* Groups. */

/**
 * @brief Find a group for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of group.
 * @param[out]  group       Group return, 0 if successfully failed to find group.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find group), TRUE on error.
 */
static gboolean
find_group_with_permission (const char* uuid, group_t* group,
                            const char *permission)
{
  return find_resource_with_permission ("group", uuid, group, permission, 0);
}

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

  sql_begin_immediate ();

  ret = copy_resource_lock ("group", name, comment, group_id, NULL, 1, &new,
                            &old);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  sql ("INSERT INTO group_users (\"group\", \"user\")"
       " SELECT %llu, \"user\" FROM group_users"
       " WHERE \"group\" = %llu;",
       new,
       old);

  sql_commit ();
  if (new_group_return)
    *new_group_return = new;
  return 0;
}

/**
 * @brief Add users to a group.
 *
 * Caller must take care of transaction.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Group or role.
 * @param[in]  users     List of users.
 *
 * @return 0 success, 2 failed to find user, 4 user name validation failed,
 *         99 permission denied, -1 error.
 */
static int
add_users (const gchar *type, resource_t resource, const char *users)
{
  if (users)
    {
      gchar **split, **point;
      GList *added;

      /* Add each user. */

      added = NULL;
      split = g_strsplit_set (users, " ,", 0);
      point = split;

      while (*point)
        {
          user_t user;
          gchar *name;

          name = *point;

          g_strstrip (name);

          if (strcmp (name, "") == 0)
            {
              point++;
              continue;
            }

          if (g_list_find_custom (added, name, (GCompareFunc) strcmp))
            {
              point++;
              continue;
            }

          added = g_list_prepend (added, name);

          if (user_exists (name) == 0)
            {
              g_list_free (added);
              g_strfreev (split);
              return 2;
            }

          if (find_user_by_name (name, &user))
            {
              g_list_free (added);
              g_strfreev (split);
              return -1;
            }

          if (user == 0)
            {
              gchar *uuid;

              if (validate_username (name))
                {
                  g_list_free (added);
                  g_strfreev (split);
                  return 4;
                }

              uuid = user_uuid_any_method (name);

              if (uuid == NULL)
                {
                  g_list_free (added);
                  g_strfreev (split);
                  return -1;
                }

              if (sql_int ("SELECT count(*) FROM users WHERE uuid = '%s';",
                           uuid)
                  == 0)
                {
                  gchar *quoted_name;
                  quoted_name = sql_quote (name);
                  sql ("INSERT INTO users"
                       " (uuid, name, creation_time, modification_time)"
                       " VALUES"
                       " ('%s', '%s', m_now (), m_now ());",
                       uuid,
                       quoted_name);
                  g_free (quoted_name);

                  user = sql_last_insert_id ();
                }
              else
                {
                  /* find_user_by_name should have found it. */
                  assert (0);
                  g_free (uuid);
                  g_list_free (added);
                  g_strfreev (split);
                  return -1;
                }

              g_free (uuid);
            }

          if (find_user_by_name_with_permission (name, &user, "get_users"))
            {
              g_list_free (added);
              g_strfreev (split);
              return -1;
            }

          if (user == 0)
            {
              g_list_free (added);
              g_strfreev (split);
              return 99;
            }

          sql ("INSERT INTO %s_users (\"%s\", \"user\") VALUES (%llu, %llu);",
               type,
               type,
               resource,
               user);

          point++;
        }

      g_list_free (added);
      g_strfreev (split);
    }

  return 0;
}

/**
 * @brief Create a group.
 *
 * @param[in]   group_name       Group name.
 * @param[in]   comment          Comment on group.
 * @param[in]   users            Users group applies to.
 * @param[in]   special_full     Whether to give group super on itself (full
 *                               sharing between members).
 * @param[out]  group            Group return.
 *
 * @return 0 success, 1 group exists already, 2 failed to find user, 4 user
 *         name validation failed, 99 permission denied, -1 error.
 */
int
create_group (const char *group_name, const char *comment, const char *users,
              int special_full, group_t* group)
{
  int ret;
  gchar *quoted_group_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (group_name);
  assert (group);

  sql_begin_immediate ();

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

  if (resource_with_name_exists (group_name, "group", 0))
    {
      sql_rollback ();
      return 1;
    }
  quoted_group_name = sql_quote (group_name);
  quoted_comment = comment ? sql_quote (comment) : g_strdup ("");
  sql ("INSERT INTO groups"
       " (uuid, name, owner, comment, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE uuid = '%s'),"
       "  '%s', m_now (), m_now ());",
       quoted_group_name,
       current_credentials.uuid,
       quoted_comment);
  g_free (quoted_comment);
  g_free (quoted_group_name);

  *group = sql_last_insert_id ();
  ret = add_users ("group", *group, users);

  if (ret)
    sql_rollback ();
  else
    {
      if (special_full)
        {
          char *group_id;

          group_id = group_uuid (*group);
          ret = create_permission_internal (1, "Super", NULL, "group", group_id,
                                            "group", group_id, NULL);
          g_free (group_id);
          if (ret)
            {
              sql_rollback ();
              return ret;
            }
        }
      sql_commit ();
    }

  return ret;
}

/**
 * @brief Delete a group.
 *
 * @param[in]  group_id  UUID of group.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a permission refers to the group, 2 failed
 *         to find group, 3 predefined group, 99 permission denied, -1 error.
 */
int
delete_group (const char *group_id, int ultimate)
{
  group_t group = 0;
  GArray *affected_users;
  iterator_t users_iter;

  sql_begin_immediate ();

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

  if (find_group_with_permission (group_id, &group, "delete_group"))
    {
      sql_rollback ();
      return -1;
    }

  if (group == 0)
    {
      if (find_trash ("group", group_id, &group))
        {
          sql_rollback ();
          return -1;
        }
      if (group == 0)
        {
          sql_rollback ();
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql_commit ();
          return 0;
        }

      if (trash_group_in_use (group))
        {
          sql_rollback ();
          return 1;
        }

      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);

      tags_remove_resource ("group", group, LOCATION_TRASH);

      sql ("DELETE FROM group_users_trash WHERE \"group\" = %llu;", group);
      sql ("DELETE FROM groups_trash WHERE id = %llu;", group);
      sql_commit ();
      return 0;
    }

  if (group_in_use (group))
    {
      sql_rollback ();
      return 1;
    }

  if (ultimate == 0)
    {
      group_t trash_group;

      sql ("INSERT INTO groups_trash"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "  modification_time"
           " FROM groups WHERE id = %llu;",
           group);

      trash_group = sql_last_insert_id ();

      sql ("INSERT INTO group_users_trash"
           " (\"group\", \"user\")"
           " SELECT %llu, \"user\""
           " FROM group_users WHERE \"group\" = %llu;",
           trash_group,
           group);

      permissions_set_locations ("group", group, trash_group, LOCATION_TRASH);
      tags_set_locations ("group", group, trash_group, LOCATION_TRASH);
      permissions_set_subjects ("group", group, trash_group, LOCATION_TRASH);
    }
  else
    {
      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'group'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           group);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           group);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'group'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           group);
    }

  tags_remove_resource ("group", group, LOCATION_TABLE);

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

  sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group);
  sql ("DELETE FROM groups WHERE id = %llu;", group);

  cache_all_permissions_for_users (affected_users);
  g_array_free (affected_users, TRUE);

  sql_commit ();
  return 0;
}

/**
 * @brief Return the UUID of a group.
 *
 * @param[in]  group  Group.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
group_uuid (group_t group)
{
  return sql_string ("SELECT uuid FROM groups WHERE id = %llu;",
                     group);
}

/**
 * @brief Gets users of group as a string.
 *
 * @param[in]  group  Group.
 *
 * @return Users.
 */
gchar *
group_users (group_t group)
{
  return sql_string ("SELECT group_concat (name, ', ')"
                     " FROM (SELECT users.name FROM users, group_users"
                     "       WHERE group_users.\"group\" = %llu"
                     "       AND group_users.user = users.id"
                     "       GROUP BY users.name)"
                     "      AS sub;",
                     group);
}

/**
 * @brief Check whether a group is writable.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
group_writable (group_t group)
{
  return 1;
}

/**
 * @brief Check whether a trashcan group is writable.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
trash_group_writable (group_t group)
{
  return 1;
}

/**
 * @brief Check whether a group is in use.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
group_in_use (group_t group)
{
  return 0;
}

/**
 * @brief Check whether a trashcan group is in use.
 *
 * @param[in]  group  Group.
 *
 * @return 1 yes, 0 no.
 */
int
trash_group_in_use (group_t group)
{
  return 0;
}

/**
 * @brief Filter columns for group iterator.
 */
#define GROUP_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, NULL }

/**
 * @brief Group iterator columns.
 */
#define GROUP_ITERATOR_COLUMNS                                                \
 {                                                                            \
   GET_ITERATOR_COLUMNS (groups),                                             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Group iterator columns for trash case.
 */
#define GROUP_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                            \
   GET_ITERATOR_COLUMNS (groups_trash),                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Count number of groups.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of groups in grouped set.
 */
int
group_count (const get_data_t *get)
{
  static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = GROUP_ITERATOR_COLUMNS;
  static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS;
  return count ("group", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a group iterator, including observed groups.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find group, 2 failed to find group (filt_id),
 *         -1 error.
 */
int
init_group_iterator (iterator_t* iterator, get_data_t *get)
{
  static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = GROUP_ITERATOR_COLUMNS;
  static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS;

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

/**
 * @brief Modify a group.
 *
 * @param[in]   group_id       UUID of group.
 * @param[in]   name           Name of group.
 * @param[in]   comment        Comment on group.
 * @param[in]   users          Group users.
 *
 * @return 0 success, 1 failed to find group, 2 failed to find user, 3 group_id
 *         required, 4 user name validation failed, 5 group with new name
 *         exists, 99 permission denied, -1 internal error.
 */
int
modify_group (const char *group_id, const char *name, const char *comment,
              const char *users)
{
  int ret;
  gchar *quoted_name, *quoted_comment;
  group_t group;
  GArray *affected_users;
  iterator_t users_iter;

  assert (current_credentials.uuid);

  if (group_id == NULL)
    return 3;

  sql_begin_immediate ();

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

  group = 0;

  if (find_group_with_permission (group_id, &group, "modify_group"))
    {
      sql_rollback ();
      return -1;
    }

  if (group == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* Check whether a group with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "group", group))
        {
          sql_rollback ();
          return 5;
        }
    }

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

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

  g_free (quoted_comment);
  g_free (quoted_name);

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

  sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group);

  ret = add_users ("group", group, users);

  init_iterator (&users_iter,
                 "SELECT \"user\" FROM group_users"
                 " WHERE \"group\" = %llu",
                 group);

  // users not looked for in this above loop were removed
  //  -> possible permissions change
  while (next (&users_iter))
    {
      int index, found_user;
      user_t user = iterator_int64 (&users_iter, 0);

      found_user = 0;
      for (index = 0; index < affected_users->len && found_user == 0; index++)
        {
          if (g_array_index (affected_users, user_t, index) == user)
            {
              found_user = 1;
              break;
            }
        }

      if (found_user)
        {
          // users found here stay in the group -> no change in permissions
          g_array_remove_index_fast (affected_users, index);
        }
      else
        {
          // user added to group -> possible permissions change
          g_array_append_val (affected_users, user);
        }
    }

  cleanup_iterator (&users_iter);

  cache_all_permissions_for_users (affected_users);

  g_array_free (affected_users, TRUE);

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

  return ret;
}


/* Permissions. */

/**
 * @brief Adjust location of resource in permissions.
 *
 * @param[in]   type  Type.
 * @param[in]   old   Resource ID in old table.
 * @param[in]   new   Resource ID in new table.
 * @param[in]   to    Destination, trash or table.
 */
void
permissions_set_locations (const char *type, resource_t old, resource_t new,
                           int to)
{
  sql ("UPDATE permissions SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
  sql ("UPDATE permissions_trash SET resource_location = %i, resource = %llu"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       to,
       new,
       type,
       old,
       to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE);
}

/**
 * @brief Set permissions to orphan.
 *
 * @param[in]  type      Type.
 * @param[in]  resource  Resource ID.
 * @param[in]  location  Location: table or trash.
 */
void
permissions_set_orphans (const char *type, resource_t resource, int location)
{
  sql ("UPDATE permissions SET resource = -1"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
  sql ("UPDATE permissions_trash SET resource = -1"
       " WHERE resource_type = '%s' AND resource = %llu"
       " AND resource_location = %i;",
       type,
       resource,
       location);
}

/**
 * @brief Adjust subject in permissions.
 *
 * @param[in]   type  Subject type.
 * @param[in]   old   Resource ID in old table.
 * @param[in]   new   Resource ID in new table.
 * @param[in]   to    Destination, trash or table.
 */
static 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 ("(");

  if (roles_str)
    {
      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;
}


/* Roles. */

/**
 * @brief List roles.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  verbose     Whether to print UUID.
 *
 * @return 0 success, -1 error.
 */
int
manage_get_roles (GSList *log_config, const db_conn_info_t *database,
                  int verbose)
{
  iterator_t roles;
  int ret;

  g_info ("   Getting roles.");

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

  init_iterator (&roles, "SELECT name, uuid FROM roles;");
  while (next (&roles))
    if (verbose)
      printf ("%s %s\n", iterator_string (&roles, 0), iterator_string (&roles, 1));
    else
      printf ("%s\n", iterator_string (&roles, 0));

  cleanup_iterator (&roles);

  manage_option_cleanup ();

  return 0;
}

/**
 * @brief Create a role from an existing role.
 *
 * @param[in]  name       Name of new role.  NULL to copy from existing.
 * @param[in]  comment    Comment on new role.  NULL to copy from existing.
 * @param[in]  role_id    UUID of existing role.
 * @param[out] new_role_return  New role.
 *
 * @return 0 success, 1 role exists already, 2 failed to find existing
 *         role, 99 permission denied, -1 error.
 */
int
copy_role (const char *name, const char *comment, const char *role_id,
           role_t *new_role_return)
{
  int ret;
  role_t new_role, old_role;

  sql_begin_immediate ();

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

  if (acl_role_can_super_everyone (role_id))
    return 99;

  ret = copy_resource_lock ("role", name, comment, role_id, NULL, 1, &new_role,
                            &old_role);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  sql ("INSERT INTO permissions"
       " (uuid, owner, name, comment, resource_type, resource_uuid, resource,"
       "  resource_location, subject_type, subject, subject_location,"
       "  creation_time, modification_time)"
       " SELECT make_uuid (),"
       "        (SELECT id FROM users WHERE users.uuid = '%s'),"
       "        name, comment, resource_type,"
       "        resource_uuid, resource, resource_location, subject_type, %llu,"
       "        subject_location, m_now (), m_now ()"
       " FROM permissions"
       " WHERE subject_type = 'role'"
       " AND subject = %llu"
       " AND subject_location = " G_STRINGIFY (LOCATION_TABLE)
       " AND (resource = 0 OR owner IS NULL);",
       current_credentials.uuid,
       new_role,
       old_role);

  sql_commit ();
  if (new_role_return)
    *new_role_return = new_role;
  return 0;
}

/**
 * @brief Create a role.
 *
 * @param[in]   role_name        Role name.
 * @param[in]   comment          Comment on role.
 * @param[in]   users            Users role applies to.
 * @param[in]   role             Role return.
 *
 * @return 0 success, 1 role exists already, 2 failed to find user, 4 user
 *         name validation failed, 99 permission denied, -1 error.
 */
int
create_role (const char *role_name, const char *comment, const char *users,
             role_t* role)
{
  int ret;
  gchar *quoted_role_name, *quoted_comment;

  assert (current_credentials.uuid);
  assert (role_name);
  assert (role);

  sql_begin_immediate ();

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

  if (resource_with_name_exists (role_name, "role", 0))
    {
      sql_rollback ();
      return 1;
    }

  quoted_role_name = sql_quote (role_name);
  quoted_comment = comment ? sql_quote (comment) : g_strdup ("");
  sql ("INSERT INTO roles"
       " (uuid, name, owner, comment, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), '%s',"
       "  (SELECT id FROM users WHERE users.uuid = '%s'),"
       "  '%s', m_now (), m_now ());",
       quoted_role_name,
       current_credentials.uuid,
       quoted_comment);
  g_free (quoted_comment);
  g_free (quoted_role_name);

  *role = sql_last_insert_id ();
  ret = add_users ("role", *role, users);

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

  return ret;
}

/**
 * @brief Return whether a role is predefined.
 *
 * @param[in]  role  Role.
 *
 * @return 1 if predefined, else 0.
 */
static int
role_is_predefined (role_t role)
{
  return sql_int ("SELECT COUNT (*) FROM roles"
                  " WHERE id = %llu"
                  " AND (uuid = '" ROLE_UUID_ADMIN "'"
                  "      OR uuid = '" ROLE_UUID_GUEST "'"
                  "      OR uuid = '" ROLE_UUID_MONITOR "'"
                  "      OR uuid = '" ROLE_UUID_INFO "'"
                  "      OR uuid = '" ROLE_UUID_USER "'"
                  "      OR uuid = '" ROLE_UUID_SUPER_ADMIN "'"
                  "      OR uuid = '" ROLE_UUID_OBSERVER "');",
                  role)
         != 0;
}

/**
 * @brief Return whether a role is predefined.
 *
 * @param[in]  uuid  UUID of role.
 *
 * @return 1 if predefined, else 0.
 */
static int
role_is_predefined_id (const char *uuid)
{
  return uuid && ((strcmp (uuid, ROLE_UUID_ADMIN) == 0)
                  || (strcmp (uuid, ROLE_UUID_GUEST) == 0)
                  || (strcmp (uuid, ROLE_UUID_MONITOR) == 0)
                  || (strcmp (uuid, ROLE_UUID_INFO) == 0)
                  || (strcmp (uuid, ROLE_UUID_USER) == 0)
                  || (strcmp (uuid, ROLE_UUID_SUPER_ADMIN) == 0)
                  || (strcmp (uuid, ROLE_UUID_OBSERVER) == 0));
}

/**
 * @brief Delete a role.
 *
 * @param[in]  role_id   UUID of role.
 * @param[in]  ultimate  Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the role, 2 failed
 *         to find role, 3 predefined role, -1 error.
 */
int
delete_role (const char *role_id, int ultimate)
{
  role_t role = 0;
  GArray *affected_users;
  iterator_t users_iter;

  sql_begin_immediate ();

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

  if (find_role_with_permission (role_id, &role, "delete_role"))
    {
      sql_rollback ();
      return -1;
    }

  if (role == 0)
    {
      if (find_trash ("role", role_id, &role))
        {
          sql_rollback ();
          return -1;
        }
      if (role == 0)
        {
          sql_rollback ();
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql_commit ();
          return 0;
        }

      if (trash_role_in_use (role))
        {
          sql_rollback ();
          return 1;
        }

      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);

      tags_remove_resource ("role", role, LOCATION_TRASH);

      sql ("DELETE FROM role_users_trash WHERE role = %llu;", role);
      sql ("DELETE FROM roles_trash WHERE id = %llu;", role);
      sql_commit ();
      return 0;
    }

  if (role_is_predefined (role))
    {
      sql_rollback ();
      return 3;
    }

  if (role_in_use (role))
    {
      sql_rollback ();
      return 1;
    }

  if (ultimate == 0)
    {
      role_t trash_role;

      sql ("INSERT INTO roles_trash"
           " (uuid, owner, name, comment, creation_time, modification_time)"
           " SELECT uuid, owner, name, comment, creation_time,"
           "        modification_time"
           " FROM roles WHERE id = %llu;",
           role);

      trash_role = sql_last_insert_id ();

      sql ("INSERT INTO role_users_trash"
           " (\"role\", \"user\")"
           " SELECT %llu, \"user\""
           " FROM role_users WHERE \"role\" = %llu;",
           trash_role,
           role);

      permissions_set_locations ("role", role, trash_role, LOCATION_TRASH);
      tags_set_locations ("role", role, trash_role, LOCATION_TRASH);
      permissions_set_subjects ("role", role, trash_role, LOCATION_TRASH);
    }
  else
    {
      sql ("DELETE FROM permissions"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE resource_type = 'role'"
           " AND resource = %llu"
           " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";",
           role);
      sql ("DELETE FROM permissions"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           role);
      sql ("DELETE FROM permissions_trash"
           " WHERE subject_type = 'role'"
           " AND subject = %llu"
           " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           role);
      tags_remove_resource ("role", role, LOCATION_TABLE);
    }

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

  sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role);
  sql ("DELETE FROM roles WHERE id = %llu;", role);

  cache_all_permissions_for_users (affected_users);
  g_array_free (affected_users, TRUE);

  sql_commit ();
  return 0;
}

/**
 * @brief Find a role for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of role.
 * @param[out]  role        Role return, 0 if successfully failed to find role.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find role), TRUE on error.
 */
static gboolean
find_role_with_permission (const char* uuid, role_t* role,
                           const char *permission)
{
  return find_resource_with_permission ("role", uuid, role, permission, 0);
}

/**
 * @brief Find a role given a name.
 *
 * @param[in]   name  A role name.
 * @param[out]  role  Role return, 0 if successfully failed to find role.
 *
 * @return FALSE on success (including if failed to find role), TRUE on error.
 */
static gboolean
find_role_by_name (const char* name, role_t *role)
{
  return find_resource_by_name ("role", name, role);
}

/**
 * @brief Gets UUID of role.
 *
 * @param[in]  role  Role.
 *
 * @return Users.
 */
gchar *
role_uuid (role_t role)
{
  return sql_string ("SELECT uuid FROM roles WHERE id = %llu;",
                     role);
}

/**
 * @brief Gets users of role as a string.
 *
 * @param[in]  role  Role.
 *
 * @return Users.
 */
gchar *
role_users (role_t role)
{
  return sql_string ("SELECT group_concat (name, ', ')"
                     " FROM (SELECT users.name FROM users, role_users"
                     "       WHERE role_users.role = %llu"
                     "       AND role_users.user = users.id"
                     "       GROUP BY users.name)"
                     "      AS sub;",
                     role);
}

/**
 * @brief Check whether a role is writable.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
role_writable (role_t role)
{
  if (role_is_predefined (role))
    return 0;
  return 1;
}

/**
 * @brief Check whether a trashcan role is writable.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
trash_role_writable (role_t role)
{
  return 1;
}

/**
 * @brief Check whether a role is in use.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
role_in_use (role_t role)
{
  return 0;
}

/**
 * @brief Check whether a trashcan role is in use.
 *
 * @param[in]  role  Role.
 *
 * @return 1 yes, 0 no.
 */
int
trash_role_in_use (role_t role)
{
  return 0;
}

/**
 * @brief Modify a role.
 *
 * @param[in]   role_id  UUID of role.
 * @param[in]   name     Name of role.
 * @param[in]   comment  Comment on role.
 * @param[in]   users    Role users.
 *
 * @return 0 success, 1 failed to find role, 2 failed to find user, 3 role_id
 *         required, 4 user name validation failed, 5 role with new name
 *         exists, 99 permission denied, -1 internal error.
 */
int
modify_role (const char *role_id, const char *name, const char *comment,
             const char *users)
{
  int ret;
  gchar *quoted_name, *quoted_comment;
  role_t role;
  GArray *affected_users;
  iterator_t users_iter;

  assert (current_credentials.uuid);

  if (role_id == NULL)
    return 3;

  sql_begin_immediate ();

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

  role = 0;

  if (find_role_with_permission (role_id, &role, "modify_role"))
    {
      sql_rollback ();
      return -1;
    }

  if (role == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* Check whether a role with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "role", role))
        {
          sql_rollback ();
          return 5;
        }
    }

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

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

  g_free (quoted_comment);
  g_free (quoted_name);

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

  sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role);

  ret = add_users ("role", role, users);

  init_iterator (&users_iter,
                 "SELECT \"user\" FROM role_users"
                 " WHERE \"role\" = %llu",
                 role);

  // users not looked for in this loop were removed
  //  -> possible permissions change
  while (next (&users_iter))
    {
      int index, found_user;
      user_t user = iterator_int64 (&users_iter, 0);

      found_user = 0;
      for (index = 0; index < affected_users->len && found_user == 0; index++)
        {
          if (g_array_index (affected_users, user_t, index) == user)
            {
              found_user = 1;
              break;
            }
        }

      if (found_user)
        {
          // users found here stay in the role -> no change in permissions
          g_array_remove_index_fast (affected_users, index);
        }
      else
        {
          // user added to role -> possible permissions change
          g_array_append_val (affected_users, user);
        }
    }

  cleanup_iterator (&users_iter);

  cache_all_permissions_for_users (affected_users);

  g_array_free (affected_users, TRUE);

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

  return ret;
}

/**
 * @brief Filter columns for role iterator.
 */
#define ROLE_ITERATOR_FILTER_COLUMNS                                         \
 { GET_ITERATOR_FILTER_COLUMNS, NULL }

/**
 * @brief Role iterator columns.
 */
#define ROLE_ITERATOR_COLUMNS                                                \
 {                                                                           \
   GET_ITERATOR_COLUMNS (roles),                                             \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Role iterator columns for trash case.
 */
#define ROLE_ITERATOR_TRASH_COLUMNS                                          \
 {                                                                           \
   GET_ITERATOR_COLUMNS (roles_trash),                                       \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                      \
 }

/**
 * @brief Count number of roles.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of roles in roleed set.
 */
int
role_count (const get_data_t *get)
{
  static const char *extra_columns[] = ROLE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ROLE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS;
  return count ("role", get, columns, trash_columns, extra_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a role iterator, including observed roles.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find role, 2 failed to find role (filt_id),
 *         -1 error.
 */
int
init_role_iterator (iterator_t* iterator, get_data_t *get)
{
  static const char *filter_columns[] = ROLE_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = ROLE_ITERATOR_COLUMNS;
  static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS;

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


/* Filters. */

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

/**
 * @brief Return the UUID of a filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
filter_uuid (filter_t filter)
{
  return sql_string ("SELECT uuid FROM filters WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the UUID of a trashcan filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
static char*
trash_filter_uuid (filter_t filter)
{
  return sql_string ("SELECT uuid FROM filters_trash WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the name of a filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return name of filter.
 */
char*
filter_name (filter_t filter)
{
  return sql_string ("SELECT name FROM filters WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the name of a trashcan filter.
 *
 * @param[in]  filter  Filter.
 *
 * @return name of filter.
 */
static char*
trash_filter_name (filter_t filter)
{
  return sql_string ("SELECT name FROM filters_trash WHERE id = %llu;",
                     filter);
}

/**
 * @brief Return the term of a filter.
 *
 * @param[in]  uuid  Filter UUID.
 *
 * @return Newly allocated term if available, else NULL.
 */
gchar*
filter_term (const char *uuid)
{
  gchar *quoted_uuid, *ret;
  quoted_uuid = sql_quote (uuid);
  ret = sql_string ("SELECT term FROM filters WHERE uuid = '%s';",
                    quoted_uuid);
  g_free (quoted_uuid);
  return ret;
}

/**
 * @brief Return the value of a column keyword of a filter term.
 *
 * @param[in]  term    Filter term.
 * @param[in]  column  Column name.
 *
 * @return Value of column keyword if one exists, else NULL.
 */
gchar*
filter_term_value (const char *term, const char *column)
{
  keyword_t **point;
  array_t *split;

  if (term == NULL)
    return NULL;

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

      keyword = *point;
      if (keyword->column
          && ((strcasecmp (keyword->column, column) == 0)
              || (keyword->column[0] == '_'
                  && (strcasecmp (keyword->column + 1, column) == 0))))
        {
          gchar *ret = g_strdup (keyword->string);
          filter_free (split);
          return ret;
        }
      point++;
    }
  filter_free (split);
  return NULL;
}

/**
 * @brief Return the value of the apply_overrides keyword of a filter term.
 *
 * @param[in]  term    Filter term.
 *
 * @return Value of apply_overrides if it exists, else APPLY_OVERRIDES_DEFAULT.
 */
int
filter_term_apply_overrides (const char *term)
{
  if (term)
    {
      int ret;
      gchar *apply_overrides_str;

      apply_overrides_str = filter_term_value (term, "apply_overrides");
      ret = apply_overrides_str
              ? (strcmp (apply_overrides_str, "0") ? 1 : 0)
              : APPLY_OVERRIDES_DEFAULT;

      g_free (apply_overrides_str);
      return ret;
    }
  else
    return APPLY_OVERRIDES_DEFAULT;
}

/**
 * @brief Return the value of the min_qod keyword of a filter term.
 *
 * @param[in]  term    Filter term.
 *
 * @return Value of min_qod if it exists, else MIN_QOD_DEFAULT.
 */
int
filter_term_min_qod (const char *term)
{
  if (term)
    {
      int ret;
      gchar *min_qod_str;

      min_qod_str = filter_term_value (term, "min_qod");
      ret = (min_qod_str && strcmp (min_qod_str, ""))
              ? atoi (min_qod_str) : MIN_QOD_DEFAULT;

      g_free (min_qod_str);
      return ret;
    }
  else
    return MIN_QOD_DEFAULT;
}


/**
 * @brief Create a filter.
 *
 * @param[in]   name            Name of filter.
 * @param[in]   comment         Comment on filter.
 * @param[in]   type            Type of resource.
 * @param[in]   term            Filter term.
 * @param[out]  filter          Created filter.
 *
 * @return 0 success, 1 filter exists already, 2 error in type, 99 permission
 *         denied.
 */
int
create_filter (const char *name, const char *comment, const char *type,
               const char *term, filter_t* filter)
{
  gchar *quoted_name, *quoted_comment, *quoted_term, *clean_term;
  const char *db_type;

  assert (current_credentials.uuid);

  if (type && strlen (type))
    {
      db_type = type_db_name (type);
      if ((db_type == NULL || !valid_type (db_type)) && !valid_subtype (type))
      {
        return 2;
      }
      type = valid_subtype (type) ? type : db_type;
    }

  sql_begin_immediate ();

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

  if (resource_with_name_exists (name, "filter", 0))
    {
      sql_rollback ();
      return 1;
    }
  quoted_name = sql_quote (name ?: "");

  clean_term = manage_clean_filter (term ? term : "");
  quoted_term = sql_quote (clean_term);
  g_free (clean_term);

  if (comment)
    {
      quoted_comment = sql_quote (comment);
      sql ("INSERT INTO filters"
           " (uuid, name, owner, comment, type, term, creation_time,"
           "  modification_time)"
           " VALUES (make_uuid (), '%s',"
           " (SELECT id FROM users WHERE users.uuid = '%s'),"
           " '%s', %s%s%s, '%s', m_now (), m_now ());",
           quoted_name,
           current_credentials.uuid,
           quoted_comment,
           type ? "lower ('" : "",
           type ? type : "''",
           type ? "')" : "",
           quoted_term);
      g_free (quoted_comment);
    }
  else
    sql ("INSERT INTO filters"
         " (uuid, name, owner, comment, type, term, creation_time,"
         "  modification_time)"
         " VALUES (make_uuid (), '%s',"
         " (SELECT id FROM users WHERE users.uuid = '%s'),"
         " '', %s%s%s, '%s', m_now (), m_now ());",
         quoted_name,
         current_credentials.uuid,
         type ? "lower ('" : "",
         type ? type : "''",
         type ? "')" : "",
         quoted_term);

  if (filter)
    *filter = sql_last_insert_id ();

  g_free (quoted_name);
  g_free (quoted_term);

  sql_commit ();

  return 0;
}

/**
 * @brief Create a filter from an existing filter.
 *
 * @param[in]  name        Name of new filter.  NULL to copy from existing.
 * @param[in]  comment     Comment on new filter.  NULL to copy from existing.
 * @param[in]  filter_id   UUID of existing filter.
 * @param[out] new_filter  New filter.
 *
 * @return 0 success, 1 filter exists already, 2 failed to find existing
 *         filter, -1 error.
 */
int
copy_filter (const char* name, const char* comment, const char *filter_id,
             filter_t* new_filter)
{
  return copy_resource ("filter", name, comment, filter_id, "term, type",
                        1, new_filter, NULL);
}

/**
 * @brief Delete a filter.
 *
 * @param[in]  filter_id  UUID of filter.
 * @param[in]  ultimate   Whether to remove entirely, or to trashcan.
 *
 * @return 0 success, 1 fail because a task refers to the filter, 2 failed
 *         to find filter, 99 permission denied, -1 error.
 */
int
delete_filter (const char *filter_id, int ultimate)
{
  gchar *quoted_filter_id;
  filter_t filter = 0;

  sql_begin_immediate ();

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

  if (find_filter_with_permission (filter_id, &filter, "delete_filter"))
    {
      sql_rollback ();
      return -1;
    }

  if (filter == 0)
    {
      if (find_trash ("filter", filter_id, &filter))
        {
          sql_rollback ();
          return -1;
        }
      if (filter == 0)
        {
          sql_rollback ();
          return 2;
        }
      if (ultimate == 0)
        {
          /* It's already in the trashcan. */
          sql_commit ();
          return 0;
        }

      /* Check if it's in use by an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alerts_trash"
                   " WHERE filter = %llu"
                   " AND filter_location = " G_STRINGIFY (LOCATION_TRASH) ";",
                   filter))
        {
          sql_rollback ();
          return 1;
        }

      /* Check if it's in use by the condition of an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alert_condition_data_trash"
                   " WHERE name = 'filter_id'"
                   " AND data = (SELECT uuid FROM filters_trash"
                   "             WHERE id = %llu)"
                   " AND (SELECT condition = %i OR condition = %i"
                   "      FROM alerts_trash WHERE id = alert);",
                   filter,
                   ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                   ALERT_CONDITION_FILTER_COUNT_CHANGED))
        {
          sql_rollback ();
          return 1;
        }

      permissions_set_orphans ("filter", filter, LOCATION_TRASH);
      tags_remove_resource ("filter", filter, LOCATION_TRASH);

      sql ("DELETE FROM filters_trash WHERE id = %llu;", filter);
      sql_commit ();
      return 0;
    }

  /* Check if it's in use by an alert. */
  if (sql_int ("SELECT count(*) FROM alerts"
               " WHERE filter = %llu;",
               filter))
    {
      sql_rollback ();
      return 1;
    }

  /* Check if it's in use by the condition of an alert. */
  if (sql_int ("SELECT count(*) FROM alert_condition_data"
               " WHERE name = 'filter_id'"
               " AND data = (SELECT uuid FROM filters"
               "             WHERE id = %llu)"
               " AND (SELECT condition = %i OR condition = %i"
               "      FROM alerts WHERE id = alert);",
               filter,
               ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
               ALERT_CONDITION_FILTER_COUNT_CHANGED))
    {
      sql_rollback ();
      return 1;
    }

  if (ultimate)
    {
      /* Check if it's in use by the condition of an alert in the trashcan. */
      if (sql_int ("SELECT count(*) FROM alert_condition_data_trash"
                   " WHERE name = 'filter_id'"
                   " AND data = (SELECT uuid FROM filters"
                   "             WHERE id = %llu)"
                   " AND (SELECT condition = %i OR condition = %i"
                   "      FROM alerts_trash WHERE id = alert);",
                   filter,
                   ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                   ALERT_CONDITION_FILTER_COUNT_CHANGED))
        {
          sql_rollback ();
          return 1;
        }
    }

  quoted_filter_id = sql_quote (filter_id);
  sql ("DELETE FROM settings WHERE name %s '%% Filter' AND value = '%s';",
       sql_ilike_op (),
       quoted_filter_id);
  g_free (quoted_filter_id);

  if (ultimate == 0)
    {
      sql ("INSERT INTO filters_trash"
           " (uuid, owner, name, comment, type, term, creation_time,"
           "  modification_time)"
           " SELECT uuid, owner, name, comment, type, term, creation_time,"
           "  modification_time"
           " FROM filters WHERE id = %llu;",
           filter);

      /* Update the location of the filter in any trashcan alerts. */
      sql ("UPDATE alerts_trash"
           " SET filter = %llu,"
           "     filter_location = " G_STRINGIFY (LOCATION_TRASH)
           " WHERE filter = %llu"
           " AND filter_location = " G_STRINGIFY (LOCATION_TABLE) ";",
           sql_last_insert_id (),
           filter);

      permissions_set_locations ("filter", filter,
                                 sql_last_insert_id (),
                                 LOCATION_TRASH);
      tags_set_locations ("filter", filter,
                          sql_last_insert_id (),
                          LOCATION_TRASH);
    }
  else
    {
      permissions_set_orphans ("filter", filter, LOCATION_TABLE);
      tags_remove_resource ("filter", filter, LOCATION_TABLE);
    }

  sql ("DELETE FROM filters WHERE id = %llu;", filter);

  sql_commit ();
  return 0;
}

/**
 * @brief Check whether a filter is in use.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
filter_in_use (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts"
                    /* Filter applied to results passed to alert's "generate". */
                    " WHERE filter = %llu"
                    /* Filter applied to check alert condition. */
                    "   OR (EXISTS (SELECT * FROM alert_condition_data"
                    "             WHERE name = 'filter_id'"
                    "             AND data = (SELECT uuid FROM filters"
                    "                          WHERE id = %llu)"
                    "             AND alert = alerts.id)"
                    "       AND (condition = %i OR condition = %i))",
                    filter,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @brief Check whether a filter is in use for the output of any alert.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
static int
filter_in_use_for_output (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts"
                    " WHERE filter = %llu;",
                    filter);
}

/**
 * @brief Check whether a filter is in use by any result alert conditions.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
static int
filter_in_use_for_result_event (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts"
                    " WHERE event = %llu"
                    " AND (EXISTS (SELECT * FROM alert_condition_data"
                    "              WHERE name = 'filter_id'"
                    "              AND data = (SELECT uuid FROM filters"
                    "                          WHERE id = %llu)"
                    "              AND alert = alerts.id)"
                    " AND (condition = %i OR condition = %i))",
                    EVENT_TASK_RUN_STATUS_CHANGED,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @brief Check whether a filter is in use by any secinfo alert conditions.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
static int
filter_in_use_for_secinfo_event (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts"
                    " WHERE (event = %llu OR event = %llu)"
                    " AND (EXISTS (SELECT * FROM alert_condition_data"
                    "              WHERE name = 'filter_id'"
                    "              AND data = (SELECT uuid FROM filters"
                    "                          WHERE id = %llu)"
                    "              AND alert = alerts.id)"
                    " AND (condition = %i OR condition = %i))",
                    EVENT_NEW_SECINFO,
                    EVENT_UPDATED_SECINFO,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @brief Check whether a trashcan filter is in use.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
trash_filter_in_use (filter_t filter)
{
  return !!sql_int ("SELECT count (*) FROM alerts_trash"
                    " WHERE (filter = %llu"
                    "        AND filter_location = "
                                    G_STRINGIFY (LOCATION_TRASH) ")"
                    "   OR (EXISTS (SELECT *"
                    "               FROM alert_condition_data_trash"
                    "               WHERE name = 'filter_id'"
                    "                 AND data = (SELECT uuid"
                    "                             FROM filters_trash"
                    "                             WHERE id = %llu)"
                    "                 AND alert = alerts_trash.id)"
                    "       AND (condition = %i OR condition = %i))",
                    filter,
                    filter,
                    ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                    ALERT_CONDITION_FILTER_COUNT_CHANGED);
}

/**
 * @brief Check whether a filter is writable.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
filter_writable (filter_t filter)
{
  return 1;
}

/**
 * @brief Check whether a trashcan filter is writable.
 *
 * @param[in]  filter  Filter.
 *
 * @return 1 yes, 0 no.
 */
int
trash_filter_writable (filter_t filter)
{
  return 1;
}

/**
 * @brief Filter columns for filter iterator.
 */
#define FILTER_ITERATOR_FILTER_COLUMNS                        \
 { GET_ITERATOR_FILTER_COLUMNS, "type", "term", NULL }

/**
 * @brief Filter iterator columns.
 */
#define FILTER_ITERATOR_COLUMNS                               \
 {                                                            \
   GET_ITERATOR_COLUMNS (filters),                            \
   { "type" , NULL, KEYWORD_TYPE_STRING },                    \
   { "term", NULL, KEYWORD_TYPE_STRING },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                       \
 }

/**
 * @brief Filter iterator columns for trash case.
 */
#define FILTER_ITERATOR_TRASH_COLUMNS                         \
 {                                                            \
   GET_ITERATOR_COLUMNS (filters_trash),                      \
   { "type" , NULL, KEYWORD_TYPE_STRING },                    \
   { "term", NULL, KEYWORD_TYPE_STRING },                     \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                       \
 }

/**
 * @brief Count number of filters.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of filters in filtered set.
 */
int
filter_count (const get_data_t *get)
{
  static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = FILTER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS;
  return count ("filter", get, columns, trash_columns, filter_columns,
                0, 0, 0, TRUE);
}

/**
 * @brief Initialise a filter iterator, including observed filters.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find filter, 2 failed to find filter (filt_id),
 *         -1 error.
 */
int
init_filter_iterator (iterator_t* iterator, get_data_t *get)
{
  static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = FILTER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS;

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

/**
 * @brief Get the type from a filter iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The type of the filter, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.  "" for any type.
 */
const char*
filter_iterator_type (iterator_t* iterator)
{
  const char *ret;
  if (iterator->done) return NULL;
  ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT);
  return ret ? ret : "";
}

/**
 * @brief Get the term from a filter iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The term of the filter, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (filter_iterator_term, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Initialise a filter alert iterator.
 *
 * Iterates over all alerts that use the filter.
 *
 * @param[in]  iterator   Iterator.
 * @param[in]  filter     Filter.
 */
void
init_filter_alert_iterator (iterator_t* iterator, filter_t filter)
{
  gchar *available, *with_clause;
  get_data_t get;
  array_t *permissions;

  assert (filter);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_alerts"));
  available = acl_where_owned ("alert", &get, 1, "any", 0, permissions, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT name, uuid, %s FROM alerts"
                 " WHERE filter = %llu"
                 " OR (EXISTS (SELECT * FROM alert_condition_data"
                 "             WHERE name = 'filter_id'"
                 "             AND data = (SELECT uuid FROM filters"
                 "                         WHERE id = %llu)"
                 "             AND alert = alerts.id)"
                 "     AND (condition = %i OR condition = %i))"
                 " ORDER BY name ASC;",
                 with_clause ? with_clause : "",
                 available,
                 filter,
                 filter,
                 ALERT_CONDITION_FILTER_COUNT_AT_LEAST,
                 ALERT_CONDITION_FILTER_COUNT_CHANGED);

  g_free (with_clause);
  g_free (available);
}

/**
 * @brief Get the name from a filter_alert iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host, or NULL if iteration is complete.  Freed by
 *         cleanup_iterator.
 */
DEF_ACCESS (filter_alert_iterator_name, 0);

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

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

/**
 * @brief Modify a filter.
 *
 * @param[in]   filter_id       UUID of filter.
 * @param[in]   name            Name of filter.
 * @param[in]   comment         Comment on filter.
 * @param[in]   term            Filter term.
 * @param[in]   type            Type of filter.
 *
 * @return 0 success, 1 failed to find filter, 2 filter with new name exists,
 *         3 error in type name, 4 filter_id required, 5 filter is in use so
 *         type must be "result", 6 filter is in use so type must be "info",
 *         99 permission denied, -1 internal error.
 */
int
modify_filter (const char *filter_id, const char *name, const char *comment,
               const char *term, const char *type)
{
  gchar *quoted_name, *quoted_comment, *quoted_term, *quoted_type, *clean_term;
  filter_t filter;
  const char *db_type;

  if (filter_id == NULL)
    return 4;

  db_type = type_db_name (type);
  if (db_type && !((strcmp (db_type, "") == 0) || valid_type (db_type))) 
    {
      if (!valid_subtype (type))
        return 3;
    }
  type = valid_subtype (type) ? type : db_type;

  sql_begin_immediate ();

  assert (current_credentials.uuid);

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

  filter = 0;
  if (find_filter_with_permission (filter_id, &filter, "modify_filter"))
    {
      sql_rollback ();
      return -1;
    }

  if (filter == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* If the filter is linked to an alert, check that the type is valid. */

  if ((filter_in_use_for_output (filter)
       || filter_in_use_for_result_event (filter))
      && type
      && strcasecmp (type, "result"))
    {
      sql_rollback ();
      return 5;
    }

  if (filter_in_use_for_secinfo_event (filter)
      && type
      && strcasecmp (type, "info"))
    {
      sql_rollback ();
      return 6;
    }

  /* Check whether a filter with the same name exists already. */
  if (name)
    {
      if (resource_with_name_exists (name, "filter", filter))
        {
          sql_rollback ();
          return 2;
        }
    }

  quoted_name = sql_quote(name ?: "");
  clean_term = manage_clean_filter (term ? term : "");
  quoted_term = sql_quote (clean_term);
  g_free (clean_term);
  quoted_comment = sql_quote (comment ? comment : "");
  quoted_type = sql_quote (type ? type : "");

  sql ("UPDATE filters SET"
       " name = '%s',"
       " comment = '%s',"
       " term = '%s',"
       " type = lower ('%s'),"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_name,
       quoted_comment,
       quoted_term,
       quoted_type,
       filter);

  g_free (quoted_comment);
  g_free (quoted_name);
  g_free (quoted_term);
  g_free (quoted_type);

  sql_commit ();

  return 0;
}


/* Schema. */

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

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

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

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

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


/* Assets. */

/**
 * \page asset_rules Ruleset for updating assets from scan detections
 *
 * During a scan various assets are identfied. The findings are by default
 * used to update the asset database. Since assets may already be present in
 * the database or even be present with contradictive properties, a ruleset
 * defines how the asset database is updated upon findings.
 *
 * Hosts
 * -----
 *
 * When a host is detected, and there is at least one asset host that has the
 * same name and owner as the detected host, and whose identifiers all have
 * the same values as the identifiers of the detected host, then the most
 * recent such asset host is used. Otherwise a new asset host is created.
 * Either way the identifiers are added to the asset host. It does not matter
 * if the asset host has fewer identifiers than detected, as long as the
 * existing identifiers match.
 *
 * At the beginning of a scan, when a host is first detected, the decision
 * about which asset host to use is made by \ref host_notice.  At the end
 * of the scan, if the host has identifiers, then this decision is revised
 * by \ref hosts_set_identifiers to take the identifiers into account.
 *
 * Host identifiers can be ip, hostname, MAC, OS or ssh-key.
 *
 * This documentation includes some pseudo-code and tabular definition.
 * Eventually one of them will repalce the other.
 *
 * Name    : The assigned name (usually the IP)
 * IP      : The detected IP
 * Hostname: The detected Hostname
 * OS:     : The detected OS
 *
 * If IP And Not Hostname:
 *   If Not Assets.Host(id=Name) And Not Assets.Host(attrib=IP, IP):
 *     Assets.Host.New(id=Name, ip=IP)
 *   If Assets.Host(id=Name) == 1:
 *     Assets.Host.Add(id=Name, ip=IP)
 *
 * This pseudo-code is equivalent to the first two rows of:
 *
 * Detection                     | Asset State                                                                 |     Asset Update
 * ----------------------------- | --------------------------------------------------------------------------- | -----------------------------
 * IP address X.                 | No host with Name=X or any ip=X.                                            | Create host with Name=X and ip=X.
 * IP address X.                 | Host A with Name=X.                                                         | Add ip=X to host A.
 * IP address X.                 | (Host A with Name=X and ip=X) and (Host B with Name=X and ip=X).            | Add ip=X to host (Newest(A,B)).
 * IP address X with Hostname Y. | Host A with Name=X and ip=X.                                                | Add ip=X and hostname=Y to host A.
 * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y.                                 | Add ip=X and hostname=Y to host A.
 * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname<>Y.                                | Create host with Name=X, ip=X and hostname=Y.
 * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y and host B with Name=X and ip=X. | Add ip=X and hostname=Y to host (Newst(A,B)).
 *
 * Follow up action: If a MAC, OS or ssh-key was detected, then the respective
 * identifiers are added to the asset host selected during asset update.
 *
 * Operating Systems
 * -----------------
 *
 * If OS:
 *   If Not Assets.OS(id=OS):
 *     Assets.OS.New(id=OS)
 *
 * This pseudo-code is equivalent to:
 *
 * Detection | Asset State        | Asset Update
 * --------- | ------------------ | ------------------------
 * OS X.     | No OS with Name=X. | Create OS with Name=X.
 * OS X.     | OS with Name=X.    | No action.
 */

/**
 * @brief Return the UUID of the asset associated with a result host.
 *
 * @param[in]  host    Host value from result.
 * @param[in]  result  Result.
 *
 * @return Asset UUID.
 */
char *
result_host_asset_id (const char *host, result_t result)
{
  gchar *quoted_host;
  char *asset_id;

  quoted_host = sql_quote (host);
  asset_id = sql_string ("SELECT uuid FROM hosts"
                         " WHERE id = (SELECT host FROM host_identifiers"
                         "             WHERE source_type = 'Report Host'"
                         "             AND name = 'ip'"
                         "             AND source_id"
                         "                 = (SELECT uuid"
                         "                    FROM reports"
                         "                    WHERE id = (SELECT report"
                         "                                FROM results"
                         "                                WHERE id = %llu))"
                         "             AND value = '%s'"
                         "             LIMIT 1);",
                         result,
                         quoted_host);
  g_free (quoted_host);
  return asset_id;
}

/**
 * @brief Return the UUID of a host.
 *
 * @param[in]  host  Host.
 *
 * @return Host UUID.
 */
char*
host_uuid (resource_t host)
{
  return sql_string ("SELECT uuid FROM hosts WHERE id = %llu;",
                     host);
}

/**
 * @brief Add a report host.
 *
 * @param[in]  report   UUID of resource.
 * @param[in]  host     Host.
 * @param[in]  start    Start time.
 * @param[in]  end      End time.
 *
 * @return Report host.
 */
report_host_t
manage_report_host_add (report_t report, const char *host, time_t start,
                        time_t end)
{
  char *quoted_host = sql_quote (host);

  sql ("INSERT INTO report_hosts"
       " (report, host, start_time, end_time, current_port, max_port)"
       " SELECT %llu, '%s', %lld, %lld, 0, 0"
       " WHERE NOT EXISTS (SELECT 1 FROM report_hosts WHERE report = %llu"
       "                   AND host = '%s');",
       report, quoted_host, (long long) start, (long long) end, report,
       quoted_host);
  g_free (quoted_host);
  return sql_last_insert_id ();
}

/**
 * @brief Tests if a report host is marked as dead.
 *
 * @param[in]  report_host  Report host.
 *
 * @return 1 if the host is marked as dead, 0 otherwise.
 */
static int
report_host_dead (report_host_t report_host)
{
  return sql_int ("SELECT count(*) != 0 FROM report_host_details"
                  " WHERE report_host = %llu"
                  "   AND name = 'Host dead'"
                  "   AND value != '0';",
                  report_host);
}

/**
 * @brief Counts.
 *
 * @param[in]  report_host  Report host.
 *
 * @return 1 if the host is marked as dead, 0 otherwise.
 */
static int
report_host_result_count (report_host_t report_host)
{
  return sql_int ("SELECT count(*) FROM report_hosts, results"
                  " WHERE report_hosts.id = %llu"
                  "   AND results.report = report_hosts.report"
                  "   AND report_hosts.host = results.host;",
                  report_host);
}

/**
 * @brief Set end time of a report host.
 *
 * @param[in]  report_host  Report host.
 * @param[in]  end_time     End time.
 */
void
report_host_set_end_time (report_host_t report_host, time_t end_time)
{
  sql ("UPDATE report_hosts SET end_time = %lld WHERE id = %llu;",
       end_time, report_host);
}

/**
 * @brief Host identifiers for the current scan.
 */
array_t *identifiers = NULL;

/**
 * @brief Unique hosts listed in host_identifiers.
 */
static array_t *identifier_hosts = NULL;

/**
 * @brief Host identifier type.
 */
typedef struct
{
  gchar *ip;                ///< IP of host.
  gchar *name;              ///< Name of identifier, like "hostname".
  gchar *value;             ///< Value of identifier.
  gchar *source_type;       ///< Type of identifier source, like "Report Host".
  gchar *source_id;         ///< ID of source.
  gchar *source_data;       ///< Extra data for source.
} identifier_t;

/**
 * @brief Free an identifier.
 *
 * @param[in]  identifier  Identifier.
 */
static void
identifier_free (identifier_t *identifier)
{
  if (identifier)
    {
      g_free (identifier->ip);
      g_free (identifier->name);
      g_free (identifier->value);
      g_free (identifier->source_type);
      g_free (identifier->source_id);
      g_free (identifier->source_data);
    }
}

/**
 * @brief Setup hosts and their identifiers after a scan, from host details.
 *
 * At the end of a scan this revises the decision about which asset host to use
 * for each host that has identifiers.  The rules for this decision are described
 * in \ref asset_rules.  (The initial decision is made by \ref host_notice.)
 *
 * @param[in]  report  Report that the identifiers come from.
 */
void
hosts_set_identifiers (report_t report)
{
  if (identifier_hosts)
    {
      int host_index, index;
      gchar *ip;

      array_terminate (identifiers);
      array_terminate (identifier_hosts);

      host_index = 0;
      while ((ip = (gchar*) g_ptr_array_index (identifier_hosts, host_index)))
        {
          host_t host, host_new;
          gchar *quoted_host_name;
          identifier_t *identifier;
          GString *select;

          if (report_host_noticeable (report, ip) == 0)
            {
              host_index++;
              continue;
            }

          quoted_host_name = sql_quote (ip);

          select = g_string_new ("");

          /* Select the most recent host whose identifiers all match the given
           * identifiers, even if the host has fewer identifiers than given. */

          g_string_append_printf (select,
                                  "SELECT id FROM hosts"
                                  " WHERE name = '%s'"
                                  " AND owner = (SELECT id FROM users"
                                  "              WHERE uuid = '%s')",
                                  quoted_host_name,
                                  current_credentials.uuid);

          index = 0;
          while ((identifier = (identifier_t*) g_ptr_array_index (identifiers, index)))
            {
              gchar *quoted_identifier_name, *quoted_identifier_value;

              if (strcmp (identifier->ip, ip))
                {
                  index++;
                  continue;
                }

              quoted_identifier_name = sql_quote (identifier->name);
              quoted_identifier_value = sql_quote (identifier->value);

              g_string_append_printf (select,
                                      " AND (EXISTS (SELECT * FROM host_identifiers"
                                      "              WHERE host = hosts.id"
                                      "              AND owner = (SELECT id FROM users"
                                      "                           WHERE uuid = '%s')"
                                      "              AND name = '%s'"
                                      "              AND value = '%s')"
                                      "      OR NOT EXISTS (SELECT * FROM host_identifiers"
                                      "                     WHERE host = hosts.id"
                                      "                     AND owner = (SELECT id FROM users"
                                      "                                  WHERE uuid = '%s')"
                                      "                     AND name = '%s'))",
                                      current_credentials.uuid,
                                      quoted_identifier_name,
                                      quoted_identifier_value,
                                      current_credentials.uuid,
                                      quoted_identifier_name);

              g_free (quoted_identifier_name);
              g_free (quoted_identifier_value);

              index++;
            }

          g_string_append_printf (select,
                                  " ORDER BY creation_time DESC LIMIT 1;");

          switch (sql_int64 (&host, select->str))
            {
              case 0:
                break;
              case 1:        /* Too few rows in result of query. */
                host = 0;
                break;
              default:       /* Programming error. */
                assert (0);
              case -1:
                host = 0;
                break;
            }

          g_string_free (select, TRUE);

          if (host == 0)
            {
              /* Add the host. */

              sql ("INSERT into hosts"
                   " (uuid, owner, name, comment, creation_time, modification_time)"
                   " VALUES"
                   " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', '',"
                   "  m_now (), m_now ());",
                   current_credentials.uuid,
                   quoted_host_name);

              host_new = host = sql_last_insert_id ();

              /* Make sure the Report Host identifiers added when the host was
               * first noticed now refer to the new host. */

              sql ("UPDATE host_identifiers SET host = %llu"
                   " WHERE source_id = (SELECT uuid FROM reports"
                   "                    WHERE id = %llu)"
                   " AND name = 'ip'"
                   " AND value = '%s';",
                   host_new,
                   report,
                   quoted_host_name);
            }
          else
            {
              /* Use the existing host. */

              host_new = 0;
            }

          /* Add the host identifiers. */

          index = 0;
          while ((identifier = (identifier_t*) g_ptr_array_index (identifiers,
                                                                  index)))
            {
              gchar *quoted_identifier_name, *quoted_identifier_value;
              gchar *quoted_source_id, *quoted_source_type, *quoted_source_data;

              if (strcmp (identifier->ip, ip))
                {
                  index++;
                  continue;
                }

              quoted_identifier_name = sql_quote (identifier->name);
              quoted_identifier_value = sql_quote (identifier->value);
              quoted_source_id = sql_quote (identifier->source_id);
              quoted_source_data = sql_quote (identifier->source_data);
              quoted_source_type = sql_quote (identifier->source_type);

              if (strcmp (identifier->name, "OS") == 0)
                {
                  resource_t os;

                  switch (sql_int64 (&os,
                                     "SELECT id FROM oss"
                                     " WHERE name = '%s'"
                                     " AND owner = (SELECT id FROM users"
                                     "              WHERE uuid = '%s');",
                                     quoted_identifier_value,
                                     current_credentials.uuid))
                    {
                      case 0:
                        break;
                      default:       /* Programming error. */
                        assert (0);
                      case -1:
                      case 1:        /* Too few rows in result of query. */
                        sql ("INSERT into oss"
                             " (uuid, owner, name, comment, creation_time,"
                             "  modification_time)"
                             " VALUES"
                             " (make_uuid (),"
                             "  (SELECT id FROM users WHERE uuid = '%s'),"
                             "  '%s', '', m_now (), m_now ());",
                             current_credentials.uuid,
                             quoted_identifier_value);
                        os = sql_last_insert_id ();
                        break;
                    }

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

                  if (host_new == 0)
                    {
                      sql ("UPDATE hosts"
                           " SET modification_time = (SELECT modification_time"
                           "                          FROM host_oss"
                           "                          WHERE id = %llu)"
                           " WHERE id = %llu;",
                           sql_last_insert_id (),
                           host);

                      sql ("UPDATE oss"
                           " SET modification_time = (SELECT modification_time"
                           "                          FROM host_oss"
                           "                          WHERE id = %llu)"
                           " WHERE id = %llu;",
                           sql_last_insert_id (),
                           os);
                    }
                }
              else
                {
                  sql ("INSERT into host_identifiers"
                       " (uuid, host, owner, name, comment, value, source_type,"
                       "  source_id, source_data, creation_time, modification_time)"
                       " VALUES"
                       " (make_uuid (), %llu,"
                       "  (SELECT id FROM users WHERE uuid = '%s'),"
                       "  '%s', '', '%s', '%s', '%s', '%s', m_now (), m_now ());",
                       host,
                       current_credentials.uuid,
                       quoted_identifier_name,
                       quoted_identifier_value,
                       quoted_source_type,
                       quoted_source_id,
                       quoted_source_data);

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

              g_free (quoted_source_type);
              g_free (quoted_source_id);
              g_free (quoted_source_data);
              g_free (quoted_identifier_name);
              g_free (quoted_identifier_value);

              index++;
            }

          g_free (quoted_host_name);
          host_index++;
        }

      index = 0;
      while (identifiers && (index < identifiers->len))
        identifier_free (g_ptr_array_index (identifiers, index++));
      array_free (identifiers);
      identifiers = NULL;

      array_free (identifier_hosts);
      identifier_hosts = NULL;
    }
}

/**
 * @brief Set the maximum severity of each host in a scan.
 *
 * @param[in]  report         The report associated with the scan.
 * @param[in]  overrides_arg  Whether override should be applied.
 * @param[in]  min_qod_arg    Min QOD to use.
 */
void
hosts_set_max_severity (report_t report, int *overrides_arg, int *min_qod_arg)
{
  gchar *new_severity_sql;
  int dynamic_severity, overrides, min_qod;

  if (overrides_arg)
    overrides = *overrides_arg;
  else
    {
      task_t task;
      /* Get "Assets Apply Overrides" task preference. */
      overrides = 1;
      if (report_task (report, &task) == 0)
        {
          char *value;
          value = task_preference_value (task, "assets_apply_overrides");
          if (value && (strcmp (value, "yes") == 0))
            overrides = 1;
          else
            overrides = 0;
          free (value);
        }
    }

  if (min_qod_arg)
    min_qod = *min_qod_arg;
  else
    {
      task_t task;
      /* Get "Assets Min QOD". */
      min_qod = MIN_QOD_DEFAULT;
      if (report_task (report, &task) == 0)
        {
          char *value;
          value = task_preference_value (task, "assets_min_qod");
          if (value)
            min_qod = atoi (value);
          free (value);
        }
    }

  dynamic_severity = setting_dynamic_severity_int ();
  new_severity_sql = new_severity_clause (overrides, dynamic_severity);

  sql ("INSERT INTO host_max_severities"
       " (host, severity, source_type, source_id, creation_time)"
       " SELECT asset_host,"
       "        coalesce ((SELECT max (%s) FROM results"
       "                   WHERE report = %llu"
       "                   AND qod >= %i"
       "                   AND host = (SELECT name FROM hosts"
       "                               WHERE id = asset_host)),"
       "                  0.0),"
       "        'Report',"
       "        (SELECT uuid FROM reports WHERE id = %llu),"
       "        m_now ()"
       " FROM (SELECT host AS asset_host"
       "       FROM host_identifiers"
       "       WHERE source_id = (SELECT uuid FROM reports WHERE id = %llu))"
       "      AS subquery;",
       new_severity_sql,
       report,
       min_qod,
       report,
       report);

  g_free (new_severity_sql);
}

/**
 * @brief Store certain host details in the assets after a scan.
 *
 * @param[in]  report  The report associated with the scan.
 */
void
hosts_set_details (report_t report)
{
  sql ("INSERT INTO host_details"
       " (detail_source_type, detail_source_name, detail_source_description,"
       "  name, value, source_type, source_id, host)"
       " SELECT source_type,"
       "        source_name,"
       "        source_description,"
       "        name,"
       "        value,"
       "        'Report',"
       "        (SELECT uuid FROM reports WHERE id = %llu),"
       "        (SELECT host"
       "         FROM host_identifiers"
       "         WHERE source_id = (SELECT uuid FROM reports"
       "                            WHERE id = %llu)"
       "         AND (SELECT name FROM hosts WHERE id = host)"
       "             = (SELECT host FROM report_hosts"
       "                WHERE id = report_host_details.report_host)"
       "         LIMIT 1)"
       " FROM report_host_details"
       " WHERE (SELECT report FROM report_hosts"
       "        WHERE id = report_host)"
       "       = %llu"
       /* Only if the task is included in the assets. */
       " AND (SELECT value = 'yes' FROM task_preferences"
       "      WHERE task = (SELECT task FROM reports WHERE id = %llu)"
       "      AND name = 'in_assets')"
       /* Ensure that every report host detail has a corresponding host
        *  in the assets. */
       " AND EXISTS (SELECT *"
       "               FROM host_identifiers"
       "              WHERE source_id = (SELECT uuid FROM reports"
       "                                 WHERE id = %llu)"
       "                AND (SELECT name FROM hosts WHERE id = host)"
       "                      = (SELECT host FROM report_hosts"
       "                         WHERE id = report_host_details.report_host))"
       " AND (name IN ('best_os_cpe', 'best_os_txt', 'traceroute'));",
       report,
       report,
       report,
       report,
       report);
}

/**
 * @brief Get XML of a detailed host route.
 *
 * @param[in]  host  The host.
 *
 * @return XML.
 */
gchar*
host_routes_xml (host_t host)
{
  iterator_t routes;
  GString* buffer;

  gchar *owned_clause, *with_clause;

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

  buffer = g_string_new ("<routes>");
  init_iterator (&routes,
                 "SELECT outer_details.value,"
                 "       outer_details.source_type,"
                 "       outer_details.source_id,"
                 "       outer_identifiers.modification_time"
                 "  FROM host_details AS outer_details"
                 "  JOIN host_identifiers AS outer_identifiers"
                 "    ON outer_identifiers.host = outer_details.host"
                 " WHERE outer_details.host = %llu"
                 "   AND outer_details.name = 'traceroute'"
                 "   AND outer_details.source_id = outer_identifiers.source_id"
                 "   AND outer_identifiers.name='ip'"
                 "   AND outer_identifiers.modification_time"
                 "         = (SELECT max (modification_time)"
                 "              FROM host_identifiers"
                 "             WHERE host_identifiers.host = %llu"
                 "               AND host_identifiers.source_id IN"
                 "                   (SELECT source_id FROM host_details"
                 "                     WHERE host = %llu"
                 "                       AND value = outer_details.value)"
                 "              AND host_identifiers.name='ip')"
                 " ORDER BY outer_identifiers.modification_time DESC;",
                 host, host, host);

  while (next (&routes))
    {
      const char *traceroute;
      const char *source_id;
      time_t modified;
      gchar **hop_ips, **hop_ip;
      int distance;

      g_string_append (buffer, "<route>");

      traceroute = iterator_string (&routes, 0);
      source_id = iterator_string (&routes, 2);
      modified = iterator_int64 (&routes, 3);

      hop_ips = g_strsplit (traceroute, ",", 0);
      hop_ip = hop_ips;

      distance = 0;

      while (*hop_ip != NULL) {
        iterator_t best_host_iterator;
        const char *best_host_id;
        int same_source;

        init_iterator (&best_host_iterator,
                       "%s"
                       " SELECT hosts.uuid,"
                       "       (source_id='%s')"
                       "         AS same_source"
                       "  FROM hosts, host_identifiers"
                       " WHERE hosts.id = host_identifiers.host"
                       "   AND host_identifiers.name = 'ip'"
                       "   AND host_identifiers.value='%s'"
                       "   AND %s"
                       " ORDER BY same_source DESC,"
                       "          abs(host_identifiers.modification_time"
                       "              - %llu) ASC"
                       " LIMIT 1;",
                       with_clause ? with_clause : "",
                       source_id,
                       *hop_ip,
                       owned_clause,
                       modified);

        if (next (&best_host_iterator))
          {
            best_host_id = iterator_string (&best_host_iterator, 0);
            same_source = iterator_int (&best_host_iterator, 1);
          }
        else
          {
            best_host_id = NULL;
            same_source = 0;
          }

        g_string_append_printf (buffer,
                                "<host id=\"%s\""
                                " distance=\"%d\""
                                " same_source=\"%d\">"
                                "<ip>%s</ip>"
                                "</host>",
                                best_host_id ? best_host_id : "",
                                distance,
                                same_source,
                                *hop_ip);

        cleanup_iterator (&best_host_iterator);

        distance++;
        hop_ip++;
      }

      g_string_append (buffer, "</route>");
      g_strfreev(hop_ips);
    }

  g_free (with_clause);
  g_free (owned_clause);

  cleanup_iterator (&routes);

  g_string_append (buffer, "</routes>");

  return g_string_free (buffer, FALSE);
}

/**
 * @brief Add host details to a report host.
 *
 * @param[in]  report  UUID of resource.
 * @param[in]  ip      Host.
 * @param[in]  entity  XML entity containing details.
 * @param[in]  hashed_host_details  A GHashtable containing hashed host details.
 *
 * @return 0 success, -1 failed to parse XML.
 */
int
manage_report_host_details (report_t report, const char *ip,
                            entity_t entity, GHashTable *hashed_host_details)
{
  int in_assets;
  entities_t details;
  entity_t detail;
  char *uuid;
  char *hash_value;

  in_assets = sql_int ("SELECT not(value = 'no') FROM task_preferences"
                       " WHERE task = (SELECT task FROM reports"
                       "                WHERE id = %llu)"
                       " AND name = 'in_assets';",
                       report);

  details = entity->entities;
  if (identifiers == NULL)
    identifiers = make_array ();
  if (identifier_hosts == NULL)
    identifier_hosts = make_array ();
  uuid = report_uuid (report);
  while ((detail = first_entity (details)))
    {
      if (strcmp (entity_name (detail), "detail") == 0)
        {
          entity_t source, source_type, source_name, source_desc, name, value;

          /* Parse host detail and add to report */
          source = entity_child (detail, "source");
          if (source == NULL)
            goto error;
          source_type = entity_child (source, "type");
          if (source_type == NULL)
            goto error;
          source_name = entity_child (source, "name");
          if (source_name == NULL)
            goto error;
          source_desc = entity_child (source, "description");
          if (source_desc == NULL)
            goto error;
          name = entity_child (detail, "name");
          if (name == NULL)
            goto error;
          value = entity_child (detail, "value");
          if (value == NULL)
            goto error;

          if (!check_host_detail_exists (report, ip, entity_text (source_type),
                                         entity_text (source_name),
                                         entity_text (source_desc),
                                         entity_text (name),
                                         entity_text (value),
                                         (char**) &hash_value,
                                         hashed_host_details))
            {
              insert_report_host_detail
               (report, ip, entity_text (source_type), entity_text (source_name),
                entity_text (source_desc), entity_text (name), entity_text (value),
                hash_value);
              g_free (hash_value);
            }
          else
            {
              g_free (hash_value);
              details = next_entities (details);
              continue;
            }

          /* Only add to assets if "Add to Assets" is set on the task. */
          if (in_assets)
            {
              if (strcmp (entity_text (name), "hostname") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("hostname");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "MAC") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("MAC");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "OS") == 0
                  && g_str_has_prefix (entity_text (value), "cpe:"))
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("OS");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
              if (strcmp (entity_text (name), "ssh-key") == 0)
                {
                  identifier_t *identifier;

                  identifier = g_malloc (sizeof (identifier_t));
                  identifier->ip = g_strdup (ip);
                  identifier->name = g_strdup ("ssh-key");
                  identifier->value = g_strdup (entity_text (value));
                  identifier->source_id = g_strdup (uuid);
                  identifier->source_type = g_strdup ("Report Host Detail");
                  identifier->source_data
                    = g_strdup (entity_text (source_name));
                  array_add (identifiers, identifier);
                  array_add_new_string (identifier_hosts, g_strdup (ip));
                }
            }
        }
      details = next_entities (details);
    }
  free (uuid);

  return 0;

 error:
  free (uuid);
  return -1;
}

/**
 * @brief Add a host detail to a report host.
 *
 * @param[in]  report  UUID of resource.
 * @param[in]  host    Host.
 * @param[in]  xml     Report host detail XML.
 * @param[in]  hashed_host_details  A GHashtable containing hashed host details.
 *
 * @return 0 success, -1 failed to parse XML, -2 host was NULL.
 */
int
manage_report_host_detail (report_t report, const char *host,
                           const char *xml, GHashTable *hashed_host_details)
{
  int ret;
  entity_t entity;

  if (host == NULL)
    return -2;

  entity = NULL;
  if (parse_entity (xml, &entity))
    return -1;

  ret = manage_report_host_details (report,
                                    host,
                                    entity,
                                    hashed_host_details);
  free_entity (entity);
  return ret;
}

/**
 * @brief Initialise a host identifier iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  host        Host.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for type then start.
 */
void
init_host_identifier_iterator (iterator_t* iterator, host_t host,
                               int ascending, const char* sort_field)
{
  assert (current_credentials.uuid);

  if (host)
    init_iterator (iterator,
                   "SELECT id, uuid, name, comment, creation_time,"
                   "       modification_time, creation_time AS created,"
                   "       modification_time AS modified, owner, owner, value,"
                   "       source_type, source_id, source_data,"
                   "       (CASE WHEN source_type LIKE 'Report%%'"
                   "        THEN NOT EXISTS (SELECT * FROM reports"
                   "                         WHERE uuid = source_id)"
                   "        ELSE CAST (0 AS boolean)"
                   "        END),"
                   "       '', ''"
                   " FROM host_identifiers"
                   " WHERE host = %llu"
                   " UNION"
                   " SELECT id, uuid, name, comment, creation_time,"
                   "        modification_time, creation_time AS created,"
                   "        modification_time AS modified, owner, owner,"
                   "        (SELECT name FROM oss WHERE id = os),"
                   "        source_type, source_id, source_data,"
                   "        (CASE WHEN source_type LIKE 'Report%%'"
                   "         THEN NOT EXISTS (SELECT * FROM reports"
                   "                          WHERE uuid = source_id)"
                   "         ELSE CAST (0 AS boolean)"
                   "         END),"
                   "        (SELECT uuid FROM oss WHERE id = os),"
                   "        cpe_title ((SELECT name FROM oss WHERE id = os))"
                   " FROM host_oss"
                   " WHERE host = %llu"
                   " ORDER BY %s %s;",
                   host,
                   host,
                   sort_field ? sort_field : "creation_time",
                   ascending ? "ASC" : "DESC");
  else
    init_iterator (iterator,
                   "SELECT id, uuid, name, comment, creation_time,"
                   "       modification_time, creation_time AS created,"
                   "       modification_time AS modified, owner, owner, value,"
                   "       source_type, source_id, source_data, 0, '', ''"
                   " FROM host_identifiers"
                   " ORDER BY %s %s;",
                   sort_field ? sort_field : "creation_time",
                   ascending ? "ASC" : "DESC");
}

/**
 * @brief Get the value from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The value of the host identifier, or NULL if iteration is complete.
 *         Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_value, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the source type from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source type of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_type,
            GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the source from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_id, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the source data from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source data of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_source_data,
            GET_ITERATOR_COLUMN_COUNT + 3);

/**
 * @brief Get the source orphan state from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source orphan state of the host identifier, or 0 if iteration is
 *         complete. Freed by cleanup_iterator.
 */
int
host_identifier_iterator_source_orphan (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4);
}

/**
 * @brief Get the OS UUID from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The OS UUID of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_os_id,
            GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the OS title from a host identifier iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The OS title of the host identifier, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (host_identifier_iterator_os_title,
            GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Filter columns for host iterator.
 */
#define HOST_ITERATOR_FILTER_COLUMNS                                        \
 { GET_ITERATOR_FILTER_COLUMNS, "severity", "os", "oss", "hostname", "ip",  \
   "severity_level", "updated", "best_os_cpe", NULL }

/**
 * @brief Host iterator columns.
 */
#define HOST_ITERATOR_COLUMNS                                         \
 {                                                                    \
   GET_ITERATOR_COLUMNS (hosts),                                      \
   {                                                                  \
     "1",                                                             \
     "writable",                                                      \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "0",                                                             \
     "in_use",                                                        \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "(SELECT round (CAST (severity AS numeric), 1)"                  \
     " FROM host_max_severities"                                      \
     " WHERE host = hosts.id"                                         \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT CASE"                                                   \
     "        WHEN best_os_text LIKE '%[possible conflict]%'"         \
     "        THEN best_os_text"                                      \
     "        WHEN best_os_cpe IS NULL"                               \
     "        THEN '[unknown]'"                                       \
     "        ELSE best_os_cpe"                                       \
     "        END"                                                    \
     " FROM (SELECT (SELECT value"                                    \
     "               FROM (SELECT max (id) AS id"                     \
     "                     FROM host_details"                         \
     "                     WHERE host = hosts.id"                     \
     "                     AND name = 'best_os_cpe')"                 \
     "                     AS sub,"                                   \
     "                    host_details"                               \
     "               WHERE sub.id = host_details.id)"                 \
     "              AS best_os_cpe,"                                  \
     "              (SELECT value"                                    \
     "               FROM (SELECT max (id) AS id"                     \
     "                     FROM host_details"                         \
     "                     WHERE host = hosts.id"                     \
     "                     AND name = 'best_os_text')"                \
     "                     AS sub,"                                   \
     "                    host_details"                               \
     "               WHERE sub.id = host_details.id)"                 \
     "              AS best_os_text)"                                 \
     "      AS vars)",                                                \
     "os",                                                            \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT group_concat (name, ', ') FROM oss"                     \
     "  WHERE id IN (SELECT distinct os FROM host_oss"                \
     "               WHERE host = hosts.id))",                        \
     "oss",                                                           \
     KEYWORD_TYPE_INTEGER                                             \
   },                                                                 \
   {                                                                  \
     "(SELECT value"                                                  \
     " FROM host_identifiers"                                         \
     " WHERE host = hosts.id"                                         \
     " AND name = 'hostname'"                                         \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "hostname",                                                      \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "(SELECT value"                                                  \
     " FROM host_identifiers"                                         \
     " WHERE host = hosts.id"                                         \
     " AND name = 'ip'"                                               \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "ip",                                                            \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

/**
 * @brief Host iterator WHERE columns.
 */
#define HOST_ITERATOR_WHERE_COLUMNS                                   \
 {                                                                    \
   {                                                                  \
     "(SELECT severity_to_level (CAST (severity AS numeric), 0)"      \
     " FROM host_max_severities"                                      \
     " WHERE host = hosts.id"                                         \
     " ORDER by creation_time DESC"                                   \
     " LIMIT 1)",                                                     \
     "severity_level",                                                \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   {                                                                  \
     "modification_time", "updated", KEYWORD_TYPE_INTEGER             \
   },                                                                 \
   {                                                                  \
     "(SELECT value"                                                  \
     "   FROM (SELECT max (id) AS id"                                 \
     "           FROM host_details"                                   \
     "          WHERE host = hosts.id"                                \
     "            AND name = 'best_os_cpe')"                          \
     "         AS sub, host_details"                                  \
     "  WHERE sub.id = host_details.id)",                             \
     "best_os_cpe",                                                   \
     KEYWORD_TYPE_STRING                                              \
   },                                                                 \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                               \
 }

/**
 * @brief Extra WHERE clause for host assets.
 *
 * @param[in]  filter  Filter term.
 *
 * @return WHERE clause.
 */
static gchar*
asset_host_extra_where (const char *filter)
{
  gchar *ret, *os_id;

  os_id = filter_term_value (filter, "os_id");

  if (os_id)
    {
      gchar *quoted_os_id = os_id ? sql_quote (os_id) : NULL;
      ret = g_strdup_printf (" AND EXISTS"
                             "  (SELECT * FROM host_oss"
                             "   WHERE os = (SELECT id FROM oss"
                             "                WHERE uuid = '%s')"
                             "     AND host = hosts.id)",
                             quoted_os_id);
      g_free (quoted_os_id);
    }
  else
    ret = g_strdup ("");

  g_free (os_id);

  return ret;
}

/**
 * @brief Initialise a host iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find host, 2 failed to find filter,
 *         -1 error.
 */
int
init_asset_host_iterator (iterator_t *iterator, const get_data_t *get)
{
  static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = HOST_ITERATOR_COLUMNS;
  static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS;

  int ret;
  gchar *filter, *extra_where;

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

  extra_where = asset_host_extra_where (filter ? filter : get->filter);

  ret = init_get_iterator2 (iterator,
                            "host",
                            get,
                            /* Columns. */
                            columns,
                            /* Columns for trashcan. */
                            NULL,
                            /* WHERE Columns. */
                            where_columns,
                            /* WHERE Columns for trashcan. */
                            NULL,
                            filter_columns,
                            0,
                            NULL,
                            extra_where,
                            NULL,
                            TRUE,
                            FALSE,
                            NULL);

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

/**
 * @brief Initialise a host iterator for GET_RESOURCE_NAMES.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find host, 2 failed to find filter,
 *         -1 error.
 */
int
init_resource_names_host_iterator (iterator_t *iterator, get_data_t *get)
{
  static const char *filter_columns[] = { GET_ITERATOR_FILTER_COLUMNS };
  static column_t columns[] = { GET_ITERATOR_COLUMNS (hosts) };
  int ret;

  ret = init_get_iterator2 (iterator,
                            "host",
                            get,
                            /* Columns. */
                            columns,
                            /* Columns for trashcan. */
                            NULL,
                            /* WHERE Columns. */
                            NULL,
                            /* WHERE Columns for trashcan. */
                            NULL,
                            filter_columns,
                            0,
                            NULL,
                            NULL,
                            NULL,
                            TRUE,
                            FALSE,
                            NULL);

  return ret;
}

/**
 * @brief Get the writable status from an asset iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if writable, else 0.
 */
int
asset_iterator_writable (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT);
}

/**
 * @brief Get the "in use" status from an asset iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return 1 if in use, else 0.
 */
int
asset_iterator_in_use (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 1);
}

/**
 * @brief Get the max severity from an asset host iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The maximum severity of the host, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Count number of hosts.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of hosts in filtered set.
 */
int
asset_host_count (const get_data_t *get)
{
  static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = HOST_ITERATOR_COLUMNS;
  static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS;
  return count2 ("host", get, columns, NULL, where_columns, NULL,
                 filter_columns, 0, NULL, NULL, NULL, TRUE);
}

/**
 * @brief Filter columns for os iterator.
 */
#define OS_ITERATOR_FILTER_COLUMNS                                           \
 { GET_ITERATOR_FILTER_COLUMNS, "title", "hosts", "latest_severity",         \
   "highest_severity", "average_severity", "average_severity_score",         \
   "severity", "all_hosts", NULL }

/**
 * @brief OS iterator columns.
 */
#define OS_ITERATOR_COLUMNS                                                   \
 {                                                                            \
   GET_ITERATOR_COLUMNS (oss),                                                \
   {                                                                          \
     "0",                                                                     \
     "writable",                                                              \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT count (*) > 0 FROM host_oss WHERE os = oss.id)",                \
     "in_use",                                                                \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT coalesce (cpe_title (oss.name), ''))",                          \
     "title",                                                                 \
     KEYWORD_TYPE_STRING                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT count(*)"                                                       \
     " FROM (SELECT inner_cpes[1] AS cpe, host"                               \
     "       FROM (SELECT array_agg (host_details.value"                      \
     "                               ORDER BY host_details.id DESC)"          \
     "                    AS inner_cpes,"                                     \
     "                    host"                                               \
     "             FROM host_details, hosts"                                  \
     "             WHERE host_details.name = 'best_os_cpe'"                   \
     "             AND hosts.id = host_details.host"                          \
     "             AND (" ACL_USER_MAY_OPTS ("hosts") ")"                     \
     "             GROUP BY host)"                                            \
     "            AS host_details_subselect)"                                 \
     "      AS array_removal_subselect"                                       \
     " WHERE cpe = oss.name)",                                                \
     "hosts",                                                                 \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   {                                                                          \
     "(SELECT round (CAST (severity AS numeric), 1) FROM host_max_severities" \
     " WHERE host = (SELECT host FROM host_oss"                               \
     "               WHERE os = oss.id"                                       \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     " ORDER BY creation_time DESC LIMIT 1)",                                 \
     "latest_severity",                                                       \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT round (max (CAST (severity AS numeric)), 1)"                    \
     " FROM host_max_severities"                                              \
     " WHERE host IN (SELECT DISTINCT host FROM host_oss"                     \
     "                WHERE os = oss.id))",                                   \
     "highest_severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT round (CAST (avg (severity) AS numeric), 2)"                    \
     " FROM (SELECT (SELECT severity FROM host_max_severities"                \
     "               WHERE host = hosts.host"                                 \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     "              AS severity"                                              \
     "       FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)"     \
     "       AS hosts)"                                                       \
     " AS severities)",                                                       \
     "average_severity",                                                      \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT count(DISTINCT host) FROM host_oss WHERE os = oss.id)",         \
     "all_hosts",                                                             \
     KEYWORD_TYPE_INTEGER                                                     \
   },                                                                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief OS iterator optional filtering columns.
 */
#define OS_ITERATOR_WHERE_COLUMNS                                             \
 {                                                                            \
   {                                                                          \
     "(SELECT round (CAST (avg (severity) AS numeric)"                        \
     "               * (SELECT count (distinct host)"                         \
     "                  FROM host_oss WHERE os = oss.id), 2)"                 \
     " FROM (SELECT (SELECT severity FROM host_max_severities"                \
     "               WHERE host = hosts.host"                                 \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     "              AS severity"                                              \
     "       FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)"     \
     "       AS hosts)"                                                       \
     " AS severities)",                                                       \
     "average_severity_score",                                                \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   {                                                                          \
     "(SELECT round (CAST (avg (severity) AS numeric), 2)"                    \
     " FROM (SELECT (SELECT severity FROM host_max_severities"                \
     "               WHERE host = hosts.host"                                 \
     "               ORDER BY creation_time DESC LIMIT 1)"                    \
     "              AS severity"                                              \
     "       FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)"     \
     "       AS hosts)"                                                       \
     " AS severities)",                                                       \
     "severity",                                                              \
     KEYWORD_TYPE_DOUBLE                                                      \
   },                                                                         \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                       \
 }

/**
 * @brief Generate the extra_tables string for an OS iterator.
 *
 * @return Newly allocated string.
 */
static gchar *
asset_os_iterator_opts_table ()
{
  assert (current_credentials.uuid);

  return g_strdup_printf (", (SELECT"
                          "   (SELECT id FROM users"
                          "    WHERE users.uuid = '%s')"
                          "   AS user_id,"
                          "   'host' AS type)"
                          "  AS opts",
                          current_credentials.uuid);
}

/**
 * @brief Initialise an OS iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find os, 2 failed to find filter,
 *         -1 error.
 */
int
init_asset_os_iterator (iterator_t *iterator, const get_data_t *get)
{
  int ret;
  static const char *filter_columns[] = OS_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OS_ITERATOR_COLUMNS;
  static column_t where_columns[] = OS_ITERATOR_WHERE_COLUMNS;
  gchar *extra_tables;

  extra_tables = asset_os_iterator_opts_table ();

  ret = init_get_iterator2_with (iterator,
                                 "os",
                                 get,
                                 /* Columns. */
                                 columns,
                                 /* Columns for trashcan. */
                                 NULL,
                                 /* WHERE Columns. */
                                 where_columns,
                                 /* WHERE Columns for trashcan. */
                                 NULL,
                                 filter_columns,
                                 0,
                                 extra_tables,
                                 NULL,
                                 NULL,
                                 TRUE,
                                 FALSE,
                                 NULL,
                                 NULL,
                                 0,
                                 0);

  g_free (extra_tables);

  return ret;
}

/**
 * @brief Initialise an OS iterator for GET_RESOURCE_NAMES.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find os, 2 failed to find filter,
 *         -1 error.
 */
int
init_resource_names_os_iterator (iterator_t *iterator, get_data_t *get)
{
  static const char *filter_columns[] = { GET_ITERATOR_FILTER_COLUMNS };
  static column_t columns[] = { GET_ITERATOR_COLUMNS (oss) };
  int ret;

  ret = init_get_iterator2_with (iterator,
                                 "os",
                                 get,
                                 /* Columns. */
                                 columns,
                                 /* Columns for trashcan. */
                                 NULL,
                                 /* WHERE Columns. */
                                 NULL,
                                 /* WHERE Columns for trashcan. */
                                 NULL,
                                 filter_columns,
                                 0,
                                 NULL,
                                 NULL,
                                 NULL,
                                 TRUE,
                                 FALSE,
                                 NULL,
                                 NULL,
                                 0,
                                 0);

  return ret;
}

/**
 * @brief Get the title from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The title of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_title, GET_ITERATOR_COLUMN_COUNT + 2);

/**
 * @brief Get the number of installs from an asset OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Number of hosts that have the OS.
 */
int
asset_os_iterator_installs (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3);
}

/**
 * @brief Get the latest severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_latest_severity, GET_ITERATOR_COLUMN_COUNT + 4);

/**
 * @brief Get the highest severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_highest_severity, GET_ITERATOR_COLUMN_COUNT + 5);

/**
 * @brief Get the average severity from an OS iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS, or NULL if iteration is
 *         complete. Freed by cleanup_iterator.
 */
DEF_ACCESS (asset_os_iterator_average_severity, GET_ITERATOR_COLUMN_COUNT + 6);

/**
 * @brief Get the number of all installs from an asset OS iterator.
 *
 * This includes hosts where the OS is not the best match.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Number of any hosts that have the OS not only as the best match.
 */
int
asset_os_iterator_all_installs (iterator_t* iterator)
{
  if (iterator->done) return 0;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 7);
}

/**
 * @brief Count number of oss.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of oss in filtered set.
 */
int
asset_os_count (const get_data_t *get)
{
  static const char *extra_columns[] = OS_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = OS_ITERATOR_COLUMNS;
  static column_t where_columns[] = OS_ITERATOR_WHERE_COLUMNS;
  int ret;

  ret = count2 ("os", get, columns, NULL, where_columns, NULL,
                extra_columns, 0, 0, 0, NULL, TRUE);

  return ret;
}

/**
 * @brief Initialise an OS host iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  os          OS.
 */
void
init_os_host_iterator (iterator_t* iterator, resource_t os)
{
  assert (os);
  init_iterator (iterator,
                 "SELECT id, uuid, name, comment, creation_time,"
                 "       modification_time, creation_time,"
                 "       modification_time, owner, owner,"
                 "       (SELECT round (CAST (severity AS numeric), 1)"
                 "        FROM host_max_severities"
                 "        WHERE host = hosts.id"
                 "        ORDER by creation_time DESC"
                 "        LIMIT 1)"
                 " FROM hosts"
                 " WHERE id IN (SELECT DISTINCT host FROM host_oss"
                 "              WHERE os = %llu)"
                 " ORDER BY modification_time DESC;",
                 os);
}

/**
 * @brief Get the severity from an OS host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The severity of the OS host, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (os_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Initialise an asset host detail iterator.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  host        Host.
 */
void
init_host_detail_iterator (iterator_t* iterator, resource_t host)
{
  assert (host);
  init_iterator (iterator,
                 "SELECT sub.id, name, value, source_type, source_id"
                 " FROM (SELECT max (id) AS id FROM host_details"
                 "       WHERE host = %llu"
                 "       GROUP BY name)"
                 "      AS sub,"
                 "      host_details"
                 " WHERE sub.id = host_details.id"
                 " ORDER BY name ASC;",
                 host);
}

/**
 * @brief Get the name from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_name, 1);

/**
 * @brief Get the name from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The name of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_value, 2);

/**
 * @brief Get the source type from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source type of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_source_type, 3);

/**
 * @brief Get the source ID from an asset host detail iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return The source ID of the host detail, or NULL if iteration is
 *         complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (host_detail_iterator_source_id, 4);

/**
 * @brief Find a host for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of host.
 * @param[out]  host      Host return, 0 if successfully failed to find host.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find host), TRUE on error.
 */
static gboolean
find_host_with_permission (const char* uuid, host_t* host,
                           const char *permission)
{
  return find_resource_with_permission ("host", uuid, host, permission, 0);
}

/**
 * @brief Check whether a string is an identifier name.
 *
 * @param[in]  name  Possible identifier name.
 *
 * @return 1 if an identifier name, else 0.
 */
static int
identifier_name (const char *name)
{
  return (strcmp ("hostname", name) == 0)
         || (strcmp ("MAC", name) == 0)
         || (strcmp ("OS", name) == 0)
         || (strcmp ("ssh-key", name) == 0);
}

/**
 * @brief Create a host asset.
 *
 * @param[in]  host_name    Host Name.
 * @param[in]  comment      Comment.
 * @param[out] host_return  Created asset.
 *
 * @return 0 success, 1 failed to find report, 2 host not an IP address,
 *         99 permission denied, -1 error.
 */
int
create_asset_host (const char *host_name, const char *comment,
                   resource_t* host_return)
{
  int host_type;
  resource_t host;
  gchar *quoted_host_name, *quoted_comment;

  if (host_name == NULL)
    return -1;

  sql_begin_immediate ();

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

  host_type = gvm_get_host_type (host_name);
  if (host_type != HOST_TYPE_IPV4 && host_type != HOST_TYPE_IPV6)
    {
      sql_rollback ();
      return 2;
    }

  quoted_host_name = sql_quote (host_name);
  quoted_comment = sql_quote (comment ? comment : "");
  sql ("INSERT into hosts"
       " (uuid, owner, name, comment, creation_time, modification_time)"
       " VALUES"
       " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', '%s',"
       "  m_now (), m_now ());",
       current_credentials.uuid,
       quoted_host_name,
       quoted_comment);
  g_free (quoted_comment);

  host = sql_last_insert_id ();

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

  g_free (quoted_host_name);

  if (host_return)
    *host_return = host;

  sql_commit ();

  return 0;
}

/**
 * @brief Create all available assets from a report.
 *
 * @param[in]  report_id  UUID of report.
 * @param[in]  term       Filter term, for min_qod and apply_overrides.
 *
 * @return 0 success, 1 failed to find report, 99 permission denied, -1 error.
 */
int
create_asset_report (const char *report_id, const char *term)
{
  resource_t report;
  iterator_t hosts;
  gchar *quoted_report_id;
  int apply_overrides, min_qod;

  if (report_id == NULL)
    return -1;

  sql_begin_immediate ();

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

  report = 0;
  if (find_report_with_permission (report_id, &report, "get_reports"))
    {
      sql_rollback ();
      return -1;
    }

  if (report == 0)
    {
      sql_rollback ();
      return 1;
    }

  /* These are freed by hosts_set_identifiers. */
  if (identifiers == NULL)
    identifiers = make_array ();
  if (identifier_hosts == NULL)
    identifier_hosts = make_array ();

  quoted_report_id = sql_quote (report_id);
  sql ("DELETE FROM host_identifiers WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_oss WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_max_severities WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_details WHERE source_id = '%s';",
       quoted_report_id);
  g_free (quoted_report_id);

  init_report_host_iterator (&hosts, report, NULL, 0);
  while (next (&hosts))
    {
      const char *host;
      report_host_t report_host;
      iterator_t details;

      host = host_iterator_host (&hosts);
      report_host = host_iterator_report_host (&hosts);

      if (report_host_dead (report_host)
          || report_host_result_count (report_host) == 0)
        continue;

      host_notice (host, "ip", host, "Report Host", report_id, 0, 0);

      init_report_host_details_iterator (&details, report_host);
      while (next (&details))
        {
          const char *name;

          name = report_host_details_iterator_name (&details);

          if (identifier_name (name))
            {
              const char *value;
              identifier_t *identifier;

              value = report_host_details_iterator_value (&details);

              if ((strcmp (name, "OS") == 0)
                  && (g_str_has_prefix (value, "cpe:") == 0))
                continue;

              identifier = g_malloc (sizeof (identifier_t));
              identifier->ip = g_strdup (host);
              identifier->name = g_strdup (name);
              identifier->value = g_strdup (value);
              identifier->source_id = g_strdup (report_id);
              identifier->source_type = g_strdup ("Report Host Detail");
              identifier->source_data
               = g_strdup (report_host_details_iterator_source_name (&details));

              array_add (identifiers, identifier);
              array_add_new_string (identifier_hosts, g_strdup (host));
            }
        }
      cleanup_iterator (&details);
    }
  cleanup_iterator (&hosts);
  hosts_set_identifiers (report);
  apply_overrides = filter_term_apply_overrides (term);
  min_qod = filter_term_min_qod (term);
  hosts_set_max_severity (report, &apply_overrides, &min_qod);
  hosts_set_details (report);

  sql_commit ();

  return 0;
}

/**
 * @brief Modify an asset.
 *
 * @param[in]   asset_id        UUID of asset.
 * @param[in]   comment         Comment on asset.
 *
 * @return 0 success, 1 failed to find asset, 3 asset_id required,
 *         99 permission denied, -1 internal error.
 */
int
modify_asset (const char *asset_id, const char *comment)
{
  gchar *quoted_asset_id, *quoted_comment;
  resource_t asset;

  if (asset_id == NULL)
    return 3;

  sql_begin_immediate ();

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

  /* Host. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM hosts WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset == 0)
    {
      sql_rollback ();
      return 1;
    }

  quoted_comment = sql_quote (comment ?: "");

  sql ("UPDATE hosts SET"
       " comment = '%s',"
       " modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_comment, asset);

  g_free (quoted_comment);

  sql_commit ();

  return 0;
}

/**
 * @brief Delete all asset that came from a report.
 *
 * Assume caller started a transaction.
 *
 * @param[in]  report_id  UUID of report.
 *
 * @return 0 success, 2 failed to find report, 4 UUID
 *         required, 99 permission denied, -1 error.
 */
static int
delete_report_assets (const char *report_id)
{
  resource_t report;
  gchar *quoted_report_id;

  report = 0;
  if (find_report_with_permission (report_id, &report, "delete_report"))
    {
      sql_rollback ();
      return -1;
    }

  if (report == 0)
    {
      sql_rollback ();
      return 1;
    }

  quoted_report_id = sql_quote (report_id);

  /* Delete the hosts and OSs identified by this report if they were only
   * identified by this report. */

  sql ("CREATE TEMPORARY TABLE delete_report_assets_hosts (host INTEGER);");

  /* Collect hosts that were only identified by the given source. */
  sql ("INSERT into delete_report_assets_hosts"
       " (host)"
       " SELECT id FROM hosts"
       " WHERE (EXISTS (SELECT * FROM host_identifiers"
       "                WHERE host = hosts.id"
       "                AND source_id = '%s')"
       "        OR EXISTS (SELECT * FROM host_oss"
       "                   WHERE host = hosts.id"
       "                   AND source_id = '%s'))"
       " AND NOT EXISTS (SELECT * FROM host_identifiers"
       "                 WHERE host = hosts.id"
       "                 AND source_id != '%s')"
       " AND NOT EXISTS (SELECT * FROM host_oss"
       "                 WHERE host = hosts.id"
       "                 AND source_id != '%s');",
      quoted_report_id,
      quoted_report_id,
      quoted_report_id,
      quoted_report_id);

  sql ("DELETE FROM host_identifiers WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_oss WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_max_severities WHERE source_id = '%s';",
       quoted_report_id);
  sql ("DELETE FROM host_details WHERE source_id = '%s';",
       quoted_report_id);

  g_free (quoted_report_id);

  /* The host may have details from sources that did not identify the host. */
  sql ("DELETE FROM host_details"
       " WHERE host in (SELECT host FROM delete_report_assets_hosts);");

  /* The host may have severities from sources that did not identify the
   * host. */
  sql ("DELETE FROM host_max_severities"
       " WHERE host in (SELECT host FROM delete_report_assets_hosts);");

  sql ("DELETE FROM hosts"
       " WHERE id in (SELECT host FROM delete_report_assets_hosts);");

  sql ("DROP TABLE delete_report_assets_hosts;");

  sql_commit ();
  return 0;
}

/**
 * @brief Delete an asset.
 *
 * @param[in]  asset_id   UUID of asset.
 * @param[in]  report_id  UUID of report from which to delete assets.
 *                        Overridden by asset_id.
 * @param[in]  dummy      Dummy arg to match other delete functions.
 *
 * @return 0 success, 1 asset is in use, 2 failed to find asset, 4 UUID
 *         required, 99 permission denied, -1 error.
 */
int
delete_asset (const char *asset_id, const char *report_id, int dummy)
{
  resource_t asset, parent;
  gchar *quoted_asset_id, *parent_id;

  asset = parent = 0;

  sql_begin_immediate ();

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

  if (asset_id == NULL)
    {
      if (report_id == NULL)
        {
          sql_rollback ();
          return 3;
        }
      return delete_report_assets (report_id);
    }

  /* Host identifier. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM host_identifiers WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      parent_id = sql_string ("SELECT uuid FROM hosts"
                              " WHERE id = (SELECT host FROM host_identifiers"
                              "             WHERE id = %llu);",
                              asset);
      parent = 0;
      if (find_host_with_permission (parent_id, &parent, "delete_asset"))
        {
          sql_rollback ();
          return -1;
        }

      if (parent == 0)
        {
          sql_rollback ();
          return 99;
        }

      sql ("DELETE FROM host_identifiers WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  /* Host OS. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM host_oss WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      parent_id = sql_string ("SELECT uuid FROM hosts"
                              " WHERE id = (SELECT host FROM host_oss"
                              "             WHERE id = %llu);",
                              asset);
      parent = 0;
      if (find_host_with_permission (parent_id, &parent, "delete_asset"))
        {
          sql_rollback ();
          return -1;
        }

      if (parent == 0)
        {
          sql_rollback ();
          return 99;
        }

      sql ("DELETE FROM host_oss WHERE id = %llu;", asset);
      sql_commit ();

      return 0;
    }

  /* OS. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM oss WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      if (sql_int ("SELECT count (*) FROM host_oss"
                   " WHERE os = %llu;",
                   asset))
        {
          sql_rollback ();
          return 1;
        }

      sql ("DELETE FROM oss WHERE id = %llu;", asset);
      permissions_set_orphans ("os", asset, LOCATION_TABLE);
      tags_remove_resource ("os", asset, LOCATION_TABLE);
      sql_commit ();

      return 0;
    }

  /* Host. */

  quoted_asset_id = sql_quote (asset_id);
  switch (sql_int64 (&asset,
                     "SELECT id FROM hosts WHERE uuid = '%s';",
                     quoted_asset_id))
    {
      case 0:
        break;
      case 1:        /* Too few rows in result of query. */
        asset = 0;
        break;
      default:       /* Programming error. */
        assert (0);
      case -1:
        g_free (quoted_asset_id);
        sql_rollback ();
        return -1;
        break;
    }

  g_free (quoted_asset_id);

  if (asset)
    {
      sql ("DELETE FROM host_identifiers WHERE host = %llu;", asset);
      sql ("DELETE FROM host_oss WHERE host = %llu;", asset);
      sql ("DELETE FROM host_max_severities WHERE host = %llu;", asset);
      sql ("DELETE FROM host_details WHERE host = %llu;", asset);
      sql ("DELETE FROM hosts WHERE id = %llu;", asset);
      permissions_set_orphans ("host", asset, LOCATION_TABLE);
      tags_remove_resource ("host", asset, LOCATION_TABLE);
      sql_commit ();

      return 0;
    }

  sql_rollback ();
  return 2;
}

/**
 * @brief Generates and adds assets from report host details
 *
 * @param[in]  report   The report to get host details from.
 * @param[in]  host_ip  IP address of the host to get details from.
 *
 * @return 0 success, -1 error.
 */
int
add_assets_from_host_in_report (report_t report, const char *host_ip)
{
  int ret;
  gchar *quoted_host;
  char *report_id;
  report_host_t report_host = 0;

  /* Get report UUID */
  report_id = report_uuid (report);
  if (report_id == NULL)
    {
      g_warning ("%s: report %llu not found.",
                 __func__, report);
      return -1;
    }

  /* Find report_host */
  quoted_host = sql_quote (host_ip);
  sql_int64 (&report_host,
             "SELECT id FROM report_hosts"
             " WHERE host = '%s' AND report = %llu",
             quoted_host,
             report);
  g_free (quoted_host);
  if (report_host == 0)
    {
      g_warning ("%s: report_host for host '%s' and report '%s' not found.",
                 __func__, host_ip, report_id);
      free (report_id);
      return -1;
    }

  /* Create assets */
  if (report_host_noticeable (report, host_ip))
    {
      host_notice (host_ip, "ip", host_ip, "Report Host", report_id, 1, 1);
    }

  ret = add_tls_certificates_from_report_host (report_host,
                                               report_id,
                                               host_ip);
  if (ret)
    {
      free (report_id);
      return ret;
    }

  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, 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.
 */
static 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 = 'a09285b0-2d47-49b6-a4ef-946ee71f1d5c'"
                  "          AND " ACL_USER_OWNS () ""
                  "          ORDER BY coalesce (owner, 0) DESC LIMIT 1),"
                  "         '1');",
                  current_credentials.uuid);
}

/**
 * @brief Return user setting as int.
 *
 * @return User setting.
 */
static int
setting_delta_reports_version_int ()
{
  int version;

  setting_value_int (SETTING_UUID_DELTA_REPORTS_VERSION, &version);

  return version;
}

/**
 * @brief Initialise a setting iterator, including observed settings.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  uuid        UUID of setting to limit iteration to.  0 for all.
 * @param[in]  filter      Filter term.
 * @param[in]  first       First setting.
 * @param[in]  max         Maximum number of settings returned.
 * @param[in]  ascending   Whether to sort ascending or descending.
 * @param[in]  sort_field  Field to sort on, or NULL for "id".
 */
void
init_setting_iterator (iterator_t *iterator, const char *uuid,
                       const char *filter, int first, int max, int ascending,
                       const char *sort_field)
{
  static const char *filter_columns[] = SETTING_ITERATOR_FILTER_COLUMNS;
  static column_t select_columns[] = SETTING_ITERATOR_COLUMNS;
  gchar *clause, *columns, *quoted_uuid;

  assert (current_credentials.uuid);

  if (first < 0)
    first = 0;
  if (max < 1)
    max = -1;

  clause = filter_clause ("setting", filter, filter_columns, select_columns,
                          NULL, 0, NULL, NULL, NULL, NULL, NULL);

  quoted_uuid = uuid ? sql_quote (uuid) : NULL;
  columns = columns_build_select (select_columns);

  if (quoted_uuid)
    init_iterator (iterator,
                   "SELECT %s"
                   " FROM settings"
                   " WHERE uuid = '%s'"
                   " AND (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 (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 (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_ROWS_PER_PAGE) == 0
               || strcmp (uuid, SETTING_UUID_EXCERPT_SIZE) == 0
               || strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0
               || strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0
               || strcmp (uuid, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0
               || strcmp (uuid, "578a1c14-e2dc-45ef-a591-89d31391d007") == 0
               || strcmp (uuid, "02e294fa-061b-11e6-ae64-28d24461215b") == 0
               || strcmp (uuid, "5a9046cc-0628-11e6-ba53-28d24461215b") == 0
               || strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 0
               || strcmp (uuid, "11deb7ff-550b-4950-aacf-06faeb7c61b9") == 0
               || strcmp (uuid, "d9857b7c-1159-4193-9bc0-18fae5473a69") == 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, "6765549a-934e-11e3-b358-406186ea4fc5") == 0)
        {
          GRegex *languages_regex;
          gboolean match;
          /*
           * regex: colon-separated lists of language or language and country
           *  codes (ISO 639-1, 639-2 and 3166-1 alpha-2)
           *  as used in the LANGUAGE env variable by gettext
           */
          languages_regex
            = g_regex_new ("^(Browser Language|"
                           "([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_\\-]+)?"
                           "(:([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_\\-]+)?)*)$",
                           0, 0, NULL);
          match = g_regex_match (languages_regex, value, 0, NULL);
          g_regex_unref (languages_regex);

          /* User Interface Language. */
          if (match)
            {
              // Valid languages string or "Browser Language":
              //  keep string as it is
            }
          /* Legacy full language names */
          else if (strcmp (value, "Chinese") == 0)
            {
              g_free (value);
              value = g_strdup ("zh_CN");
            }
          else if (strcmp (value, "English") == 0)
            {
              g_free (value);
              value = g_strdup ("en");
            }
          else if (strcmp (value, "German") == 0)
            {
              g_free (value);
              value = g_strdup ("de");
            }
          /* Invalid value */
          else
            {
              g_free (quoted_uuid);
              g_free (value);
              return 2;
            }
        }

      if (strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 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, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0)
        {
          double severity_dbl;
          /* Default Severity */
          if (sscanf (value, "%lf", &severity_dbl) != 1
              || severity_dbl < 0.0 || severity_dbl > 10.0)
            {
              g_free (value);
              return 2;
            }
          else
            current_credentials.default_severity = severity_dbl;
        }

      if (strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 0)
        {
          int value_int;
          /* Auto Cache Rebuild */
          if (sscanf (value, "%d", &value_int) != 1
              || (strcmp (value, "0") && strcmp (value, "1")))
            {
              g_free (value);
              return 2;
            }
        }

      if (strcmp (uuid, "11deb7ff-550b-4950-aacf-06faeb7c61b9") == 0)
        {
          int value_int;
          /* User Interface Time Format */
          if (sscanf (value, "%d", &value_int) != 1
              || (strcmp (value, "12") && strcmp (value, "24")))
            {
              g_free (value);
              return 2;
            }
        }

      if (strcmp (uuid, "d9857b7c-1159-4193-9bc0-18fae5473a69") == 0)
        {
          /* User Interface Date Format */
          if (strcmp (value, "wmdy") && strcmp (value, "wdmy"))
            {
              g_free (value);
              return 2;
            }
        }

      quoted_value = sql_quote (value);
      g_free (value);

      if (sql_int ("SELECT count(*) FROM settings"
                   " WHERE uuid = '%s'"
                   " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
                   quoted_uuid,
                   current_credentials.uuid))
        sql ("UPDATE settings SET value = '%s'"
             " WHERE uuid = '%s'"
             " AND owner = (SELECT id FROM users WHERE uuid = '%s');",
             quoted_value,
             quoted_uuid,
             current_credentials.uuid);
      else
        sql ("INSERT INTO settings (uuid, owner, name, comment, value)"
             " VALUES"
             " ('%s',"
             "  (SELECT id FROM users WHERE uuid = '%s'),"
             "  (SELECT name FROM settings"
             "   WHERE uuid = '%s' AND " ACL_IS_GLOBAL ()
             "   LIMIT 1),"
             "  (SELECT comment FROM settings"
             "   WHERE uuid = '%s' AND " ACL_IS_GLOBAL ()
             "   LIMIT 1),"
             "  '%s');",
             quoted_uuid,
             current_credentials.uuid,
             quoted_uuid,
             quoted_uuid,
             quoted_value);

      g_free (quoted_uuid);
      g_free (quoted_value);

      return 0;
    }

  /* Export file name format */
  if (uuid
      && (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0
          || strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0
          || strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0))
    {
      gsize value_size;
      gchar *value, *quoted_value;

      assert (current_credentials.uuid);
      if (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0)
        setting_name = "Details Export File Name";
      else if (strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0)
        setting_name = "List Export File Name";
      else if (strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0)
        setting_name = "Report Export File Name";
      else
        return -1;

      if (value_64 && strlen (value_64))
        {
          value = (gchar*) g_base64_decode (value_64, &value_size);
          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");
      else if (strcmp (uuid, "0f040d06-abf9-43a2-8f94-9de178b0e978") == 0)
        setting_name = g_strdup ("Assets Filter");
      #if COMPLIANCE_REPORTS == 1
        else if (strcmp (uuid, "45414da7-55f0-44c1-abbb-6b7d1126fbdf") == 0)
          setting_name = g_strdup ("Audit Reports Filter");
      #endif
      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");
      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 ("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 */
      #if COMPLIANCE_REPORTS == 1
        else if (strcmp (uuid, "8083d77b-05bb-4b17-ab39-c81175cb512c") == 0)
          setting_name = g_strdup ("Audit Reports Top Dashboard Configuration");
      #endif
      /* 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.
 *
 * @return Adjusted max.
 */
int
manage_max_rows (int max)
{
  int max_rows;

  if (current_credentials.uuid == NULL)
    return max;

  if (ignore_max_rows_per_page
      || setting_value_int (SETTING_UUID_MAX_ROWS_PER_PAGE, &max_rows))
    return max;

  if (max_rows && (max < 0 || max > max_rows))
    return max_rows;
  return max;
}

/**
 * @brief Get the name of a setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return Setting name.
 */
static const gchar *
setting_name (const gchar *uuid)
{
  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
    return "Default CA Cert";
  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    return "Max Rows Per Page";
  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_DELTA_REPORTS_VERSION) == 0)
    return "Delta Reports Version";
  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    return "SecInfo SQL Buffer Threshold";

  return NULL;
}

/**
 * @brief Check whether a setting is the Default CA Cert setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return 1 if Default CA Cert, else 0.
 */
int
setting_is_default_ca_cert (const gchar *uuid)
{
  return strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0;
}

/**
 * @brief Get the description of a setting.
 *
 * @param[in]  uuid  UUID of setting.
 *
 * @return Setting description.
 */
static const gchar *
setting_description (const gchar *uuid)
{
  if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0)
    return "Default CA Certificate for Scanners";
  if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0)
    return "The default maximum number of rows displayed in any listing.";
  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_DELTA_REPORTS_VERSION) == 0)
    return "Version of the generation of the Delta Reports.";
  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.";

  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
      && 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_DELTA_REPORTS_VERSION) == 0)
    {
      if (strcmp(value, "1") != 0 && strcmp(value, "2") != 0)
        return 1;
    }

  if (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 0)
    {
      int threshold;
      threshold = atoi (value);
      if (threshold < 0 || threshold > (INT_MAX / 1048576))
        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_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_DELTA_REPORTS_VERSION)
      && strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD))
    {
      fprintf (stderr, "Error in setting UUID.\n");
      return 3;
    }

  ret = manage_option_setup (log_config, database);
  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_DEFAULT_CA_CERT) == 0)
          || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0)
          || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)
          || (strcmp (uuid, SETTING_UUID_DELTA_REPORTS_VERSION) == 0)
          || (strcmp (uuid, SETTING_UUID_SECINFO_SQL_BUFFER_THRESHOLD) == 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 "';");
}


/* Users. */

/**
 * @brief Create the given user.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  name        Name of user.
 * @param[in]  password    Password for user.  Autogenerated if NULL.
 * @param[in]  role_name   Role of user.  Admin if NULL.
 *
 * @return 0 success, -1 error,
 *         -2 database is too old, -3 database needs to be initialised
 *         from server, -5 database is too new.
 */
int
manage_create_user (GSList *log_config, const db_conn_info_t *database,
                    const gchar *name, const gchar *password,
                    const gchar *role_name)
{
  char *uuid;
  array_t *roles;
  int ret;
  gchar *rejection_msg;

  g_info ("   Creating user.");

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

  roles = make_array ();
  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          array_free (roles);
          fprintf (stderr, "Internal Error.\n");
          manage_option_cleanup ();
          return -1;
        }
      if (role == 0)
        {
          array_free (roles);
          fprintf (stderr, "Failed to find role.\n");
          manage_option_cleanup ();
          return -1;
        }
      array_add (roles, role_uuid (role));
    }
  else
    array_add (roles, g_strdup (ROLE_UUID_ADMIN));

  if (password)
    uuid = NULL;
  else
    uuid = gvm_uuid_make ();

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

  ret = create_user (name, password ? password : uuid, "", NULL, 0,
                     NULL, NULL, NULL, roles, NULL, &rejection_msg, NULL, 0);

  switch (ret)
    {
      case 0:
        if (password)
          printf ("User created.\n");
        else
          printf ("User created with password '%s'.\n", uuid);
        break;
      case -2:
        fprintf (stderr, "User exists already.\n");
        break;
      default:
        if (rejection_msg)
          {
            fprintf (stderr, "Failed to create user: %s\n", rejection_msg);
          }
        else
          fprintf (stderr, "Failed to create user.\n");
        break;
    }

  current_credentials.uuid = NULL;
  g_free (rejection_msg);

  array_free (roles);
  free (uuid);

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief Delete the given user.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  name        Name of user.
 * @param[in]  inheritor_name  Name of user that inherits user's resources.
 *
 * @return 0 success, 2 failed to find user, 4 user has active tasks, -1 error.
 *         -2 database is too old, -3 database needs to be initialised
 *         from server, -5 database is too new.
 */
int
manage_delete_user (GSList *log_config, const db_conn_info_t *database,
                    const gchar *name, const gchar *inheritor_name)
{
  int ret;

  g_info ("   Deleting user.");

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

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

  switch ((ret = delete_user (NULL, name, 0, NULL, inheritor_name)))
    {
      case 0:
        printf ("User deleted.\n");
        break;
      case 2:
        fprintf (stderr, "Failed to find user.\n");
        break;
      case 4:
        fprintf (stderr, "User has active tasks.\n");
        break;
      case 6:
        fprintf (stderr, "Inheritor not found.\n");
        break;
      case 7:
        fprintf (stderr, "Inheritor same as deleted user.\n");
        break;
      case 8:
        fprintf (stderr, "Invalid inheritor.\n");
        break;
      case 9:
        fprintf (stderr,
                 "Resources owned by the user are still in use by others.\n");
        break;
      case 10:
        fprintf (stderr, "User is Feed Import Owner.\n");
        break;
      default:
        fprintf (stderr, "Internal Error.\n");
        break;
    }

  current_credentials.uuid = NULL;

  manage_option_cleanup ();

  return ret;
}

/**
 * @brief List users.
 *
 * @param[in]  log_config  Log configuration.
 * @param[in]  database    Location of manage database.
 * @param[in]  role_name   Role name.
 * @param[in]  verbose     Whether to print UUID.
 *
 * @return 0 success, -1 error.
 */
int
manage_get_users (GSList *log_config, const db_conn_info_t *database,
                  const gchar* role_name, int verbose)
{
  iterator_t users;
  int ret;

  g_info ("   Getting users.");

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

  if (role_name)
    {
      role_t role;
      if (find_role_by_name (role_name, &role))
        {
          fprintf (stderr, "Internal Error.\n");
          manage_option_cleanup ();
          return -1;
        }
      if (role == 0)
        {
          fprintf (stderr, "Failed to find role.\n");
          manage_option_cleanup ();
          return -1;
        }
      init_iterator (&users,
                     "SELECT name, uuid FROM users"
                     " WHERE id IN (SELECT \"user\" FROM role_users"
                     "              WHERE role = %llu);",
                     role);
    }
  else
    init_iterator (&users, "SELECT name, uuid FROM users;");
  while (next (&users))
    if (verbose)
      printf ("%s %s\n", iterator_string (&users, 0), iterator_string (&users, 1));
    else
      printf ("%s\n", iterator_string (&users, 0));

  cleanup_iterator (&users);

  manage_option_cleanup ();

  return 0;
}

/**
 * @brief Set the password of a user.
 *
 * @param[in]  name      Name of user.
 * @param[in]  uuid      User UUID.
 * @param[in]  password  New password.
 * @param[out] r_errdesc Address to receive a malloced string with the error
 *                       description, or NULL.
 *
 * @return 0 success, -1 error.
 */
static int
set_password (const gchar *name, const gchar *uuid, const gchar *password,
              gchar **r_errdesc)
{
  gchar *errstr, *hash;

  assert (name && uuid);

  if ((errstr = gvm_validate_password (password, name)))
    {
      g_warning ("new password for '%s' rejected: %s", name, errstr);
      if (r_errdesc)
        *r_errdesc = errstr;
      else
        g_free (errstr);
      return -1;
    }
  hash = manage_authentication_hash (password);
  sql ("UPDATE users SET password = '%s', modification_time = m_now ()"
       " WHERE uuid = '%s';",
       hash,
       uuid);
  g_free (hash);
  return 0;
}

/**
 * @brief Set the password of a user.
 *
 * @param[in]  log_config      Log configuration.
 * @param[in]  database  Location of manage database.
 * @param[in]  name      Name of user.
 * @param[in]  password  New password.
 *
 * @return 0 success, -1 error.
 */
int
manage_set_password (GSList *log_config, const db_conn_info_t *database,
                     const gchar *name, const gchar *password)
{
  user_t user;
  char *uuid;
  int ret;
  gchar *rejection_msg;

  g_info ("   Modifying user password.");

  if (name == NULL)
    {
      fprintf (stderr, "--user required.\n");
      return -1;
    }

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

  sql_begin_immediate ();

  if (find_user_by_name (name, &user))
    {
      fprintf (stderr, "Internal error.\n");
      goto fail;
    }

  if (user == 0)
    {
      fprintf (stderr, "Failed to find user.\n");
      goto fail;
    }

  uuid = user_uuid (user);
  if (uuid == NULL)
    {
      fprintf (stderr, "Failed to allocate UUID.\n");
      goto fail;
    }

  rejection_msg = NULL;
  if (set_password (name, uuid, password, &rejection_msg))
    {
      if (rejection_msg)
        {
          fprintf (stderr, "New password rejected: %s\n", rejection_msg);
          g_free (rejection_msg);
        }
      else
        fprintf (stderr, "New password rejected.\n");
      free (uuid);
      goto fail;
    }

  sql_commit ();
  free (uuid);
  manage_option_cleanup ();
  return ret;

 fail:
  sql_rollback ();
  manage_option_cleanup ();
  return -1;
}

/**
 * @brief Find a user for a specific permission, given a UUID.
 *
 * @param[in]   uuid        UUID of user.
 * @param[out]  user        User return, 0 if successfully failed to find user.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
static gboolean
find_user_with_permission (const char* uuid, user_t* user,
                           const char *permission)
{
  return find_resource_with_permission ("user", uuid, user, permission, 0);
}

/**
 * @brief Find a user given a name.
 *
 * @param[in]   name  A user name.
 * @param[out]  user  User return, 0 if successfully failed to find user.
 * @param[in]   permission  Permission.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
gboolean
find_user_by_name_with_permission (const char* name, user_t *user,
                                   const char *permission)
{
  return find_resource_by_name_with_permission ("user", name, user, permission);
}

/**
 * @brief Find a user given a name.
 *
 * @param[in]   name  A user name.
 * @param[out]  user  User return, 0 if successfully failed to find user.
 *
 * @return FALSE on success (including if failed to find user), TRUE on error.
 */
static gboolean
find_user_by_name (const char* name, user_t *user)
{
  return find_resource_by_name ("user", name, user);
}

/**
 * @brief Adds a new user to the GVM installation.
 *
 * @todo Adding users authenticating with certificates is not yet implemented.
 *
 * @param[in]  name         The name of the new user.
 * @param[in]  password     The password of the new user.
 * @param[in]  comment      Comment for the new user or NULL.
 * @param[in]  hosts        The host the user is allowed/forbidden to scan.
 * @param[in]  hosts_allow  Whether hosts is allow or forbid.
 * @param[in]  allowed_methods  Allowed login methods.
 * @param[in]  groups       Groups.
 * @param[out] group_id_return  ID of group on "failed to find" error.
 * @param[in]  roles        Roles.
 * @param[out] role_id_return  ID of role on "failed to find" error.
 * @param[out] r_errdesc    If not NULL the address of a variable to receive
 *                          a malloced string with the error description.  Will
 *                          always be set to NULL on success.
 * @param[out] new_user     Created user.
 * @param[in]  forbid_super_admin  Whether to forbid creation of Super Admin.
 *
 * @return 0 if the user has been added successfully, 1 failed to find group,
 *         2 failed to find role, 3 syntax error in hosts, 99 permission denied,
 *         -1 on error, -2 if user exists already, -3 if wrong number of methods,
 *         -4 error in method.
 */
int
create_user (const gchar * name, const gchar * password, const gchar *comment,
             const gchar * hosts, int hosts_allow,
             const array_t * allowed_methods, array_t *groups,
             gchar **group_id_return, array_t *roles, gchar **role_id_return,
             gchar **r_errdesc, user_t *new_user, int forbid_super_admin)
{
  char *errstr, *uuid;
  gchar *quoted_hosts, *quoted_method, *quoted_name, *hash;
  gchar *quoted_comment, *clean, *generated;
  int index, max, ret;
  user_t user;
  GArray *cache_users;

  assert (name);
  assert (password);

  if (r_errdesc)
    *r_errdesc = NULL;

  /* allowed_methods is a NULL terminated array. */
  if (allowed_methods && (allowed_methods->len > 2))
    return -3;

  if (allowed_methods && (allowed_methods->len <= 1))
    allowed_methods = NULL;

  if (allowed_methods
      && (auth_method_name_valid (g_ptr_array_index (allowed_methods, 0))
          == 0))
    return -4;

  if (validate_username (name) != 0)
    {
      g_warning ("Invalid characters in user name!");
      if (r_errdesc)
        *r_errdesc = g_strdup ("Invalid characters in user name");
      return -1;
    }

  if (allowed_methods &&
      (!strcmp (g_ptr_array_index (allowed_methods, 0), "ldap_connect")
       || !strcmp (g_ptr_array_index (allowed_methods, 0), "radius_connect")))
    password = generated = gvm_uuid_make ();
  else
    generated = NULL;

  if ((errstr = gvm_validate_password (password, name)))
    {
      g_warning ("new password for '%s' rejected: %s", name, errstr);
      if (r_errdesc)
        *r_errdesc = errstr;
      else
        g_free (errstr);
      g_free (generated);
      return -1;
    }

  sql_begin_immediate ();

  if (acl_user_may ("create_user") == 0)
    {
      sql_rollback ();
      g_free (generated);
      return 99;
    }

  /* Check if user exists already. */

  if (resource_with_name_exists_global (name, "user", 0))
    {
      sql_rollback ();
      g_free (generated);
      return -2;
    }
  quoted_name = sql_quote (name);

  /* Check hosts. */

  max = manage_max_hosts ();
  manage_set_max_hosts (MANAGE_USER_MAX_HOSTS);
  if (hosts && (manage_count_hosts (hosts, NULL) < 0))
    {
      manage_set_max_hosts (max);
      sql_rollback ();
      g_free (generated);
      return 3;
    }
  manage_set_max_hosts (max);

  /* Get the password hashes. */

  hash = manage_authentication_hash (password);

  /* Get the quoted comment */

  if (comment)
    quoted_comment = sql_quote (comment);
  else
    quoted_comment = g_strdup ("");

  /* Add the user to the database. */

  clean = clean_hosts (hosts ? hosts : "", &max);
  quoted_hosts = sql_quote (clean);
  g_free (clean);
  quoted_method = sql_quote (allowed_methods
                              ? g_ptr_array_index (allowed_methods, 0)
                              : "file");

  ret
    = sql_error ("INSERT INTO users"
                 " (uuid, owner, name, password, comment, hosts, hosts_allow,"
                 "  method, creation_time, modification_time)"
                 " VALUES"
                 " (make_uuid (),"
                 "  (SELECT id FROM users WHERE uuid = '%s'),"
                 "  '%s', '%s', '%s', '%s', %i,"
                 "  '%s', m_now (), m_now ());",
                 current_credentials.uuid,
                 quoted_name,
                 hash,
                 quoted_comment,
                 quoted_hosts,
                 hosts_allow,
                 quoted_method);
  g_free (generated);
  g_free (hash);
  g_free (quoted_comment);
  g_free (quoted_hosts);
  g_free (quoted_method);
  g_free (quoted_name);

  if (ret == 3)
    {
      sql_rollback ();
      return -2;
    }
  else if (ret)
    {
      sql_rollback ();
      return -1;
    }

  user = sql_last_insert_id ();

  /* Add the user to any given groups. */

  index = 0;
  while (groups && (index < groups->len))
    {
      gchar *group_id;
      group_t group;

      group_id = (gchar*) g_ptr_array_index (groups, index);
      if (strcmp (group_id, "0") == 0)
        {
          index++;
          continue;
        }

      if (find_group_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 group_users (\"group\", \"user\") VALUES (%llu, %llu);",
           group,
           user);

      index++;
    }

  /* Add the user to any given roles. */

  index = 0;
  while (roles && (index < roles->len))
    {
      gchar *role_id;
      role_t role;

      role_id = (gchar*) g_ptr_array_index (roles, index);
      if (strcmp (role_id, "0") == 0)
        {
          index++;
          continue;
        }

      if (forbid_super_admin && acl_role_can_super_everyone (role_id))
        {
          sql_rollback ();
          return 99;
        }

      if (find_role_with_permission (role_id, &role, "get_roles"))
        {
          sql_rollback ();
          return -1;
        }

      if (role == 0)
        {
          sql_rollback ();
          if (role_id_return) *role_id_return = role_id;
          return 2;
        }

      sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);",
           role,
           user);

      index++;
    }

  if (new_user)
    *new_user = user;

  /* Ensure the user can see themself. */

  uuid = user_uuid (user);
  if (uuid == NULL)
    {
      g_warning ("%s: Failed to allocate UUID", __func__);
      sql_rollback ();
      return -1;
    }

  create_permission_internal (1,
                              "GET_USERS",
                              "Automatically created when adding user",
                              NULL,
                              uuid,
                              "user",
                              uuid,
                              NULL);

  free (uuid);

  /* Cache permissions. */

  cache_users = g_array_new (TRUE, TRUE, sizeof (user_t));
  g_array_append_val (cache_users, user);
  cache_all_permissions_for_users (cache_users);
  g_free (g_array_free (cache_users, TRUE));

  sql_commit ();
  return 0;
}

/**
 * @brief Create a user from an existing user.
 *
 * @param[in]  name      Name of new user.  NULL to copy from existing.
 * @param[in]  comment   Comment on new user.  NULL to copy from existing.
 * @param[in]  user_id   UUID of existing user.
 * @param[out] new_user  New user.
 *
 * @return 0 success, 1 user exists already, 2 failed to find existing
 *         user, 99 permission denied, -1 error.
 */
int
copy_user (const char* name, const char* comment, const char *user_id,
           user_t* new_user)
{
  user_t user;
  int ret;
  gchar *quoted_uuid;
  GArray *cache_users;
  char *uuid;

  if (acl_user_can_super_everyone (user_id))
    return 99;

  sql_begin_immediate ();

  ret = copy_resource_lock ("user", name, comment, user_id,
                            "password, timezone, hosts, hosts_allow, method",
                            1, &user, NULL);
  if (ret)
    {
      sql_rollback ();
      return ret;
    }

  sql ("UPDATE users SET password = NULL WHERE id = %llu;", user);

  quoted_uuid = sql_quote (user_id);

  sql ("INSERT INTO group_users (\"user\", \"group\")"
       " SELECT %llu, \"group\" FROM group_users"
       " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');",
       user,
       quoted_uuid);

  sql ("INSERT INTO role_users (\"user\", role)"
       " SELECT %llu, role FROM role_users"
       " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');",
       user,
       quoted_uuid);

  g_free (quoted_uuid);

  /* Ensure the user can see themself. */

  uuid = user_uuid (user);
  if (uuid == NULL)
    {
      g_warning ("%s: Failed to allocate UUID", __func__);
      sql_rollback ();
      return -1;
    }

  create_permission_internal (1,
                              "GET_USERS",
                              "Automatically created when adding user",
                              NULL,
                              uuid,
                              "user",
                              uuid,
                              NULL);

  free (uuid);

  /* Cache permissions. */

  cache_users = g_array_new (TRUE, TRUE, sizeof (user_t));
  g_array_append_val (cache_users, user);
  cache_all_permissions_for_users (cache_users);
  g_free (g_array_free (cache_users, TRUE));

  sql_commit ();

  if (new_user)
    *new_user = user;

  return ret;
}

/**
 * @brief Delete a user.
 *
 * @param[in]  user_id_arg  UUID of user.
 * @param[in]  name_arg     Name of user.  Overridden by user_id.
 * @param[in]  forbid_super_admin  Whether to forbid removal of Super Admin.
 * @param[in]  inheritor_id   UUID of user who will inherit owned objects.
 * @param[in]  inheritor_name Name of user who will inherit owned objects.
 *
 * @return 0 success, 2 failed to find user, 4 user has active tasks,
 *         5 attempted suicide, 6 inheritor not found, 7 inheritor same as
 *         deleted user, 8 invalid inheritor, 9 resources still in use,
 *         10 user is 'Feed Import Owner' 99 permission denied, -1 error.
 */
int
delete_user (const char *user_id_arg, const char *name_arg,
             int forbid_super_admin,
             const char* inheritor_id, const char *inheritor_name)
{
  iterator_t tasks;
  user_t user, inheritor;
  get_data_t get;
  char *current_uuid, *feed_owner_id;
  gboolean has_rows;
  iterator_t rows;
  gchar *deleted_user_id;

  assert (user_id_arg || name_arg);

  if (current_credentials.username && current_credentials.uuid)
    {
      if (user_id_arg)
        {
          if (strcmp (user_id_arg, current_credentials.uuid) == 0)
            return 5;
        }
      else if (name_arg
               && (strcmp (name_arg, current_credentials.username) == 0))
        return 5;
    }

  sql_begin_immediate ();

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

  user = 0;
  if (user_id_arg)
    {
      if (forbid_super_admin
          && (strcmp (user_id_arg, ROLE_UUID_SUPER_ADMIN) == 0))
        {
          sql_rollback ();
          return 99;
        }

      if (find_user_with_permission (user_id_arg, &user, "delete_user"))
        {
          sql_rollback ();
          return -1;
        }
    }
  else if (find_user_by_name_with_permission (name_arg, &user, "delete_user"))
    {
      sql_rollback ();
      return -1;
    }

  if (user == 0)
    return 2;

  setting_value (SETTING_UUID_FEED_IMPORT_OWNER, &feed_owner_id);
  if (feed_owner_id)
    {
      char *uuid;

      uuid = user_uuid (user);
      if (strcmp (uuid, feed_owner_id) == 0)
        {
          free (uuid);
          free (feed_owner_id);
          sql_rollback ();
          return 10;
        }
      free (feed_owner_id);
      free (uuid);
    }

  if (forbid_super_admin)
    {
      char *uuid;

      uuid = user_uuid (user);
      if (acl_user_is_super_admin (uuid))
        {
          free (uuid);
          sql_rollback ();
          return 99;
        }
      free (uuid);
    }

  /* Fail if there are any active tasks. */

  memset (&get, '\0', sizeof (get));
  current_uuid = current_credentials.uuid;
  current_credentials.uuid = sql_string ("SELECT uuid FROM users"
                                         " WHERE id = %llu;",
                                         user);
  init_user_task_iterator (&tasks, 0, 1);
  while (next (&tasks))
    switch (task_iterator_run_status (&tasks))
      {
        case TASK_STATUS_DELETE_REQUESTED:
        case TASK_STATUS_DELETE_ULTIMATE_REQUESTED:
        case TASK_STATUS_DELETE_ULTIMATE_WAITING:
        case TASK_STATUS_DELETE_WAITING:
        case TASK_STATUS_REQUESTED:
        case TASK_STATUS_RUNNING:
        case TASK_STATUS_QUEUED:
        case TASK_STATUS_STOP_REQUESTED:
        case TASK_STATUS_STOP_WAITING:
        case TASK_STATUS_PROCESSING:
          {
            cleanup_iterator (&tasks);
            free (current_credentials.uuid);
            current_credentials.uuid = current_uuid;
            sql_rollback ();
            return 4;
          }
        default:
          break;
      }
  cleanup_iterator (&tasks);
  free (current_credentials.uuid);
  current_credentials.uuid = current_uuid;

  /* Check if there's an inheritor. */

  if (inheritor_id && strcmp (inheritor_id, ""))
    {
      if (strcmp (inheritor_id, "self") == 0)
        {
          sql_int64 (&inheritor, "SELECT id FROM users WHERE uuid = '%s'",
                     current_credentials.uuid);

          if (inheritor == 0)
            {
              sql_rollback ();
              return -1;
            }
        }
      else
        {
          if (find_user_with_permission (inheritor_id, &inheritor, "get_users"))
            {
              sql_rollback ();
              return -1;
            }

          if (inheritor == 0)
            {
              sql_rollback ();
              return 6;
            }
        }
    }
  else if (inheritor_name && strcmp (inheritor_name, ""))
    {
      if (find_user_by_name_with_permission (inheritor_name, &inheritor,
                                             "get_users"))
        {
          sql_rollback ();
          return -1;
        }

      if (inheritor == 0)
        {
          sql_rollback ();
          return 6;
        }
    }
  else
    inheritor = 0;

  if (inheritor)
    {
      gchar *deleted_user_name;
      gchar *real_inheritor_id, *real_inheritor_name;

      /* Transfer ownership of objects to the inheritor. */

      if (inheritor == user)
        {
          sql_rollback ();
          return 7;
        }

      real_inheritor_id = user_uuid (inheritor);

      /* Only the current user, owned users or global users may inherit. */
      if (current_credentials.uuid
          && strcmp (current_credentials.uuid, "")
          && strcmp (real_inheritor_id, current_credentials.uuid)
          && sql_int ("SELECT NOT (" ACL_IS_GLOBAL () ")"
                      " FROM users WHERE id = %llu",
                      inheritor)
          && ! acl_user_owns ("user", inheritor, 0)
          && sql_int ("SELECT owner != 0 FROM users WHERE id = %llu",
                      inheritor))
        {
          g_free (real_inheritor_id);
          sql_rollback ();
          return 8;
        }

      deleted_user_id = user_uuid (user);
      deleted_user_name = user_name (deleted_user_id);
      real_inheritor_name = user_name (real_inheritor_id);

      g_log ("event user", G_LOG_LEVEL_MESSAGE,
             "User %s (%s) is inheriting from %s (%s)",
             real_inheritor_name, real_inheritor_id,
             deleted_user_name, deleted_user_id);

      g_free (deleted_user_name);
      g_free (real_inheritor_id);
      g_free (real_inheritor_name);

      /* Transfer owned resources. */

      sql ("UPDATE alerts SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE alerts_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE configs SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE configs_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE credentials SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE credentials_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE host_identifiers SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE host_oss SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE hosts SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE filters SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE filters_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE notes SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE notes_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE oss SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu",
           inheritor, user);

      inherit_port_lists (user, inheritor);

      sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE report_counts SET \"user\" = %llu WHERE \"user\" = %llu",
           inheritor, user);
      sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE results SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE results_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE overrides SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE overrides_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE permissions_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE scanners SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE scanners_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE schedules SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE schedules_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("DELETE FROM tag_resources"
           " WHERE resource_type = 'user' AND resource = %llu;",
           user);
      sql ("UPDATE tags SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("DELETE FROM tag_resources_trash"
           " WHERE resource_type = 'user' AND resource = %llu;",
           user);
      sql ("UPDATE tags_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE targets SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE targets_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE tasks SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      inherit_tickets (user, inheritor);
      inherit_tls_certificates (user, inheritor);

      sql ("UPDATE groups SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE roles SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE users SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE groups_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE roles_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      sql ("UPDATE report_configs SET owner = %llu WHERE owner = %llu;",
           inheritor, user);
      sql ("UPDATE report_configs_trash SET owner = %llu WHERE owner = %llu;",
           inheritor, user);

      /* Report Formats. */

      has_rows = inherit_report_formats (user, inheritor, &rows);

      /* Delete user. */

      sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user);
      sql ("DELETE FROM group_users_trash WHERE \"user\" = %llu;", user);
      sql ("DELETE FROM role_users WHERE \"user\" = %llu;", user);
      sql ("DELETE FROM role_users_trash WHERE \"user\" = %llu;", user);

      delete_permissions_cache_for_user (user);

      sql ("DELETE FROM settings WHERE owner = %llu;", user);
      sql ("DELETE FROM users WHERE id = %llu;", user);

      /* Very last: report formats dirs. */

      if (deleted_user_id == NULL)
        g_warning ("%s: deleted_user_id NULL, skipping dirs", __func__);
      else if (has_rows)
        do
        {
          inherit_report_format_dir (iterator_string (&rows, 0),
                                     deleted_user_id,
                                     inheritor);
        } while (next (&rows));

      g_free (deleted_user_id);
      cleanup_iterator (&rows);

      sql_commit ();

      return 0;
    }

  /* Delete settings and miscellaneous resources not referenced directly. */

  /* Settings. */
  sql ("DELETE FROM settings WHERE owner = %llu;", user);

  /* Delete data modifiers (not directly referenced) */

  /* Notes. */
  sql ("DELETE FROM notes WHERE owner = %llu;", user);
  sql ("DELETE FROM notes_trash WHERE owner = %llu;", user);

  /* Overrides. */
  sql ("DELETE FROM overrides WHERE owner = %llu;", user);
  sql ("DELETE FROM overrides_trash WHERE owner = %llu;", user);

  /* Tags. */
  sql ("DELETE FROM tag_resources"
       " WHERE resource_type = 'user' AND resource = %llu;",
       user);
  sql ("DELETE FROM tag_resources"
       " WHERE tag IN (SELECT id FROM tags WHERE owner = %llu);",
       user);
  sql ("DELETE FROM tags WHERE owner = %llu;", user);
  sql ("DELETE FROM tag_resources_trash"
       " WHERE resource_type = 'user' AND resource = %llu;",
       user);
  sql ("DELETE FROM tag_resources_trash"
       " WHERE tag IN (SELECT id FROM tags_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM tags_trash WHERE owner = %llu;", user);

  delete_tickets_user (user);

  delete_tls_certificates_user (user);

  /* Delete assets (not directly referenced). */

  /* Hosts. */
  sql ("DELETE FROM host_details WHERE host IN"
       " (SELECT id FROM hosts WHERE owner = %llu);", user);
  sql ("DELETE FROM host_max_severities WHERE host IN"
       " (SELECT id FROM hosts WHERE owner = %llu);", user);
  sql ("DELETE FROM host_identifiers WHERE owner = %llu;", user);
  sql ("DELETE FROM host_oss WHERE owner = %llu;", user);
  sql ("DELETE FROM hosts WHERE owner = %llu;", user);

  /* OSs. */
  sql ("DELETE FROM oss WHERE owner = %llu;", user);

  /* Delete report data and tasks (not directly referenced). */

  /* Counts. */
  sql ("DELETE FROM report_counts WHERE \"user\" = %llu", user);
  sql ("DELETE FROM report_counts"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);

  /* Hosts. */
  sql ("DELETE FROM report_host_details"
       " WHERE report_host IN (SELECT id FROM report_hosts"
       "                       WHERE report IN (SELECT id FROM reports"
       "                                        WHERE owner = %llu));",
       user);
  sql ("DELETE FROM report_hosts"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);

  /* Results. */
  sql ("DELETE FROM results"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);
  sql ("DELETE FROM results_trash"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);

  /* Reports. */
  sql ("DELETE FROM result_nvt_reports"
       " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);",
       user);
  sql ("DELETE FROM reports WHERE owner = %llu;", user);

  /* Delete tasks (not directly referenced). */

  if (user_resources_in_use (user,
                             "tasks", target_in_use,
                             NULL, NULL))
    {
      sql_rollback ();
      return 9;
    }
  tickets_remove_tasks_user (user);
  sql ("DELETE FROM task_alerts"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM task_files"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM task_preferences"
       " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);",
       user);
  sql ("DELETE FROM tasks WHERE owner = %llu;", user);

  /* Delete resources directly used by tasks. */

  /* Alerts. */
  if (user_resources_in_use (user,
                             "alerts", alert_in_use,
                             "alerts_trash", trash_alert_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM alert_condition_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_condition_data_trash"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_event_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_event_data_trash"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_method_data"
       " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alert_method_data_trash"
       " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM alerts WHERE owner = %llu;", user);
  sql ("DELETE FROM alerts_trash WHERE owner = %llu;", user);

  /* Configs. */
  if (user_resources_in_use (user,
                             "configs", config_in_use,
                             "configs_trash", trash_config_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM nvt_selectors"
       " WHERE name IN (SELECT nvt_selector FROM configs WHERE owner = %llu)"
       " AND name != '" MANAGE_NVT_SELECTOR_UUID_ALL "';",
       user);
  sql ("DELETE FROM config_preferences"
       " WHERE config IN (SELECT id FROM configs WHERE owner = %llu);",
       user);
  sql ("DELETE FROM config_preferences_trash"
       " WHERE config IN (SELECT id FROM configs_trash WHERE owner = %llu);",
       user);
  sql ("DELETE FROM configs WHERE owner = %llu;", user);
  sql ("DELETE FROM configs_trash WHERE owner = %llu;", user);

  /* Scanners. */
  if (user_resources_in_use (user,
                             "scanners", scanner_in_use,
                             "scanners_trash", trash_scanner_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM scanners WHERE owner = %llu;", user);
  sql ("DELETE FROM scanners_trash WHERE owner = %llu;", user);

  /* Schedules. */
  if (user_resources_in_use (user,
                             "schedules", schedule_in_use,
                             "schedules_trash", trash_schedule_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM schedules WHERE owner = %llu;", user);
  sql ("DELETE FROM schedules_trash WHERE owner = %llu;", user);

  /* Targets. */
  if (user_resources_in_use (user,
                             "targets", target_in_use,
                             "targets_trash", trash_target_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM targets_login_data WHERE target IN"
       " (SELECT id FROM targets WHERE owner = %llu);", user);
  sql ("DELETE FROM targets_trash_login_data WHERE target IN"
       " (SELECT id FROM targets_trash WHERE owner = %llu);", user);
  sql ("DELETE FROM targets WHERE owner = %llu;", user);
  sql ("DELETE FROM targets_trash WHERE owner = %llu;", user);

  /* Delete resources used indirectly by tasks */

  /* Filters (used by alerts and settings). */
  if (user_resources_in_use (user,
                             "filters", filter_in_use,
                             "filters_trash", trash_filter_in_use))
    {
      sql_rollback ();
      return 9;
    }
  sql ("DELETE FROM filters WHERE owner = %llu;", user);
  sql ("DELETE FROM filters_trash WHERE owner = %llu;", user);

  /* Port lists (used by targets). */
  if (user_resources_in_use (user,
                             "port_lists", port_list_in_use,
                             "port_lists_trash", trash_port_list_in_use))
    {
      sql_rollback ();
      return 9;
    }
  delete_port_lists_user (user);

  /* Check credentials before deleting report formats, because we can't
   * rollback the deletion of the report format dirs. */
  if (user_resources_in_use (user,
                             "credentials", credential_in_use,
                             "credentials_trash", trash_credential_in_use))
    {
      sql_rollback ();
      return 9;
    }

  /* Check report formats (used by alerts). */
  if (user_resources_in_use (user,
                             "report_formats",
                             report_format_in_use,
                             "report_formats_trash",
                             trash_report_format_in_use))
    {
      sql_rollback ();
      return 9;
    }

  /* Delete credentials last because they can be used in various places */

  sql ("DELETE FROM credentials_data WHERE credential IN"
       " (SELECT id FROM credentials WHERE owner = %llu);",
       user);
  sql ("DELETE FROM credentials_trash_data WHERE credential IN"
       " (SELECT id FROM credentials_trash WHERE owner = %llu);",
       user);

  sql ("DELETE FROM credentials WHERE owner = %llu;", user);
  sql ("DELETE FROM credentials_trash WHERE owner = %llu;", user);

  /* Make permissions global if they are owned by the user and are related
   * to users/groups/roles that are owned by the user. */

  sql ("UPDATE permissions SET owner = NULL"
       " WHERE owner = %llu"
       " AND ((subject_type = 'user' AND subject IN (SELECT id FROM users WHERE owner = %llu))"
       "      OR (subject_type = 'group' AND subject IN (SELECT id FROM groups WHERE owner = %llu))"
       "      OR (subject_type = 'role' AND subject IN (SELECT id FROM roles WHERE owner = %llu))"
       "      OR (resource_type = 'user' AND resource IN (SELECT id FROM users WHERE owner = %llu))"
       "      OR (resource_type = 'group' AND resource IN (SELECT id FROM groups WHERE owner = %llu))"
       "      OR (resource_type = 'role' AND resource IN (SELECT id FROM roles WHERE owner = %llu)));",
       user,
       user,
       user,
       user,
       user,
       user,
       user);

  /* Make users, roles and groups global if they are owned by the user. */

  sql ("UPDATE users SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE roles SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE groups SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE roles_trash SET owner = NULL WHERE owner = %llu;", user);
  sql ("UPDATE groups_trash SET owner = NULL WHERE owner = %llu;", user);

  /* Remove all other permissions owned by the user or given on the user. */

  sql ("DELETE FROM permissions"
       " WHERE owner = %llu"
       " OR subject_type = 'user' AND subject = %llu"
       " OR (resource_type = 'user' AND resource = %llu);",  /* For Super. */
       user,
       user,
       user);
  sql ("DELETE FROM permissions_get_tasks WHERE \"user\" = %llu;", user);

  /* Delete permissions granted by the user. */

  sql ("DELETE FROM permissions WHERE owner = %llu;", user);
  sql ("DELETE FROM permissions_trash WHERE owner = %llu;", user);

  /* Remove user from groups and roles. */

  sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM group_users_trash WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM role_users WHERE \"user\" = %llu;", user);
  sql ("DELETE FROM role_users_trash WHERE \"user\" = %llu;", user);

  /* Delete report configs */

  delete_report_configs_user (user);

  /* Delete report formats. */

  has_rows = delete_report_formats_user (user, &rows);

  /* Delete user. */

  deleted_user_id = user_uuid (user);

  sql ("DELETE FROM users WHERE id = %llu;", user);

  /* Delete report format dirs. */

  if (deleted_user_id)
    delete_report_format_dirs_user (deleted_user_id, has_rows ? &rows : NULL);
  else
    g_warning ("%s: deleted_user_id NULL, skipping removal of report formats dir",
               __func__);

  sql_commit ();
  return 0;
}

/**
 * @brief Modify a user.
 *
 * @param[in]  user_id      The UUID of the user.  Overrides name.
 * @param[in]  name         The name of the user.  If NULL then set to name
 *                          when return is 3 or 4.
 * @param[in]  new_name     New name for the user.  NULL to leave as is.
 * @param[in]  password     The password of the user.  NULL to leave as is.
 * @param[in]  comment      The comment for the user.  NULL to leave as is.
 * @param[in]  hosts        The host the user is allowed/forbidden to scan.
 *                          NULL to leave as is.
 * @param[in]  hosts_allow  Whether hosts is allow or forbid.
 * @param[in]  allowed_methods  Allowed login methods.
 * @param[in]  groups           Groups.
 * @param[out] group_id_return  ID of group on "failed to find" error.
 * @param[in]  roles            Roles.
 * @param[out] role_id_return   ID of role on "failed to find" error.
 * @param[out] r_errdesc    If not NULL the address of a variable to receive
 *                          a malloced string with the error description.  Will
 *                          always be set to NULL on success.
 *
 * @return 0 if the user has been added successfully, 1 failed to find group,
 *         2 failed to find user, 3 success and user gained admin, 4 success
 *         and user lost admin, 5 failed to find role, 6 syntax error in hosts,
 *         7 syntax error in new name, 99 permission denied, -1 on error,
 *         -2 for an unknown role, -3 if wrong number of methods, -4 error in
 *         method.
 */
int
modify_user (const gchar * user_id, gchar **name, const gchar *new_name,
             const gchar * password, const gchar * comment,
             const gchar * hosts, int hosts_allow,
             const array_t * allowed_methods, array_t *groups,
             gchar **group_id_return, array_t *roles, gchar **role_id_return,
             gchar **r_errdesc)
{
  char *errstr;
  gchar *hash, *quoted_hosts, *quoted_method, *clean, *uuid;
  gchar *quoted_new_name, *quoted_comment;
  user_t user;
  int max, was_admin, is_admin;
  GArray *cache_users;

  if (r_errdesc)
    *r_errdesc = NULL;

  /* allowed_methods is a NULL terminated array. */
  if (allowed_methods && (allowed_methods->len > 2))
    return -3;

  if (allowed_methods && (allowed_methods->len <= 1))
    allowed_methods = NULL;

  if (allowed_methods
      && (strlen (g_ptr_array_index (allowed_methods, 0)) == 0))
    allowed_methods = NULL;

  if (allowed_methods
      && (auth_method_name_valid (g_ptr_array_index (allowed_methods, 0))
          == 0))
    return -4;

  sql_begin_immediate ();

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

  user = 0;
  if (user_id)
    {
      if (find_user_with_permission (user_id, &user, "modify_user"))
        {
          sql_rollback ();
          return -1;
        }
    }
  else if (find_user_by_name_with_permission (*name, &user, "modify_user"))
    {
      sql_rollback ();
      return -1;
    }
  if (user == 0)
    {
      sql_rollback ();
      return 2;
    }

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

  /* The only user that can edit a Super Admin is the Super Admin themself. */
  if (acl_user_can_super_everyone (uuid) && strcmp (uuid, current_credentials.uuid))
    {
      g_free (uuid);
      sql_rollback ();
      return 99;
    }

  was_admin = acl_user_is_admin (uuid);

  if (password)
    {
      char *user_name;

      user_name = sql_string ("SELECT name FROM users WHERE id = %llu",
                              user);
      errstr = gvm_validate_password (password, user_name);
      if (errstr)
        {
          g_warning ("new password for '%s' rejected: %s", user_name, errstr);
          if (r_errdesc)
            *r_errdesc = errstr;
          else
            g_free (errstr);
          sql_rollback ();
          g_free (user_name);
          return -1;
        }
      g_free (user_name);
    }

  /* Check hosts. */

  max = manage_max_hosts ();
  manage_set_max_hosts (MANAGE_USER_MAX_HOSTS);
  if (hosts && (manage_count_hosts (hosts, NULL) < 0))
    {
      manage_set_max_hosts (max);
      sql_rollback ();
      return 6;
    }
  manage_set_max_hosts (max);

  /* Check new name. */

  if (new_name)
    {
      if (validate_username (new_name) != 0)
        {
          sql_rollback ();
          return 7;
        }

      if (strcmp (uuid, current_credentials.uuid) == 0)
        {
          sql_rollback ();
          return 99;
        }

      if (resource_with_name_exists_global (new_name, "user", user))
        {
          sql_rollback ();
          return 8;
        }
      quoted_new_name = sql_quote (new_name);
    }
  else
    quoted_new_name = NULL;

  /* Get the password hashes. */

  if (password)
    hash = manage_authentication_hash (password);
  else
    hash = NULL;

  if (comment)
    {
      quoted_comment = sql_quote (comment);
    }
  else
    {
      quoted_comment = NULL;
    }


  /* Update the user in the database. */

  clean = clean_hosts (hosts ? hosts : "", &max);
  if ((hosts_allow == 0) && (max == 0))
    /* Convert "Deny none" to "Allow All". */
    hosts_allow = 2;
  quoted_hosts = sql_quote (clean);
  g_free (clean);
  quoted_method = sql_quote (allowed_methods
                              ? g_ptr_array_index (allowed_methods, 0)
                              : "");
  sql ("UPDATE users"
       " SET name = %s%s%s,"
       "     comment = %s%s%s,"
       "     hosts = '%s',"
       "     hosts_allow = '%i',"
       "     method = %s%s%s,"
       "     modification_time = m_now ()"
       " WHERE id = %llu;",
       quoted_new_name ? "'" : "",
       quoted_new_name ? quoted_new_name : "name",
       quoted_new_name ? "'" : "",
       quoted_comment ? "'" : "",
       quoted_comment ? quoted_comment : "comment",
       quoted_comment ? "'" : "",
       quoted_hosts,
       hosts_allow,
       allowed_methods ? "'" : "",
       allowed_methods ? quoted_method : "method",
       allowed_methods ? "'" : "",
       user);
  g_free (quoted_new_name);
  g_free (quoted_hosts);
  g_free (quoted_method);
  if (hash)
    sql ("UPDATE users"
         " SET password = '%s'"
         " WHERE id = %llu;",
         hash,
         user);
  g_free (hash);

  /* Update the user groups. */

  if (groups)
    {
      int index;

      sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user);
      index = 0;
      while (groups && (index < groups->len))
        {
          gchar *group_id;
          group_t group;

          group_id = (gchar*) g_ptr_array_index (groups, index);
          if (strcmp (group_id, "0") == 0)
            {
              index++;
              continue;
            }

          if (find_group_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 group_users (\"group\", \"user\")"
               " VALUES (%llu, %llu);",
               group,
               user);

          index++;
        }
    }

  /* Update the user roles. */

  if (roles)
    {
      int index;

      sql ("DELETE FROM role_users"
           " WHERE \"user\" = %llu"
           " AND role != (SELECT id from roles"
           "              WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "');",
           user);
      index = 0;
      while (roles && (index < roles->len))
        {
          gchar *role_id;
          role_t role;

          role_id = (gchar*) g_ptr_array_index (roles, index);
          if (strcmp (role_id, "0") == 0)
            {
              index++;
              continue;
            }

          if (find_role_with_permission (role_id, &role, "get_roles"))
            {
              sql_rollback ();
              return -1;
            }

          if (role == 0)
            {
              sql_rollback ();
              if (role_id_return) *role_id_return = role_id;
              return 1;
            }

          if (acl_role_can_super_everyone (role_id))
            {
              sql_rollback ();
              return 99;
            }

          sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);",
               role,
               user);

          index++;
        }
    }

  cache_users = g_array_new (TRUE, TRUE, sizeof (user_t));
  g_array_append_val (cache_users, user);
  cache_all_permissions_for_users (cache_users);
  g_free (g_array_free (cache_users, TRUE));

  sql_commit ();

  if (was_admin)
    {
      is_admin = acl_user_is_admin (uuid);
      g_free (uuid);
      if (is_admin)
        return 0;
      if (*name == NULL)
        *name = sql_string ("SELECT name FROM users WHERE id = %llu",
                            user);
      return 4;
    }

  is_admin = acl_user_is_admin (uuid);
  g_free (uuid);
  if (is_admin)
    {
      if (*name == NULL)
        *name = sql_string ("SELECT name FROM users WHERE id = %llu",
                            user);
      return 3;
    }

  return 0;
}

/**
 * @brief Return the name of a user.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return Newly allocated name if available, else NULL.
 */
gchar*
user_name (const char *uuid)
{
  gchar *name, *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  name = sql_string ("SELECT name FROM users WHERE uuid = '%s';",
                     quoted_uuid);
  g_free (quoted_uuid);
  return name;
}

/**
 * @brief Return the UUID of a user.
 *
 * Warning: this is only safe for users that are known to be in the db.
 *
 * @param[in]  user  User.
 *
 * @return Newly allocated UUID if available, else NULL.
 */
char*
user_uuid (user_t user)
{
  return sql_string ("SELECT uuid FROM users WHERE id = %llu;",
                     user);
}

/**
 * @brief Check whether a user is in use.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
user_in_use (user_t user)
{
  return 0;
}

/**
 * @brief Check whether a trashcan user is in use.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
trash_user_in_use (user_t user)
{
  return 0;
}

/**
 * @brief Check whether a user is writable.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
user_writable (user_t user)
{
  return 1;
}

/**
 * @brief Check whether a trashcan user is writable.
 *
 * @param[in]  user  User.
 *
 * @return 1 yes, 0 no.
 */
int
trash_user_writable (user_t user)
{
  return 1;
}

/**
 * @brief Return the hosts of a user.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return Newly allocated hosts value if available, else NULL.
 */
gchar*
user_hosts (const char *uuid)
{
  gchar *name, *quoted_uuid;

  quoted_uuid = sql_quote (uuid);
  name = sql_string ("SELECT hosts FROM users WHERE uuid = '%s';",
                     quoted_uuid);
  g_free (quoted_uuid);
  return name;
}

/**
 * @brief Return whether hosts value of a user denotes allowed.
 *
 * @param[in]  uuid  UUID of user.
 *
 * @return 1 if allow, else 0.
 */
int
user_hosts_allow (const char *uuid)
{
  gchar *quoted_uuid;
  int allow;

  quoted_uuid = sql_quote (uuid);
  allow = sql_int ("SELECT hosts_allow FROM users WHERE uuid = '%s';",
                   quoted_uuid);
  g_free (quoted_uuid);
  return allow;
}

/**
 * @brief User columns for user iterator.
 */
#define USER_ITERATOR_FILTER_COLUMNS                                  \
 { GET_ITERATOR_FILTER_COLUMNS, "method", "roles", "groups", "hosts", \
   NULL }

/**
 * @brief User iterator columns.
 */
#define USER_ITERATOR_COLUMNS                                              \
 {                                                                         \
   GET_ITERATOR_COLUMNS (users),                                           \
   { "method", NULL, KEYWORD_TYPE_STRING },                                \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                                 \
   { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER },                          \
   {                                                                       \
     "coalesce ((SELECT group_concat (name, ', ')"                         \
     "           FROM (SELECT DISTINCT name, order_role (name)"            \
     "                 FROM roles, role_users"                             \
     "                 WHERE role_users.role = roles.id"                   \
     "                 AND \"user\" = users.id"                            \
     "                 ORDER BY order_role (roles.name) ASC)"              \
     "                 AS user_iterator_sub),"                             \
     "           '')",                                                     \
     "roles",                                                              \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   {                                                                       \
     "coalesce ((SELECT group_concat (name, ', ')"                         \
     "           FROM (SELECT DISTINCT name FROM groups, group_users"      \
     "                 WHERE group_users.\"group\" = groups.id"            \
     "                 AND \"user\" = users.id"                            \
     "                 ORDER BY groups.name ASC)"                          \
     "                AS user_iterator_sub),"                              \
     "           '')",                                                     \
     "groups",                                                             \
     KEYWORD_TYPE_STRING                                                   \
   },                                                                      \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @brief User iterator columns for trash case.
 */
#define USER_ITERATOR_TRASH_COLUMNS                                        \
 {                                                                         \
   GET_ITERATOR_COLUMNS (users_trash),                                     \
   { "method", NULL, KEYWORD_TYPE_STRING },                                \
   { "hosts", NULL, KEYWORD_TYPE_STRING },                                 \
   { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER },                          \
   { NULL, NULL, KEYWORD_TYPE_UNKNOWN }                                    \
 }

/**
 * @brief Count number of users.
 *
 * @param[in]  get  GET params.
 *
 * @return Total number of users in usered set.
 */
int
user_count (const get_data_t *get)
{
  static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = USER_ITERATOR_COLUMNS;
  return count ("user", get, columns, NULL, filter_columns,
                  0, 0, 0, TRUE);
}

/**
 * @brief Initialise a user iterator, including observed users.
 *
 * @param[in]  iterator    Iterator.
 * @param[in]  get         GET data.
 *
 * @return 0 success, 1 failed to find user, 2 failed to find user (filt_id),
 *         -1 error.
 */
int
init_user_iterator (iterator_t* iterator, get_data_t *get)
{
  static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS;
  static column_t columns[] = USER_ITERATOR_COLUMNS;
  static column_t trash_columns[] = USER_ITERATOR_TRASH_COLUMNS;

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

/**
 * @brief Get the method of the user from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Method of the user or NULL if iteration is complete.
 */
DEF_ACCESS (user_iterator_method, GET_ITERATOR_COLUMN_COUNT);

/**
 * @brief Get the hosts from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts or NULL if iteration is complete.
 */
DEF_ACCESS (user_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 1);

/**
 * @brief Get the hosts allow value from a user iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return Hosts allow.
 */
int
user_iterator_hosts_allow (iterator_t* iterator)
{
  if (iterator->done) return -1;
  return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2);
}

/**
 * @brief Initialise an info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  user            User.
 */
void
init_user_group_iterator (iterator_t *iterator, user_t user)
{
  gchar *available, *with_clause;
  get_data_t get;
  array_t *permissions;

  assert (user);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_groups"));
  available = acl_where_owned ("group", &get, 1, "any", 0, permissions, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT DISTINCT id, uuid, name, %s FROM groups"
                 " WHERE id IN (SELECT \"group\" FROM group_users"
                 "              WHERE \"user\" = %llu)"
                 " ORDER by name;",
                 with_clause ? with_clause : "",
                 available,
                 user);

  g_free (with_clause);
  g_free (available);
}

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

/**
 * @brief Get the NAME from a user group iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NAME or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_group_iterator_name, 2);

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

/**
 * @brief Initialise an info iterator.
 *
 * @param[in]  iterator        Iterator.
 * @param[in]  user            User.
 */
void
init_user_role_iterator (iterator_t *iterator, user_t user)
{
  gchar *available, *with_clause;
  get_data_t get;
  array_t *permissions;

  assert (user);

  get.trash = 0;
  permissions = make_array ();
  array_add (permissions, g_strdup ("get_roles"));
  available = acl_where_owned ("role", &get, 1, "any", 0, permissions, 0,
                               &with_clause);
  array_free (permissions);

  init_iterator (iterator,
                 "%s"
                 " SELECT DISTINCT id, uuid, name, order_role (name), %s"
                 " FROM roles"
                 " WHERE id IN (SELECT role FROM role_users"
                 "              WHERE \"user\" = %llu)"
                 " ORDER by order_role (name);",
                 with_clause ? with_clause : "",
                 available,
                 user);

  g_free (with_clause);
  g_free (available);
}

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

/**
 * @brief Get the NAME from a user role iterator.
 *
 * @param[in]  iterator  Iterator.
 *
 * @return NAME or NULL if iteration is complete.  Freed by cleanup_iterator.
 */
DEF_ACCESS (user_role_iterator_name, 2);

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

/**
 * @brief Check if a user still has resources that are in use.
 *
 * @param[in] user          The user to check.
 * @param[in] table         The table to check for resources in use.
 * @param[in] in_use        Function to check if a resource is in use.
 * @param[in] trash_table   The trash table to check for resources in use.
 * @param[in] trash_in_use  Function to check if a trash resource is in use.
 *
 * @return 0 no resources in use, 1 found resources used by user.
 */
static int
user_resources_in_use (user_t user,
                       const char *table, int(*in_use)(resource_t),
                       const char *trash_table, int(*trash_in_use)(resource_t))
{
  iterator_t iter;
  int has_resource_in_use = 0;

  init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu",
                 table, user);
  while (next (&iter) && has_resource_in_use == 0)
    {
      resource_t resource = iterator_int64 (&iter, 0);
      has_resource_in_use = in_use (resource);
    }
  cleanup_iterator (&iter);
  if (has_resource_in_use)
    return 1;

  if (trash_table == NULL || trash_in_use == NULL)
    return 0;

  init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu",
                 trash_table, user);
  while (next (&iter) && has_resource_in_use == 0)
    {
      resource_t resource = iterator_int64 (&iter, 0);
      has_resource_in_use = trash_in_use (resource);
    }
  cleanup_iterator (&iter);
  if (has_resource_in_use)
    return 2;

  return 0;
}

/**
 * @brief 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 (task_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.
 */
void
manage_set_ldap_info (int enabled, gchar *host, gchar *authdn,
                      int allow_plaintext, gchar *cacert, int ldaps_only)
{
  gchar *quoted;

  sql_begin_immediate ();

  if (enabled >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_enable';");
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_enable', %i);", enabled);
    }

  if (host)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_host';");
      quoted = sql_quote (host);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_host', '%s');",
           quoted);
      g_free (quoted);
    }

  if (authdn)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_authdn';");
      quoted = sql_quote (authdn);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_authdn', '%s');",
           quoted);
      g_free (quoted);
    }

  if (allow_plaintext >= 0)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_allow_plaintext';");
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_allow_plaintext', %i);",
           allow_plaintext);
    }

  if (cacert)
    {
      sql ("DELETE FROM meta WHERE name LIKE 'ldap_cacert';");
      quoted = sql_quote (cacert);
      sql ("INSERT INTO meta (name, value) VALUES ('ldap_cacert', '%s');",
           quoted);
      g_free (quoted);
    }

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

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

  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;

  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");
      type = g_strdup("report");
    }
  else if (type_is_task_subtype (type))
    {
      resource_permission = g_strdup ("get_tasks");
      type = g_strdup("task");
    }
  else if (type_is_config_subtype (type))
    {
      resource_permission = g_strdup ("get_configs");
      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, type, current_uuid,
                                   resource_permission);
      if (ret)
        {
          g_free (resource_permission);
          if (error_extra)
            *error_extra = g_strdup (current_uuid);
          return ret;
        }
    }

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

  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"));
        }
      #if COMPLIANCE_REPORTS == 1
        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"));
          }
      #endif

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

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

  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))
        {
          ignore_max_rows_per_page = 0;
          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"));
        }
      #if COMPLIANCE_REPORTS == 1
        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"));
          }
      #endif

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

  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 },                                   \
   { "(SELECT count(*) FROM tag_resources"                                   \
     " WHERE tag = tags.id"                                                  \
     " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) ")",           \
     "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 },                                   \
   { "(SELECT count(*) FROM tag_resources_trash"                             \
     " WHERE tag = tags_trash.id)",                                          \
     "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;

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

  init_iterator (iterator,
                 "%s"
                 " SELECT id, uuid, name, value, comment"
                 " FROM tags"
                 " WHERE 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,
                 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;

  assert (type);
  assert (resource);

  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 tags.resource_type = tag_resources.resource_type)"
                 "   %s;",
                 type,
                 resource,
                 LOCATION_TABLE,
                 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)
{
  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 (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 (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,
                          &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.
 */
static 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;
    }

  ret = manage_option_setup (log_config, database);
  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 = '7eda49c5-096c-4bef-b1ab-d080d87300df'"
          "             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, "migrate-relay-sensors") == 0)
    {
      if (get_relay_mapper_path ())
        {
          sql_begin_immediate ();

          success_text = manage_migrate_relay_sensors ();

          sql_commit ();
        }
      else
        {
          fprintf (stderr,
                   "No relay mapper found."
                   " Please check your $PATH or the --relay-mapper option.\n");
          success_text = NULL;
          ret = -1;
        }
    }
  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;
}
