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

/**
 * @file
 * @brief Implementation of container image scanner management
 *        functions for GVMD.
 */

#if ENABLE_CONTAINER_SCANNING

#include "debug_utils.h"
#include "manage_assets.h"
#include "manage_container_image_scanner.h"
#include "manage_report_exports.h"
#include "manage_runtime_flags.h"
#include "manage_sql.h"
#include "manage_sql_oci_image_targets.h"
#include "manage_openvas.h"
#include "manage_osp.h"
#include <sys/types.h>
#include <bsd/unistd.h>
#include <unistd.h>

#include <gvm/base/gvm_sentry.h>
#include <gvm/container_image_scanner/container_image_scanner.h>

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

#define CONTAINER_SCANNER_SCAN_PREFIX "container-image-scanner"

/**
 * @brief Create a new connection to a container image scanner.
 *
 * @param[in]   scanner      Scanner.
 * @param[in]   scan_id      scan uuid.
 *
 * @return New connection if success, NULL otherwise.
 *         The connection has to be freed by the caller.
 */
http_scanner_connector_t
container_image_scanner_connect (scanner_t scanner, const char *scan_id)
{
  http_scanner_connector_t connection;

  connection = http_scanner_connect (scanner, scan_id);
  if (connection)
      http_scanner_connector_builder (connection,
                                      HTTP_SCANNER_SCAN_PREFIX,
                                      CONTAINER_SCANNER_SCAN_PREFIX);
  else
    g_warning ("%s: Could not connect to container image scanner", __func__);

  return connection;
}

/**
 * @brief Get the credential of a OCI image target as
 *        container_image_credential_t.
 *
 * @param[in]  target  The OCI image target to get the credential from.
 *
 * @return  Pointer to a newly allocated container_image_credential_t.
 */
container_image_credential_t *
container_image_target_credential (oci_image_target_t target)
{
  credential_t credential;
  credential = oci_image_target_credential (target);
  if (credential)
    {
      iterator_t iter;
      container_image_credential_t *container_image_credential;

      init_credential_iterator_one (&iter, credential);
      if (!next (&iter))
        {
          g_warning ("%s: Credential not found.", __func__);
          cleanup_iterator (&iter);
          return NULL;
        }
      if (strcmp (credential_iterator_type (&iter), "up"))
        {
          g_warning ("%s: Credential not a user/pass pair.", __func__);
          cleanup_iterator (&iter);
          return NULL;
        }

      container_image_credential
        = container_image_credential_new ("up", "generic");

      container_image_credential_set_auth_data (
        container_image_credential,
        "username",
        credential_iterator_login (&iter)
      );

      container_image_credential_set_auth_data (
        container_image_credential,
        "password",
        credential_iterator_password (&iter)
      );

      cleanup_iterator (&iter);
      return container_image_credential;
    }
  g_warning ("%s: No credential assigned to target.", __func__);
  return NULL;
}

/**
 * @brief Insert a host detail into a container scan 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.
 */
static void
insert_container_scan_report_host_detail (report_t report,
                                          const char *host,
                                          const char *hostname,
                                          const char *s_type,
                                          const char *s_name,
                                          const char *s_desc,
                                          const char *name,
                                          const char *value,
                                          const char *hash_value)
{
  sql_ps ("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 = $1 AND host = $2 AND hostname = $3),"
          "  $4, $5, $6, $7, $8, $9);",
          SQL_RESOURCE_PARAM (report), SQL_STR_PARAM (host),
          SQL_STR_PARAM (hostname), SQL_STR_PARAM (s_type),
          SQL_STR_PARAM (s_name), SQL_STR_PARAM (s_desc), SQL_STR_PARAM (name),
          SQL_STR_PARAM (value), SQL_STR_PARAM (hash_value), NULL);
}

/**
 * @brief Add a report host.
 *
 * @param[in]  report   UUID of resource.
 * @param[in]  host     Host.
 * @param[in]  hostname Hostname.
 * @param[in]  start    Start time.
 * @param[in]  end      End time.
 *
 * @return Report host.
 */
