mageni/mageni

View on GitHub
src/backend/api/src/api_get.c

Summary

Maintainability
Test Coverage
/**
 * SPDX-License-Identifier: GPL-2.0-or-later
 * SPDX-FileCopyrightText: Copyright 2018 Greenbone Networks GmbH
 * SPDX-FileComment: Base facilities.
 * SPDX-FileContributor: Matthew Mundell <matthew.mundell@greenbone.net>
 * SPDX-FileContributor: Mageni Security LLC
 * 
 */

#include "api_get.h"

#include "api_base.h"
#include "manage_acl.h"

#include <stdlib.h>
#include <string.h>

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

/**
 * @brief Parse attributes for a GET command.
 *
 * @param[in]  data              GET operation data.
 * @param[in]  type              Resource type.
 * @param[in]  attribute_names   XML attribute names.
 * @param[in]  attribute_values  XML attribute values.
 *
 * @param[in]  data  Command data.
 */
void
get_data_parse_attributes (get_data_t *data,
                           const gchar *type,
                           const gchar **attribute_names,
                           const gchar **attribute_values)
{
  gchar *name;
  const gchar *attribute;

  data->type = g_strdup (type);

  append_attribute (attribute_names, attribute_values, "filter", &data->filter);

  name = g_strdup_printf ("%s_id", type);
  append_attribute (attribute_names, attribute_values, name, &data->id);
  g_free (name);

  append_attribute (
    attribute_names, attribute_values, "filt_id", &data->filt_id);

  if (find_attribute (attribute_names, attribute_values, "trash", &attribute))
    data->trash = strcmp (attribute, "0");
  else
    data->trash = 0;

  if (find_attribute (attribute_names, attribute_values, "details", &attribute))
    data->details = strcmp (attribute, "0");
  else
    data->details = 0;

  if (find_attribute (
        attribute_names, attribute_values, "ignore_pagination", &attribute))
    data->ignore_pagination = strcmp (attribute, "0");
  else
    data->ignore_pagination = 0;

  append_attribute (
    attribute_names, attribute_values, "filter_replace", &data->filter_replace);
}

/**
 * @brief Init for a GET handler.
 *
 * @param[in]  command       GMP command name.
 * @param[in]  get           GET data.
 * @param[in]  setting_name  Type name for setting.
 * @param[out] first         First result, from filter.
 *
 * @return 0 success, -1 error.
 */
int
init_get (gchar *command,
          get_data_t *get,
          const gchar *setting_name,
          int *first)
{
  gchar *filter, *replacement;

  if (acl_user_may (command) == 0)
    return 99;

  /* Get any replacement out of get->filter, before it changes.  Used to add
   * task_id to the filter for GET_REPORTS. */

  if (get->filter_replace && strlen (get->filter_replace) && get->filter)
    replacement = filter_term_value (get->filter, get->filter_replace);
  else
    replacement = NULL;

  /* Switch to the default filter from the setting, if required. */

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_USER_SETTING) == 0)
    {
      char *user_filter = setting_filter (setting_name);

      if (user_filter && strlen (user_filter))
        {
          get->filt_id = user_filter;
          get->filter = filter_term (user_filter);
        }
      else
        {
          free (user_filter);
          get->filt_id = g_strdup ("0");
        }
    }

  /* Get the actual filter string. */

  if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
    {
      filter = filter_term (get->filt_id);
      if (filter == NULL)
        {
          char *user_filter;

          /* Probably the user deleted the filter, switch to default. */

          g_free (get->filt_id);

          user_filter = setting_filter (setting_name);
          if (user_filter && strlen (user_filter))
            {
              get->filt_id = user_filter;
              get->filter = filter_term (user_filter);
              filter = filter_term (get->filt_id);
            }
          else
            get->filt_id = g_strdup ("0");
        }
    }
  else
    filter = NULL;

  if (replacement)
    {
      const gchar *term;

      /* Replace the term in filter.  Used to add task_id to the filter
       * for GET_REPORTS. */

      term = filter ? filter : get->filter;

      if (term)
        {
          gchar *new_filter, *clean;

          clean = manage_clean_filter_remove (term, get->filter_replace);
          new_filter = g_strdup_printf (
            "%s=%s %s", get->filter_replace, replacement, clean);
          g_free (clean);
          if (get->filter)
            {
              g_free (get->filter);
              get->filter = new_filter;
            }
          else
            {
              g_free (filter);
              filter = new_filter;
            }
          get->filter_replacement = g_strdup (new_filter);
        }

      g_free (replacement);
    }

  /* Get the value of "first" from the filter string.
   *
   * This is used by get_next when the result set is empty, to determine if
   * the query should be rerun with first 1.
   */
  manage_filter_controls (
    filter ? filter : get->filter, first, NULL, NULL, NULL);

  g_free (filter);

  return 0;
}