static report_host_t
manage_container_scan_report_host_add (report_t report,
                                       const char *host,
                                       const char *hostname,
                                       time_t start,
                                       time_t end)
{
  resource_t report_host;

  sql_ps ("INSERT INTO report_hosts"
          " (report, host, hostname, start_time, end_time, current_port, max_port)"
          " SELECT $1, $2, $3, $4, $5, 0, 0"
          " WHERE NOT EXISTS (SELECT 1 FROM report_hosts WHERE report = $1"
          "                   AND host = $2 AND hostname = $3);",
          SQL_RESOURCE_PARAM (report), SQL_STR_PARAM (host),
          SQL_STR_PARAM (hostname), SQL_INT_PARAM (start),
          SQL_INT_PARAM (end), NULL);

  report_host = sql_int64_0_ps ("SELECT id FROM report_hosts"
                                " WHERE report = $1 AND host = $2"
                                " AND hostname = $3;",
                                SQL_RESOURCE_PARAM (report), SQL_STR_PARAM (host),
                                SQL_STR_PARAM (hostname), NULL);
  return report_host;
}

/**
 * @brief Set the end time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  hostname   Hostname.
 * @param[in]  timestamp  End time. In ISO format.
 */
static void
set_container_scan_host_end_time_isotime (report_t report,
                                          const char* host,
                                          const char* hostname,
                                          const char* timestamp)
{
  int end_time;

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

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

  if (sql_int_ps ("SELECT COUNT(*) FROM report_hosts"
                  " WHERE report = $1 AND host = $2 AND hostname = $3;",
                  SQL_RESOURCE_PARAM (report), SQL_STR_PARAM (host),
                  SQL_STR_PARAM (hostname), NULL))
    sql_ps ("UPDATE report_hosts SET end_time = $1"
            " WHERE report = $2 AND host = $3 AND hostname = $4;",
             SQL_INT_PARAM (end_time), SQL_RESOURCE_PARAM (report),
             SQL_STR_PARAM (host), SQL_STR_PARAM (hostname), NULL);
  else
    manage_container_scan_report_host_add (report,
                                           host,
                                           hostname,
                                           0,
                                           end_time);
}

/**
 * @brief Set the start time of a scanned host.
 *
 * @param[in]  report     Report associated with the scan.
 * @param[in]  host       Host.
 * @param[in]  hostname   Hostname.
 * @param[in]  timestamp  Start time. In ISO format.
 */
static void
set_container_scan_host_start_time_isotime (report_t report,
                                            const char* host,
                                            const char* hostname,
                                            const char* timestamp)
{
  int start_time;

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

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

  if (sql_int_ps ("SELECT COUNT(*) FROM report_hosts"
               " WHERE report = $1 AND host = $2"
               " AND hostname = $3;",
               SQL_RESOURCE_PARAM (report), SQL_STR_PARAM (host),
               SQL_STR_PARAM (hostname), NULL))
    sql_ps ("UPDATE report_hosts SET start_time = $1"
            " WHERE report = $2 AND host = $3 AND hostname = $4;",
            SQL_INT_PARAM (start_time), SQL_RESOURCE_PARAM (report),
            SQL_STR_PARAM (host), SQL_STR_PARAM (hostname), NULL);
  else
    manage_container_scan_report_host_add (report, host, hostname,
                                           start_time, 0);
}

/**
 * @brief Check if host detail exists.
 *
 * @param[in]  report      Report.
 * @param[in]  host        Host.
 * @param[in]  hostname    Hostname.
 * @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 result already exists, else "0"
 */
static int
check_container_scan_host_detail_exists (report_t report, const char *host,
                                         const char *hostname, 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;
  resource_t report_host;
  int return_value = 0;

  hash_string = g_strdup_printf ("%llu-%s-%s-%s-%s-%s-%s-%s", report, host,
                                 hostname, 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_ps (&report_host, "SELECT id FROM report_hosts"
                                    " WHERE report = $1 AND host = $2"
                                    " AND hostname = $3;",
                                    SQL_RESOURCE_PARAM (report),
                                    SQL_STR_PARAM (host),
                                    SQL_STR_PARAM (hostname),
                                    NULL);

        if (sql_int_ps ("SELECT EXISTS"
                        " (SELECT * FROM report_host_details"
                        "  WHERE report_host = $1 and hash_value = $2);",
                        SQL_RESOURCE_PARAM (report_host),
                        SQL_STR_PARAM (*detail_hash_value),
                        NULL))
          {
            if (sql_int_ps ("SELECT EXISTS"
                            " (SELECT * FROM report_host_details"
                            "   WHERE report_host = $1 and hash_value = $2"
                            "   and source_type = $3 and source_name = $4"
                            "   and source_description = $5"
                            "   and  name = $6 and value = $7);",
                            SQL_RESOURCE_PARAM (report_host),
                            SQL_STR_PARAM (*detail_hash_value),
                            SQL_STR_PARAM (s_type),
                            SQL_STR_PARAM (s_name),
                            SQL_STR_PARAM (s_desc),
                            SQL_STR_PARAM (name),
                            SQL_STR_PARAM (value),
                            NULL))
              {
                return_value = 1;
              }
          }
    }

  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 Create a key for host and hostname combination.
 *
 * @param[in]  host      Host IP address.
 * @param[in]  hostname  Hostname.
 * @param[in]  separator Separator to use to combine host and hostname in the key.
 *
 * @return Key for host and hostname combination.
 */
gchar*
create_host_key (const gchar *host, const gchar *hostname,
                 const gchar *separator)
{
  return g_strdup_printf ("%s%s%s",
                          host ? host : "",
                          separator,
                          hostname ? hostname : "");
}

/**
 * @brief Parse host and hostname from a key.
 *
 * @param[in]  key       Host and hostname combination.
 * @param[in]  separator Separator used in the key.
 * @param[out] host      Host.
 * @param[out] hostname  Hostname.
 *
 * @return 0 on success, -1 on error.
 */
int
parse_host_key (const char *key, const char *separator,
                char **host, char **hostname)
{
  gchar **parts = g_strsplit (key, separator, -1);
  if (g_strv_length (parts) != 2)
    {
      g_strfreev (parts);
      return -1;
    }
  *host = g_strdup (parts[0]);
  *hostname = g_strdup (parts[1]);
  g_strfreev (parts);
  return 0;
}

/**
 * @brief Add a container image scan result to a report.
 *
 * @param[in]      res           The container image scan result.
 * @param[in,out]  results_aux   Auxiliary data, a pointer to struct
 *                               report_aux.
 */
static void
add_container_image_scan_result (http_scanner_result_t res,
                                 gpointer *results_aux)
{

  struct report_aux *rep_aux = *results_aux;
  result_t result;
  char *type, *host, *hostname, *test_id;
  char *port = NULL, *desc = NULL;

  type = res->type;
  test_id = res->oid;

  host = extract_sha256_digest_if_found (res->ip_address);
  hostname = res->hostname ?: "";
  port = res->port;
  desc = res->message;

  if (host)
    manage_container_scan_report_host_add (rep_aux->report,
                                           host,
                                           hostname,
                                           0, 0);

  if (strcmp (type, "host_detail") == 0)
    {
      gchar *hash_value = NULL;
      if (!check_container_scan_host_detail_exists (rep_aux->report,
                                                    host, hostname,
                                                    res->detail_source_type,
                                                    res->detail_source_name,
                                                    res->detail_source_description,
                                                    res->detail_name,
                                                    res->detail_value,
                                                    &hash_value,
                                                    rep_aux->hash_hostdetails))
        {
          insert_container_scan_report_host_detail (rep_aux->report,
                                                    host, hostname,
                                                    res->detail_source_type,
                                                    res->detail_source_name,
                                                    res->detail_source_description,
                                                    res->detail_name,
                                                    res->detail_value,
                                                    hash_value);
        }
    }
  else if (strcmp (type, "host_start") == 0)
    {
      set_container_scan_host_start_time_isotime (rep_aux->report,
                                                  host,
                                                  hostname,
                                                  desc);
    }
  else if (strcmp (type, "host_end") == 0)
    {
      set_container_scan_host_end_time_isotime (rep_aux->report,
                                                host,
                                                hostname,
                                                desc);
    }
  else
    {
      char *nvt_id = NULL, *severity_str = NULL;
      char *hash_value, *result_type;
      int qod_int;

      nvt_id = g_strdup (test_id);
      result_type = convert_http_scanner_type_to_osp_type (type);
      severity_str = nvt_severity (test_id, result_type);
      qod_int = get_http_scanner_nvti_qod (test_id);

      if (!check_http_scanner_result_exists (rep_aux->report, rep_aux->task,
                                             res, &hash_value,
                                             rep_aux->hash_results))
        {
          result = make_osp_result (rep_aux->task,
                                    host ?: "",
                                    hostname ?: "",
                                    nvt_id ?: "",
                                    result_type ?: "",
                                    desc ?: "",
                                    port ?: "",
                                    severity_str ?: NULL,
                                    qod_int,
                                    NULL,
                                    hash_value);
          g_array_append_val (rep_aux->results_array, result);
        }
      g_free (hash_value);
      g_free (severity_str);
      g_free (result_type);
      g_free (nvt_id);
    }

  return;
}

/**
 * @brief Parse container image scan results.
 *
 * @param[in]  task        Task.
 * @param[in]  report      Report.
 * @param[in]  results     container image scan results.
 */
static void
parse_container_image_scan_report (task_t task,
                                   report_t report,
                                   GSList *results,
                                   time_t start_time,
                                   time_t end_time)
{
  GArray *results_array = NULL;
  GHashTable *hashed_results = NULL;
  GHashTable *hashed_host_details = NULL;
  struct report_aux *rep_aux;

  assert (task);
  assert (report);

  sql_begin_immediate ();

  /* Set the report's start and end times. */
  if (start_time)
    set_scan_start_time_epoch (report, start_time);

  if (end_time)
    set_scan_end_time_epoch (report, end_time);

  if (results == NULL)
    {
      sql_commit ();
      return;
    }

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

  results_array = g_array_new(TRUE, TRUE, sizeof(result_t));
  rep_aux = g_malloc0 (sizeof (struct report_aux));
  rep_aux->report = report;
  rep_aux->task = task;
  rep_aux->results_array = results_array;
  rep_aux->hash_results = hashed_results;
  rep_aux->hash_hostdetails = hashed_host_details;

  g_slist_foreach(results,
                  (GFunc) add_container_image_scan_result,
                  &rep_aux);

  if (results_array->len > 0)
    {
      sql ("UPDATE reports SET modification_time = m_now() WHERE id = %llu;",
           report);
      report_add_results_array (report, results_array);
    }

  sql_commit ();
  if (results_array && results_array->len > 0)
    g_array_free (results_array, TRUE);

  g_hash_table_destroy (hashed_results);
  g_hash_table_destroy (hashed_host_details);
  g_free (rep_aux);
}

/**
 * @brief Prepare a report for resuming a container image scan.
 *
 * @param[in]  task     The task of the scan.
 * @param[in]  scan_id  The scan uuid.
 * @param[out] error    Error return.
 *
 * @return 0 scan finished or still running,
 *         1 scan must be started,
 *         -1 error
 */
int
prepare_container_image_scan_for_resume (task_t task, const char *scan_id,
                                         char **error)
{
  http_scanner_connector_t connection;
  int ret;

  assert (task);
  assert (scan_id);
  assert (global_current_report);
  assert (error);

  connection = container_image_scanner_connect (task_scanner (task), scan_id);
  if (connection == NULL)
    {
      *error = g_strdup ("Could not connect to container image Scanner");
      return -1;
    }

  g_debug ("%s: Preparing scan %s for resume", __func__, scan_id);

  ret = prepare_http_scanner_scan_for_resume (connection, error);

  if (ret == 1)
    trim_partial_report (global_current_report);

  http_scanner_connector_free (connection);

  return ret;
}

/**
 * @brief Get the boolean string for scanner preferences.
 *
 * @param[in]  value  The preference value.
 *
 * @return "true", "false" or a copy of the original value.
 */
char *
get_bool_string (const char *preference_value)
{
  if (strcmp (preference_value, "no") == 0
      || strcmp (preference_value, "0") == 0)
    return g_strdup("false");
  else if (strcmp (preference_value, "yes") == 0
           || strcmp (preference_value, "1") == 0)
    return g_strdup("true");
  else
    return g_strdup (preference_value);
}

/*
* @brief Get the value of a named scanner preference from a list of preferences.
*
* @param[in]  name        The name of the scanner preferences.
* @param[in]  scan_prefs  The list of preferences.
*
* @return     The value of the preference.
*/
static gchar *
get_preference_from_list (char *name, GSList *scan_prefs)
{
  gchar *value = NULL;
  GSList *point = scan_prefs;

  while (point)
    {
      http_scanner_param_t *param;
      char *p_name;

      param = point->data;
      p_name = http_scanner_param_id (param);

      if (p_name && strcmp (p_name, name) == 0)
        {
          value = g_strdup (http_scanner_param_default (param));
          break;
        }
      point = g_slist_next (point);
    }
  return value;
}

/*
* @brief Add container image scan preferences to the scanner options.
*        Task preferences override default scanner preferences.
*
* @param[in]  scanner_options  The scanner preferences table to add to.
* @param[in]  task             The task.
*
* @return     0 on success, -1 if error.
*/
static int
add_container_image_scan_preferences (http_scanner_connector_t connector,
                                      GHashTable *scanner_options,
                                      task_t task)
{
  GSList *scan_prefs = NULL;
  gchar *accept_invalid_certs, *registry_allow_insecure;
  int err = 0;

  accept_invalid_certs = task_preference_value (task, "accept_invalid_certs");

  if (accept_invalid_certs)
    g_hash_table_insert (scanner_options, g_strdup ("accept_invalid_certs"),
                         get_bool_string(accept_invalid_certs));

  registry_allow_insecure = task_preference_value (task, "registry_allow_insecure");

  if (registry_allow_insecure)
    g_hash_table_insert (scanner_options, g_strdup ("registry_allow_insecure"),
                         get_bool_string (registry_allow_insecure));


  if (!accept_invalid_certs || !registry_allow_insecure)
    err = http_scanner_parsed_scans_preferences (connector, &scan_prefs);

  if (err < 0)
    {
      if (scan_prefs)
        g_slist_free_full (scan_prefs, (GDestroyNotify) http_scanner_param_free);
      g_free (accept_invalid_certs);
      g_free (registry_allow_insecure);
      return err;
    };

  if (scan_prefs && !accept_invalid_certs)
    {
      accept_invalid_certs = get_preference_from_list ("accept_invalid_certs", scan_prefs);
      if (accept_invalid_certs)
        g_hash_table_insert (scanner_options, g_strdup ("accept_invalid_certs"),
                             get_bool_string (accept_invalid_certs));
    }

  if (scan_prefs && !registry_allow_insecure)
    {
      registry_allow_insecure = get_preference_from_list ("registry_allow_insecure", scan_prefs);
      if (registry_allow_insecure)
        g_hash_table_insert (scanner_options, g_strdup ("registry_allow_insecure"),
                             get_bool_string (registry_allow_insecure));
    }

  g_slist_free_full (scan_prefs, (GDestroyNotify) http_scanner_param_free);
  g_free (accept_invalid_certs);
  g_free (registry_allow_insecure);

  return err;
}

/**
 * @brief Launch container image scanning task.
 *
 * @param[in]   task              The task.
 * @param[in]   oci_image_target  The OCI image target.
 * @param[in]   scan_id           The scan uuid.
 * @param[in]   from              0 start from beginning,
 *                                1 continue from stopped,
 *                                2 continue if stopped else start from
 *                                  beginning.
 * @param[out]  error             Error return.
 *
 * @return 0 on success, -1 if error.
 */
static int
launch_container_image_task (task_t task,
                             oci_image_target_t oci_image_target,
                             const char *scan_id,
                             int from,
                             char **error)
{
  http_scanner_connector_t connection;
  char *oci_image_references_str, *finished_images_str, *exclude_images_str;
  container_image_target_t *container_image_target;
  container_image_credential_t *credential;
  gchar *max_checks, *max_hosts;
  GHashTable *scanner_options;
  http_scanner_resp_t response;
  int ret;

  connection = NULL;
  finished_images_str = NULL;

  /* Prepare the report */
  if (from)
    {
      ret = prepare_container_image_scan_for_resume (task, scan_id, error);
      if (ret == 0)
        return 0;
      else if (ret == -1)
        return -1;
      finished_images_str = report_finished_container_images_str (global_current_report);
    }

  oci_image_references_str
    = oci_image_target_image_references (oci_image_target);

  exclude_images_str = oci_image_target_exclude_images (oci_image_target);

  GString *target_exclude_images
    = g_string_new (exclude_images_str ? exclude_images_str : "");

  if (finished_images_str != NULL && strlen (finished_images_str) > 0)
    {
      g_string_append_printf (target_exclude_images,
                              "%s%s",
                              exclude_images_str
                                && strlen (exclude_images_str) > 0 ? "," : "",
                              finished_images_str);
    }

  container_image_target
      = container_image_target_new (scan_id, oci_image_references_str,
                                    target_exclude_images->str);

  g_string_free (target_exclude_images, TRUE);
  free (exclude_images_str);
  free (finished_images_str);
  free (oci_image_references_str);

  credential = container_image_target_credential (oci_image_target);

  if (credential)
      container_image_target_add_credential (container_image_target, credential);
  else
    g_warning ("%s: No credential assigned to target.", __func__);

  /* Setup scanner preferences */
  scanner_options
    = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  /* Setup user-specific scanner preference */
  add_user_scan_preferences (scanner_options);

  /* Setup general task preferences */
  max_checks = task_preference_value (task, "max_checks");
  g_hash_table_insert (scanner_options, g_strdup ("max_checks"),
                       max_checks ? max_checks : g_strdup (MAX_CHECKS_DEFAULT));

  max_hosts = task_preference_value (task, "max_hosts");
  g_hash_table_insert (scanner_options, g_strdup ("max_hosts"),
                       max_hosts ? max_hosts : g_strdup (MAX_HOSTS_DEFAULT));

  connection = container_image_scanner_connect (task_scanner (task), scan_id);
  if (!connection)
    {
      if (error)
        *error = g_strdup ("Could not connect to Scanner");
      container_image_target_free (container_image_target);
      g_hash_table_destroy (scanner_options);
      return -1;
    }

  /* Setup container image scanner preferences */
  if (add_container_image_scan_preferences (connection,
                                            scanner_options,
                                            task) < 0)
    {
      if (error)
        *error = g_strdup ("Could not get scan preferences from scanner");
      container_image_target_free (container_image_target);
      g_hash_table_destroy (scanner_options);
      return -1;
    }

  gchar *scan_config = NULL;
  scan_config =
    container_image_build_scan_config_json (container_image_target,
                                            scanner_options);

  response = http_scanner_create_scan (connection, scan_config);

  if (response->code != 201)
    {
      g_warning ("%s: Failed to create scan: %ld", __func__, response->code);
      container_image_target_free (container_image_target);
      g_hash_table_destroy (scanner_options);
      http_scanner_response_cleanup (response);
      return -1;
    }

  http_scanner_response_cleanup (response);
  response = http_scanner_start_scan (connection);

  if (response->code != 204)
    {
      g_warning ("%s: Failed to start scan: %ld", __func__, response->code);
      container_image_target_free (container_image_target);
      g_hash_table_destroy (scanner_options);
      http_scanner_response_cleanup (response);
      return -1;
    }

  container_image_target_free (container_image_target);
  g_hash_table_destroy (scanner_options);
  http_scanner_response_cleanup (response);
  http_scanner_connector_free (connection);

  return 0;
}

/**
 * @brief Handle a container image scan, until success or failure.
 *
 * @param[in]   task      The task.
 * @param[in]   report    The report.
 * @param[in]   scan_id   The UUID of the scan on the scanner.
 *
 * @return 0 if success, -1 if error, -2 if scan was stopped,
 *         -3 if the scan was interrupted, -4 already stopped.
 */
static int
handle_container_image_scan (task_t task,
                             report_t report,
                             const char *scan_id)
{
  scanner_t scanner;
  http_scanner_connector_t connector;
  http_scanner_resp_t response;
  int rc;

  scanner = task_scanner (task);
  connector = container_image_scanner_connect (scanner, scan_id);

  if (connector == NULL)
    {
      g_warning ("%s: Could not connect to container image scanner", __func__);
      return -1;
    }

  gboolean started, queued_status_updated;
  int retry, connection_retry;

  if (connector == NULL)
    {
      g_warning ("%s: Could not connect to http scanner", __func__);
      return -1;
    }

  response = NULL;
  started = FALSE;
  queued_status_updated = FALSE;
  connection_retry = get_scanner_connection_retry ();

  retry = connection_retry;
  rc = -1;
  while (retry >= 0)
    {
      int run_status;

      run_status = task_run_status (task);
      if (run_status == TASK_STATUS_STOPPED
          || run_status == TASK_STATUS_STOP_REQUESTED)
        {
          rc = -4;
          break;
        }

      rc = update_http_scanner_scan (connector, task, report,
                                     parse_container_image_scan_report, &retry,
                                     &queued_status_updated, &started);

      if (rc <= 0)
        break;

      if (rc != 1)
        {
          retry = connection_retry;
          gvm_sleep (5);
        }
    }

  http_scanner_response_cleanup (response);
  http_scanner_connector_free (connector);

  return rc;
}

/**
 * @brief Fork a child to handle a container image scan's
 *        fetching and inserting.
 *
 * @param[in]   task               The task.
 * @param[in]   oci_image_target   The OCI image target.
 * @param[in]   from               0 start from beginning,
 *                                 1 continue from stopped,
 *                                 2 continue if stopped else
 *                                   start from beginning.
 * @param[out]  report_id_return   UUID of the report.
 *
 * @return Parent returns with 0 if success, -1 if failure. Child process
 *         doesn't return and simply exits.
 */
static int
fork_container_image_scan_handler (task_t task,
                                   oci_image_target_t oci_image_target,
                                   int from,
                                   char **report_id_return)
{
  char *report_id, *error = NULL;
  int rc;

  assert (task);
  assert (oci_image_target);

  if (report_id_return)
    *report_id_return = NULL;

  if (run_osp_scan_get_report (task, from, &report_id))
    return -1;

  current_scanner_task = task;
  set_task_run_status (task, TASK_STATUS_REQUESTED);

  switch (fork ())
    {
      case 0:
        init_sentry ();
        break;
      case -1:
        /* Parent, failed to fork. */
        global_current_report = 0;
        g_warning ("%s: Failed to fork: %s",
                   __func__,
                   strerror (errno));
        set_task_interrupted (task,
                              "Error forking scan handler."
                              "  Interrupting scan.");
        set_report_scan_run_status (global_current_report,
                                    TASK_STATUS_INTERRUPTED);
        global_current_report = (report_t) 0;
        current_scanner_task = 0;
        g_free (report_id);
        return -9;
      default:
        /* Parent, successfully forked. */
        global_current_report = 0;
        current_scanner_task = 0;
        if (report_id_return)
          *report_id_return = report_id;
        else
          g_free (report_id);
        return 0;
    }

  /* Child: Re-open DB after fork and periodically check scan progress.
   * If progress == 100%: Parse the report results and other info then exit(0).
   * Else, exit(1) in error cases like connection to scanner failure.
   */
  reinit_manage_process ();
  manage_session_init (current_credentials.uuid);

  rc = launch_container_image_task (task, oci_image_target, report_id, from,
                                    &error);

  if (rc < 0)
    {
      result_t result;

      g_warning ("container image scanner start_scan %s: %s", report_id, error);
      result = make_osp_result (task, "", "", "",
                                threat_message_type ("Error"),
                                error, "", "", QOD_DEFAULT, NULL, NULL);
      report_add_result (global_current_report, result);
      set_task_run_status (task, TASK_STATUS_DONE);
      set_report_scan_run_status (global_current_report, TASK_STATUS_DONE);
      set_task_end_time_epoch (task, time (NULL));
      set_scan_end_time_epoch (global_current_report, time (NULL));

      g_free (error);
      g_free (report_id);
      gvm_close_sentry ();
      exit (-1);
    }

  setproctitle ("container image scanner: Handling scan %s", report_id);

  g_info ("%s: Handling container image scan %s", __func__, report_id);

  rc = handle_container_image_scan (task, global_current_report, report_id);

  g_free (report_id);

  if (rc >= 0)
    {
      set_task_run_status (task, TASK_STATUS_PROCESSING);
      set_report_scan_run_status (global_current_report,
                                  TASK_STATUS_PROCESSING);
      asset_snapshots_container_image (global_current_report, task);
      set_task_run_status (task, TASK_STATUS_DONE);
      set_report_scan_run_status (global_current_report, TASK_STATUS_DONE);
      if (feature_enabled (FEATURE_ID_SECURITY_INTELLIGENCE_EXPORT))
        {
          queue_report_for_export (global_current_report);
        }
    }
  else if (rc == -1 || rc == -2)
    {
      set_task_run_status (task, TASK_STATUS_STOPPED);
      set_report_scan_run_status (global_current_report, TASK_STATUS_STOPPED);
    }
  else if (rc == -3)
    {
      set_task_run_status (task, TASK_STATUS_INTERRUPTED);
      set_report_scan_run_status (global_current_report,
                                  TASK_STATUS_INTERRUPTED);
    }

  set_task_end_time_epoch (task, time (NULL));
  set_scan_end_time_epoch (global_current_report, time (NULL));
  global_current_report = 0;
  current_scanner_task = (task_t) 0;
  gvm_close_sentry ();
  exit (rc);
}

/**
 * @brief Start a task on a container image scanner.
 *
 * @param[in]   task       The task.
 * @param[in]   from       0 start from beginning, 1 continue from stopped,
 *                         2 continue if stopped else start from beginning.
 * @param[out]  report_id  The report ID.
 *
 * @return 0 success, 99 permission denied, -1 error.
 */
int
run_container_image_task (task_t task, int from, char **report_id)
{
  if (!feature_enabled (FEATURE_ID_CONTAINER_SCANNING))
    {
      g_warning ("%s: container scanning runtime flag is disabled", __func__);
      return -1;
    }
  oci_image_target_t oci_image_target;

  oci_image_target = task_oci_image_target (task);
  if (oci_image_target)
    {
      char *uuid;
      oci_image_target_t found;

      uuid = oci_image_target_uuid (oci_image_target);
      if (find_oci_image_target_with_permission (uuid,
                                                 &found,
                                                 "get_oci_image_targets"))
        {
          g_warning ("%s: Failed to find OCI image target %s", __func__, uuid);
          g_free (uuid);
          return -1;
        }
      g_free (uuid);
      if (found == 0)
        return 99;
    }

  if (fork_container_image_scan_handler (task,
                                         oci_image_target,
                                         from,
                                         report_id))
    {
      g_warning ("Couldn't fork container image scan handler");
      return -1;
    }
  return 0;
}

/**
 * @brief Stop a container scanning task.
 *
 * @param[in]   task  The task.
 *
 * @return 0 on success, else -1.
 */
int
stop_container_image_task (task_t task)
{
  if (!feature_enabled (FEATURE_ID_CONTAINER_SCANNING))
    {
      g_warning ("%s: container scanning runtime flag is disabled", __func__);
      return -1;
    }
  int ret = 0;
  report_t scan_report;
  char *scan_id;
  task_t previous_task;
  report_t previous_report;

  scanner_t scanner;
  http_scanner_resp_t response;
  http_scanner_connector_t connector = NULL;

  scan_report = task_running_report (task);
  if (!scan_report)
    return 0;

  previous_task = current_scanner_task;
  previous_report = global_current_report;

  scan_id = report_uuid (scan_report);
  if (!scan_id)
    {
      ret = -1;
      g_warning ("%s: Failed to get scan ID from report %lld",
                 __func__, scan_report);
      goto end_stop_openvasd;
    }
  scanner = task_scanner (task);
  connector = container_image_scanner_connect (scanner, scan_id);
  if (!connector)
    {
      ret = -1;
      goto end_stop_openvasd;
    }

  current_scanner_task = task;
  global_current_report = task_running_report (task);
  set_task_run_status (task, TASK_STATUS_STOP_REQUESTED);
  response = http_scanner_stop_scan (connector);
  if (response->code < 0)
    {
      ret = -1;
      http_scanner_response_cleanup (response);
      g_free (scan_id);
      goto end_stop_openvasd;
    }
  http_scanner_response_cleanup (response);

  ret = delete_http_scanner_scan_with_retry (connector, scan_id);
  g_free (scan_id);

end_stop_openvasd:
  http_scanner_connector_free (connector);
  set_task_end_time_epoch (task, time (NULL));
  set_task_run_status (task, TASK_STATUS_STOPPED);
  if (scan_report)
    {
      set_scan_end_time_epoch (scan_report, time (NULL));
      set_report_scan_run_status (scan_report, TASK_STATUS_STOPPED);
    }
  current_scanner_task = previous_task;
  global_current_report = previous_report;

  return ret;
}

#endif