/**
 * @brief Iterate a GET iterator.
 *
 * If the user requested to start at an offset from the first result, but
 * the result set was empty, then reset the iterator to start from the
 * first result.
 *
 * @param[in]  resources  Resource iterator.
 * @param[in]  get        GET command data.
 * @param[out] first      First.  Number of first item to get.
 * @param[out] count      Count.
 * @param[in]  init       Init function, to reset the iterator.
 *
 * @return What to do next: 0 continue, 1 end, -1 fail.
 */
int
get_next (iterator_t *resources,
          get_data_t *get,
          int *first,
          int *count,
          int (*init) (iterator_t *, const get_data_t *))
{
  if (next (resources) == FALSE)
    {
      gchar *new_filter;

      if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE))
        /* If filtering by a named filter, then just end, because changing
         * the filter term would probably surprise the user. */
        return 1;

      if (*first == 0)
        return 1;

      if (*first == 1 || *count > 0)
        /* Some results were found or first was 1, so stop iterating. */
        return 1;

      /* Reset the iterator with first 1, and start again. */
      cleanup_iterator (resources);
      new_filter = g_strdup_printf ("first=1 %s", get->filter);
      g_free (get->filter);
      get->filter = new_filter;
      if (init (resources, get))
        return -1;
      *count = 0;
      *first = 1;
      if (next (resources) == FALSE)
        return 1;
    }
  return 0;
}

/**
 * @brief Send start of GET response.
 *
 * @param[in]  type                  Type.
 * @param[in]  write_to_client       Function that sends to clients.
 * @param[in]  write_to_client_data  Data for write_to_client.
 *
 * @return 0 success, 1 send to client failed.
 */
int
send_get_start (const char *type,
                int (*write_to_client) (const char *, void *),
                void *write_to_client_data)
{
  gchar *msg;

  if (strcmp (type, "info"))
    msg = g_markup_printf_escaped ("<get_%ss_response"
                                   " status=\"" STATUS_OK "\""
                                   " status_text=\"" STATUS_OK_TEXT "\">",
                                   type);
  else
    msg = g_markup_printf_escaped ("<get_%s_response"
                                   " status=\"" STATUS_OK "\""
                                   " status_text=\"" STATUS_OK_TEXT "\">",
                                   type);

  if (send_to_client (msg, write_to_client, write_to_client_data))
    {
      g_free (msg);
      return 1;
    }
  g_free (msg);
  return 0;
}

/**
 * @brief Send common part of GET response for a single resource.
 *
 * @param[in]  type                  Type.
 * @param[in]  get                   GET data.
 * @param[in]  iterator              Iterator.
 * @param[in]  write_to_client       Function that sends to clients.
 * @param[in]  write_to_client_data  Data for write_to_client.
 * @param[in]  writable              Whether the resource is writable.
 * @param[in]  in_use                Whether the resource is in use.
 *
 * @return 0 success, 1 send error.
 */
int
send_get_common (const char *type,
                 get_data_t *get,
                 iterator_t *iterator,
                 int (*write_to_client) (const char *, void *),
                 void *write_to_client_data,
                 int writable,
                 int in_use)
{
  GString *buffer;
  const char *tag_type;
  iterator_t tags;
  int tag_count;

  buffer = g_string_new ("");

  buffer_xml_append_printf (
    buffer,
    "<%s id=\"%s\">"
    "<owner><name>%s</name></owner>"
    "<name>%s</name>"
    "<comment>%s</comment>"
    "<creation_time>%s</creation_time>"
    "<modification_time>%s</modification_time>"
    "<writable>%i</writable>"
    "<in_use>%i</in_use>"
    "<permissions>",
    type,
    get_iterator_uuid (iterator) ? get_iterator_uuid (iterator) : "",
    get_iterator_owner_name (iterator) ? get_iterator_owner_name (iterator)
                                       : "",
    get_iterator_name (iterator) ? get_iterator_name (iterator) : "",
    get_iterator_comment (iterator) ? get_iterator_comment (iterator) : "",
    get_iterator_creation_time (iterator)
      ? get_iterator_creation_time (iterator)
      : "",
    get_iterator_modification_time (iterator)
      ? get_iterator_modification_time (iterator)
      : "",
    writable,
    in_use);

  if (/* The user is the owner. */
      (current_credentials.username && get_iterator_owner_name (iterator)
       && (strcmp (get_iterator_owner_name (iterator),
                   current_credentials.username)
           == 0))
      /* Or the user is effectively the owner. */
      || acl_user_has_super (current_credentials.uuid,
                             get_iterator_owner (iterator))
      /* Or the user has Admin rights and the resource is a permission or a
       * report format... */
      || (current_credentials.uuid
          && ((strcmp (type, "permission") == 0)
              && get_iterator_uuid (iterator)
              /* ... but not the special Admin permission. */
              && permission_is_admin (get_iterator_uuid (iterator)))
          && acl_user_can_everything (current_credentials.uuid)))
    {
      buffer_xml_append_printf (buffer,
                                "<permission>"
                                "<name>Everything</name>"
                                "</permission>"
                                "</permissions>");
    }
  else if (current_credentials.uuid && (strcmp (type, "user") == 0)
           && acl_user_can_super_everyone (get_iterator_uuid (iterator))
           && strcmp (get_iterator_uuid (iterator), current_credentials.uuid))
    {
      /* Resource is the Super Admin. */
      buffer_xml_append_printf (
        buffer,
        "<permission><name>get_users</name></permission>"
        "</permissions>");
    }
  else
    {
      iterator_t perms;
      get_data_t perms_get;

      memset (&perms_get, '\0', sizeof (perms_get));
      perms_get.filter = g_strdup_printf ("resource_uuid=%s"
                                          " owner=any"
                                          " permission=any",
                                          get_iterator_uuid (iterator));
      init_permission_iterator (&perms, &perms_get);
      g_free (perms_get.filter);
      while (next (&perms))
        buffer_xml_append_printf (buffer,
                                  "<permission><name>%s</name></permission>",
                                  get_iterator_name (&perms));
      cleanup_iterator (&perms);

      buffer_xml_append_printf (buffer, "</permissions>");
    }

  tag_type = get->subtype ? get->subtype : get->type;
  tag_count =
    resource_tag_count (tag_type, get_iterator_resource (iterator), 1);

  if (tag_count)
    {
      if (get->details || get->id)
        {
          buffer_xml_append_printf (buffer,
                                    "<user_tags>"
                                    "<count>%i</count>",
                                    tag_count);

          init_resource_tag_iterator (
            &tags, tag_type, get_iterator_resource (iterator), 1, NULL, 1);

          while (next (&tags))
            {
              buffer_xml_append_printf (buffer,
                                        "<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);

          buffer_xml_append_printf (buffer, "</user_tags>");
        }
      else
        {
          buffer_xml_append_printf (buffer,
                                    "<user_tags>"
                                    "<count>%i</count>"
                                    "</user_tags>",
                                    tag_count);
        }
    }

  if (send_to_client (buffer->str, write_to_client, write_to_client_data))
    {
      g_string_free (buffer, TRUE);
      return 1;
    }
  g_string_free (buffer, TRUE);
  return 0;
}

/**
 * @brief Write data of a GET command filter to a string buffer as XML.
 *
 * @param[in] msg          The string buffer to write to.
 * @param[in] type         The filtered type.
 * @param[in] get          GET data.
 * @param[in] filter_term  Filter term.
 * @param[in] extra_xml    Extra XML to include in the FILTER element.
 *
 * @return Always 0.
 */
int
buffer_get_filter_xml (GString *msg,
                       const char *type,
                       const get_data_t *get,
                       const char *filter_term,
                       const char *extra_xml)
{
  keyword_t **point;
  array_t *split;
  filter_t filter;

  buffer_xml_append_printf (msg,
                            "<filters id=\"%s\">"
                            "<term>%s</term>",
                            get->filt_id ? get->filt_id : "",
                            filter_term);

  if (get->filt_id && strcmp (get->filt_id, "")
      && (find_filter_with_permission (get->filt_id, &filter, "get_filters")
          == 0)
      && filter != 0)
    buffer_xml_append_printf (msg, "<name>%s</name>", filter_name (filter));

  if (extra_xml)
    g_string_append (msg, extra_xml);

  buffer_xml_append_printf (msg, "<keywords>");

  split = split_filter (filter_term);
  point = (keyword_t **) split->pdata;
  while (*point)
    {
      keyword_t *keyword;
      keyword = *point;
      buffer_xml_append_printf (
        msg,
        "<keyword>"
        "<column>%s</column>"
        "<relation>%s</relation>"
        "<value>%s%s%s</value>"
        "</keyword>",
        keyword->column ? keyword->column : "",
        keyword->equal ? "="
                       : (keyword_special (keyword)
                            ? ""
                            : keyword_relation_symbol (keyword->relation)),
        keyword->quoted ? "\"" : "",
        keyword->string ? keyword->string : "",
        keyword->quoted ? "\"" : "");
      point++;
    }
  filter_free (split);

  buffer_xml_append_printf (msg,
                            "</keywords>"
                            "</filters>");
  return 0;
}

/**
 * @brief Send end of GET response.
 *
 * @param[in]  type                  Type.
 * @param[in]  get                   GET data.
 * @param[in]  get_counts            Include counts.
 * @param[in]  count                 Page count.
 * @param[in]  filtered              Filtered count.
 * @param[in]  full                  Full count.
 * @param[in]  write_to_client       Function that sends to clients.
 * @param[in]  write_to_client_data  Data for write_to_client.
 *
 * @return 0 success, 1 sending to client failed, 2 failed to allocate filter
 *         term.
 */
static int
send_get_end_internal (const char *type,
                       get_data_t *get,
                       int get_counts,
                       int count,
                       int filtered,
                       int full,
                       int (*write_to_client) (const char *, void *),
                       void *write_to_client_data)
{
  gchar *sort_field, *filter;
  int first, max, sort_order;
  GString *type_many, *msg;

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

  manage_filter_controls (
    filter ? filter : get->filter, &first, &max, &sort_field, &sort_order);

  if (get->ignore_pagination && (strcmp (type, "task") == 0))
    {
      first = 1;
      max = -1;
    }

  max = manage_max_rows (max);

  if (filter || get->filter)
    {
      gchar *new_filter;
      new_filter = manage_clean_filter (filter ? filter : get->filter);
      g_free (filter);
      if ((strcmp (type, "task") == 0) || (strcmp (type, "report") == 0)
          || (strcmp (type, "result") == 0))
        {
          gchar *value;

          value = filter_term_value (new_filter, "min_qod");
          if (value == NULL)
            {
              filter = new_filter;
              new_filter =
                g_strdup_printf ("min_qod=%i %s", MIN_QOD_DEFAULT, filter);
              g_free (filter);
            }
          g_free (value);

          value = filter_term_value (new_filter, "apply_overrides");
          if (value == NULL)
            {
              filter = new_filter;
              new_filter = g_strdup_printf (
                "apply_overrides=%i %s", APPLY_OVERRIDES_DEFAULT, filter);
              g_free (filter);
            }
          g_free (value);
        }
      filter = new_filter;
    }
  else
    {
      if ((strcmp (type, "task") == 0) || (strcmp (type, "report") == 0)
          || (strcmp (type, "result") == 0))
        filter = manage_clean_filter ("apply_overrides=" G_STRINGIFY (
          APPLY_OVERRIDES_DEFAULT) " min_qod=" G_STRINGIFY (MIN_QOD_DEFAULT));
      else
        filter = manage_clean_filter ("");
    }

  type_many = g_string_new (type);

  if (strcmp (type, "info") != 0)
    g_string_append (type_many, "s");

  msg = g_string_new ("");

  buffer_get_filter_xml (msg, type, get, filter, NULL);

  buffer_xml_append_printf (msg,
                            "<sort>"
                            "<field>%s<order>%s</order></field>"
                            "</sort>"
                            "<%s start=\"%i\" max=\"%i\"/>",
                            sort_field,
                            sort_order ? "ascending" : "descending",
                            type_many->str,
                            first,
                            max);
  if (get_counts)
    buffer_xml_append_printf (msg,
                              "<%s_count>"
                              "%i"
                              "<filtered>%i</filtered>"
                              "<page>%i</page>"
                              "</%s_count>",
                              type,
                              full,
                              filtered,
                              count,
                              type);
  buffer_xml_append_printf (msg, "</get_%s_response>", type_many->str);
  g_string_free (type_many, TRUE);
  g_free (sort_field);
  g_free (filter);

  if (send_to_client (msg->str, write_to_client, write_to_client_data))
    {
      g_string_free (msg, TRUE);
      return 1;
    }
  g_string_free (msg, TRUE);
  return 0;
}

/**
 * @brief Send end of GET response.
 *
 * @param[in]  type                  Type.
 * @param[in]  get                   GET data.
 * @param[in]  count                 Page count.
 * @param[in]  filtered              Filtered count.
 * @param[in]  full                  Full count.
 * @param[in]  write_to_client       Function that sends to clients.
 * @param[in]  write_to_client_data  Data for write_to_client.
 *
 * @return 0 success, 1 sending to client failed, 2 failed to allocate filter
 *         term.
 */
int
send_get_end (const char *type,
              get_data_t *get,
              int count,
              int filtered,
              int full,
              int (*write_to_client) (const char *, void *),
              void *write_to_client_data)
{
  return send_get_end_internal (
    type, get, 1, count, filtered, full, write_to_client, write_to_client_data);
}

/**
 * @brief Send end of GET response, skipping result counts.
 *
 * @param[in]  type                  Type.
 * @param[in]  get                   GET data.
 * @param[in]  write_to_client       Function that sends to clients.
 * @param[in]  write_to_client_data  Data for write_to_client.
 *
 * @return 0 success, 1 sending to client failed, 2 failed to allocate filter
 *         term.
 */
int
send_get_end_no_counts (const char *type,
                        get_data_t *get,
                        int (*write_to_client) (const char *, void *),
                        void *write_to_client_data)
{
  return send_get_end_internal (
    type, get, 0, 0, 0, 0, write_to_client, write_to_client_data);
}