diff --git a/src/gmp.c b/src/gmp.c index ae84d3e58..5fb9ec15b 100644 --- a/src/gmp.c +++ b/src/gmp.c @@ -101,6 +101,7 @@ #include "manage_report_configs.h" #include "manage_report_formats.h" #include "manage_tls_certificates.h" +#include "sql.h" #include "utils.h" #include @@ -128,6 +129,7 @@ #include #include #include +#include #undef G_LOG_DOMAIN /** @@ -13247,6 +13249,209 @@ handle_get_groups (gmp_parser_t *gmp_parser, GError **error) set_client_state (CLIENT_AUTHENTIC); } +/** + * @brief Print CPE match node with its matched CPEs. + * + * @param[in] node CPE match node to print. + * @param[in] buffer Buffer into which to print match node. + */ +static void +print_cpe_match_nodes_xml (resource_t node, GString *buffer) +{ + iterator_t cpe_match_nodes, cpe_match_ranges; + + init_iterator (&cpe_match_nodes, + "SELECT operator, negate" + " FROM scap.cpe_match_nodes WHERE id = %llu;", + node); + + const char *operator = NULL; + int negate = 0; + while (next (&cpe_match_nodes)) + { + operator = iterator_string (&cpe_match_nodes, 0); + negate = iterator_int (&cpe_match_nodes, 1); + } + cleanup_iterator (&cpe_match_nodes); + + xml_string_append (buffer, "%s", operator?: ""); + xml_string_append (buffer, "%s", negate? "1" : "0"); + + init_cpe_match_string_iterator (&cpe_match_ranges, node); + while (next (&cpe_match_ranges)) + { + const gchar *vsi, *vse, *vei, *vee, *match_criteria_id, *criteria, *status; + + xml_string_append (buffer, ""); + match_criteria_id + = cpe_match_string_iterator_match_criteria_id (&cpe_match_ranges); + criteria = cpe_match_string_iterator_criteria (&cpe_match_ranges); + status = cpe_match_string_iterator_status (&cpe_match_ranges); + + xml_string_append (buffer, + "%s" + "%s" + "%s", + criteria?: "", + cpe_match_string_iterator_vulnerable (&cpe_match_ranges) != 0 + ? "1" + : "0", + status?: ""); + + vsi = cpe_match_string_iterator_version_start_incl (&cpe_match_ranges); + vse = cpe_match_string_iterator_version_start_excl (&cpe_match_ranges); + vei = cpe_match_string_iterator_version_end_incl (&cpe_match_ranges); + vee = cpe_match_string_iterator_version_end_excl (&cpe_match_ranges); + + xml_string_append (buffer, + "%s", + vsi ?: ""); + xml_string_append (buffer, + "%s", + vse ?: ""); + xml_string_append (buffer, + "%s", + vei ?: ""); + xml_string_append (buffer, + "%s", + vee ?: ""); + + iterator_t cpe_matches; + init_cpe_match_string_matches_iterator (&cpe_matches, match_criteria_id); + xml_string_append (buffer, ""); + + while (next (&cpe_matches)) + { + iterator_t cpes; + + init_iterator (&cpes, + "SELECT deprecated FROM scap.cpes" + " WHERE cpe_name_id = '%s';", + cpe_matches_cpe_name_id(&cpe_matches)); + + const char* cpe = cpe_matches_cpe_name (&cpe_matches); + + int deprecated = 0; + while (next (&cpes)) + { + deprecated = iterator_int (&cpes, 0); + } + cleanup_iterator (&cpes); + + xml_string_append (buffer, "", cpe?: ""); + xml_string_append (buffer, + "%s", + deprecated ? "1" : "0"); + if (deprecated) + { + iterator_t deprecated_by; + init_cpe_deprecated_by_iterator (&deprecated_by, cpe); + while (next (&deprecated_by)) + { + xml_string_append (buffer, + "", + cpe_deprecated_by_iterator_deprecated_by + (&deprecated_by)); + } + cleanup_iterator (&deprecated_by); + } + xml_string_append (buffer, ""); + } + xml_string_append (buffer, ""); + xml_string_append (buffer, ""); + cleanup_iterator (&cpe_matches); + } + cleanup_iterator (&cpe_match_ranges); +} +/** + * @brief Print CVE affected software configurations + * + * @param[in] cve_uuid uuid of the CVE. + * @param[out] result Buffer into which to print. + * + */ +static void +print_cve_configurations_xml (const gchar *cve_uuid, GString *result) +{ + iterator_t cpe_match_root_nodes; + xml_string_append (result, ""); + init_cve_cpe_match_nodes_iterator (&cpe_match_root_nodes, cve_uuid); + while (next (&cpe_match_root_nodes)) + { + result_t root_node; + iterator_t cpe_match_node_childs; + root_node = cpe_match_nodes_iterator_root_id (&cpe_match_root_nodes); + xml_string_append (result, ""); + print_cpe_match_nodes_xml (root_node, result); + init_cpe_match_node_childs_iterator (&cpe_match_node_childs, root_node); + while (next (&cpe_match_node_childs)) + { + resource_t child_node; + child_node = + cpe_match_node_childs_iterator_id (&cpe_match_node_childs); + xml_string_append (result, ""); + print_cpe_match_nodes_xml (child_node, result); + xml_string_append (result, ""); + } + xml_string_append (result, ""); + cleanup_iterator (&cpe_match_node_childs); + } + xml_string_append (result, ""); + cleanup_iterator (&cpe_match_root_nodes); +} + +/** + * @brief Print CVE references + * + * @param[in] cve_uuid uuid of the CVE. + * @param[out] result Buffer into which to print. + * + */ +static void +print_cve_references_xml (const gchar *cve_uuid, GString *result) +{ + iterator_t references; + init_cve_reference_iterator (&references, cve_uuid); + xml_string_append (result, ""); + while (next (&references)) + { + xml_string_append (result, ""); + xml_string_append (result, + "%s", + cve_reference_iterator_url (&references)); + xml_string_append (result, ""); + const char * tags_array = cve_reference_iterator_tags (&references); + if(tags_array && strlen (tags_array) > 2) + { + char *trimmed_array + = g_strndup (tags_array + 1, strlen (tags_array) - 2); + gchar **tags, **current_tag; + tags = g_strsplit (trimmed_array, ",", -1); + current_tag = tags; + while (*current_tag) + { + if (strlen (*current_tag) > 2 + && (*current_tag)[0] == '"' + && (*current_tag)[strlen (*current_tag) - 1] == '"') + { + char *trimmed_tag = g_strndup (*current_tag + 1, + strlen (*current_tag) - 2); + xml_string_append (result, "%s", trimmed_tag); + g_free (trimmed_tag); + } + else + xml_string_append (result, "%s", *current_tag); + current_tag++; + } + g_strfreev (tags); + g_free (trimmed_array); + } + xml_string_append (result, ""); + xml_string_append (result, ""); + } + xml_string_append (result, ""); + cleanup_iterator (&references); +} /** * @brief Handle end of GET_INFO element. * @@ -13622,6 +13827,10 @@ handle_get_info (gmp_parser_t *gmp_parser, GError **error) ""); } g_string_append (result, ""); + + const gchar *cve_uuid = get_iterator_uuid (&info); + print_cve_configurations_xml (cve_uuid, result); + print_cve_references_xml (cve_uuid, result); } } else if (g_strcmp0 ("cert_bund_adv", get_info_data->type) == 0) diff --git a/src/manage.c b/src/manage.c index fca82209f..f61426986 100644 --- a/src/manage.c +++ b/src/manage.c @@ -3180,7 +3180,7 @@ check_cpe_match_rule (long long int node, gboolean *match, gboolean *vulnerable, return; } - init_cpe_match_range_iterator (&cpe_match_ranges, node); + init_cpe_match_string_iterator (&cpe_match_ranges, node); while (next (&cpe_match_ranges)) { iterator_t cpe_host_details_products; @@ -3188,11 +3188,11 @@ check_cpe_match_rule (long long int node, gboolean *match, gboolean *vulnerable, gchar *range_uri_product; gchar *vsi, *vse, *vei, *vee; range_fs_cpe = vsi = vse = vei = vee = NULL; - range_fs_cpe = g_strdup (cpe_match_range_iterator_cpe (&cpe_match_ranges)); - vsi = g_strdup (cpe_match_range_iterator_version_start_incl (&cpe_match_ranges)); - vse = g_strdup (cpe_match_range_iterator_version_start_excl (&cpe_match_ranges)); - vei = g_strdup (cpe_match_range_iterator_version_end_incl (&cpe_match_ranges)); - vee = g_strdup (cpe_match_range_iterator_version_end_excl (&cpe_match_ranges)); + range_fs_cpe = g_strdup (cpe_match_string_iterator_criteria (&cpe_match_ranges)); + vsi = g_strdup (cpe_match_string_iterator_version_start_incl (&cpe_match_ranges)); + vse = g_strdup (cpe_match_string_iterator_version_start_excl (&cpe_match_ranges)); + vei = g_strdup (cpe_match_string_iterator_version_end_incl (&cpe_match_ranges)); + vee = g_strdup (cpe_match_string_iterator_version_end_excl (&cpe_match_ranges)); range_uri_product = fs_cpe_to_uri_product (range_fs_cpe); init_host_details_cpe_product_iterator (&cpe_host_details_products, range_uri_product, report_host); while (next (&cpe_host_details_products)) @@ -3216,7 +3216,7 @@ check_cpe_match_rule (long long int node, gboolean *match, gboolean *vulnerable, cpe_struct_free (&source); cpe_struct_free (&target); } - if (*match && cpe_match_range_iterator_vulnerable (&cpe_match_ranges) == 1) + if (*match && cpe_match_string_iterator_vulnerable (&cpe_match_ranges) == 1) { cpe_struct_t source, target; cpe_struct_init (&source); diff --git a/src/manage.h b/src/manage.h index df8f144a4..2930789b9 100644 --- a/src/manage.h +++ b/src/manage.h @@ -1693,6 +1693,21 @@ app_locations_iterator_location (iterator_t*); void init_cpe_match_nodes_iterator (iterator_t*, const char *); +void +init_cve_cpe_match_nodes_iterator (iterator_t*, const char *); + +void +init_cve_reference_iterator (iterator_t*, const char *); + +const char* +cve_reference_iterator_url (iterator_t*); + +const char* +cve_reference_iterator_tags (iterator_t*); + +const char* +cve_reference_iterator_tags_count (iterator_t*); + long long int cpe_match_nodes_iterator_root_id (iterator_t*); @@ -1709,25 +1724,40 @@ long long int cpe_match_node_childs_iterator_id (iterator_t*); void -init_cpe_match_range_iterator (iterator_t*, long long int); +init_cpe_match_string_iterator (iterator_t*, long long int); const char* -cpe_match_range_iterator_cpe (iterator_t*); +cpe_match_string_iterator_criteria (iterator_t*); const char* -cpe_match_range_iterator_version_start_incl (iterator_t*); +cpe_match_string_iterator_match_criteria_id (iterator_t*); const char* -cpe_match_range_iterator_version_start_excl (iterator_t*); +cpe_match_string_iterator_status (iterator_t*); const char* -cpe_match_range_iterator_version_end_incl (iterator_t*); +cpe_match_string_iterator_version_start_incl (iterator_t*); const char* -cpe_match_range_iterator_version_end_excl (iterator_t*); +cpe_match_string_iterator_version_start_excl (iterator_t*); + +const char* +cpe_match_string_iterator_version_end_incl (iterator_t*); + +const char* +cpe_match_string_iterator_version_end_excl (iterator_t*); int -cpe_match_range_iterator_vulnerable (iterator_t*); +cpe_match_string_iterator_vulnerable (iterator_t*); + +void +init_cpe_match_string_matches_iterator (iterator_t*, const char *); + +const char* +cpe_matches_cpe_name_id (iterator_t*); + +const char* +cpe_matches_cpe_name (iterator_t*); void init_host_details_cpe_product_iterator (iterator_t*, const char *, report_host_t); diff --git a/src/manage_pg.c b/src/manage_pg.c index f53b9f601..43e031b99 100644 --- a/src/manage_pg.c +++ b/src/manage_pg.c @@ -3546,20 +3546,32 @@ manage_db_init (const gchar *name) sql ("CREATE TABLE scap2.cpe_match_nodes" " (id SERIAL PRIMARY KEY," - " parent_id INTEGER DEFAULT 0," - " root_id INTEGER DEFAULT 0," - " cve_id INTEGER DEFAULT 0," - " operator text);"); + " root_id integer DEFAULT 0," + " cve_id integer DEFAULT 0," + " operator text," + " negate integer DEFAULT 0);"); - sql ("CREATE TABLE scap2.cpe_match_range" + sql ("CREATE TABLE scap2.cpe_nodes_match_criteria" " (id SERIAL PRIMARY KEY," - " node_id INTEGER DEFAULT 0," - " vulnerable INTEGER DEFAULT 0," - " cpe text DEFAULT NULL," + " node_id integer DEFAULT 0," + " vulnerable integer DEFAULT 0," + " match_criteria_id text);"); + + sql ("CREATE TABLE scap2.cpe_match_strings" + " (id SERIAL PRIMARY KEY," + " match_criteria_id text," + " criteria text DEFAULT NULL," " version_start_incl text DEFAULT NULL," " version_start_excl text DEFAULT NULL," " version_end_incl text DEFAULT NULL," - " version_end_excl text DEFAULT NULL);"); + " version_end_excl text DEFAULT NULL," + " status text);"); + + sql ("CREATE TABLE scap2.cpe_matches" + " (id SERIAL PRIMARY KEY," + " match_criteria_id text," + " cpe_name_id text," + " cpe_name text);"); sql ("CREATE TABLE scap2.cpe_details" " (id SERIAL PRIMARY KEY," @@ -3575,6 +3587,11 @@ manage_db_init (const gchar *name) " epss DOUBLE PRECISION," " percentile DOUBLE PRECISION);"); + sql ("CREATE TABLE scap2.cve_references" + " (id SERIAL PRIMARY KEY," + " cve_id INTEGER," + " url text," + " tags text[]);"); /* Init tables. */ @@ -3624,6 +3641,19 @@ manage_db_add_constraints (const gchar *name) sql ("ALTER TABLE scap2.epss_scores" " ALTER cve SET NOT NULL," " ADD UNIQUE (cve);"); + + sql ("ALTER TABLE scap2.cve_references" + " ALTER cve_id SET NOT NULL," + " ALTER url SET NOT NULL," + " ADD UNIQUE (cve_id, url);"); + + sql ("ALTER TABLE scap2.cpe_match_strings" + " ADD UNIQUE (match_criteria_id);"); + + sql ("ALTER TABLE scap2.cpe_matches" + " ALTER match_criteria_id SET NOT NULL," + " ALTER cpe_name_id SET NOT NULL," + " ADD UNIQUE (match_criteria_id, cpe_name_id);"); } else { diff --git a/src/manage_sql.c b/src/manage_sql.c index 9e33f3277..be266c79a 100644 --- a/src/manage_sql.c +++ b/src/manage_sql.c @@ -20498,21 +20498,92 @@ DEF_ACCESS (host_details_cpe_product_iterator_value, 0); * @brief Initialize an iterator of root_ids of CPE match nodes. * * @param[in] iterator Iterator. - * @param[in] cpe The cpe contained in the match nodes. + * @param[in] criteria The criteria for the match nodes. */ void -init_cpe_match_nodes_iterator (iterator_t* iterator, const char *cpe) +init_cpe_match_nodes_iterator (iterator_t* iterator, const char *criteria) { - gchar *quoted_cpe; - quoted_cpe = sql_quote (cpe); + gchar *quoted_criteria; + quoted_criteria = sql_quote (criteria); + init_iterator (iterator, + "SELECT DISTINCT n.root_id" + " FROM scap.cpe_match_nodes n" + " JOIN scap.cpe_nodes_match_criteria c" + " ON n.id = c.node_id" + " JOIN scap.cpe_match_strings r" + " ON c.match_criteria = r.match_criteria_id" + " WHERE criteria like '%s%%';", + quoted_criteria); + g_free (quoted_criteria); +} + +/** + * @brief Initialize an iterator of CPE match nodes root_ids for a CVE. + * + * @param[in] iterator Iterator. + * @param[in] cve The CVE contained in the match nodes. + */ +void +init_cve_cpe_match_nodes_iterator (iterator_t* iterator, const char *cve) +{ + gchar *quoted_cve; + quoted_cve = sql_quote (cve); init_iterator (iterator, "SELECT DISTINCT root_id" - " FROM scap.cpe_match_nodes, scap.cpe_match_range" - " WHERE cpe like '%s%%' AND scap.cpe_match_nodes.id = node_id;", - quoted_cpe); - g_free (quoted_cpe); + " FROM scap.cpe_match_nodes" + " WHERE cve_id = (SELECT id FROM scap.cves" + " WHERE uuid = '%s');", + quoted_cve); + g_free (quoted_cve); +} + +/** + * @brief Initialize an iterator of references for a CVE. + * + * @param[in] iterator Iterator. + * @param[in] cve The CVE with the references. + */ +void +init_cve_reference_iterator (iterator_t* iterator, const char *cve) +{ + gchar *quoted_cve; + quoted_cve = sql_quote (cve); + init_iterator (iterator, + "SELECT url, array_length(tags, 1), tags" + " FROM scap.cve_references" + " WHERE cve_id = (SELECT id FROM scap.cves" + " WHERE uuid = '%s');", + quoted_cve); + g_free (quoted_cve); } +/** + * @brief Get a URL from a CVE reference iterator. + * + * @param[in] iterator Iterator. + * + * @return The URL. + */ +DEF_ACCESS (cve_reference_iterator_url, 0); + +/** + * @brief Get the length of the tags array from a CVE reference iterator. + * + * @param[in] iterator Iterator. + * + * @return Length of the tags array. + */ +DEF_ACCESS (cve_reference_iterator_tags_count, 1); + +/** + * @brief Get the tags array from a CVE reference iterator. + * + * @param[in] iterator Iterator. + * + * @return The tags array. + */ +DEF_ACCESS (cve_reference_iterator_tags, 2); + /** * @brief Get a root id from an CPE match node iterator. * @@ -20537,7 +20608,8 @@ init_cpe_match_node_childs_iterator (iterator_t* iterator, long long int node) { init_iterator (iterator, "SELECT id FROM scap.cpe_match_nodes" - " WHERE parent_id = %llu;", + " WHERE root_id = %llu" + " AND root_id <> id;", node); } @@ -20555,83 +20627,149 @@ cpe_match_node_childs_iterator_id (iterator_t* iterator) } /** - * @brief Initialize an iterator of match ranges of an CPE match node. + * @brief Initialize an iterator of match strings of an CPE match node. * * @param[in] iterator Iterator. - * @param[in] node The match node with match ranges. + * @param[in] node The match node with match strings. */ void -init_cpe_match_range_iterator (iterator_t* iterator, long long int node) +init_cpe_match_string_iterator (iterator_t* iterator, long long int node) { init_iterator (iterator, - "SELECT vulnerable, cpe, version_start_incl," - " version_start_excl, version_end_incl, version_end_excl" - " FROM scap.cpe_match_range" - " WHERE node_id = %llu;", + "SELECT n.vulnerable, r.criteria, r.match_criteria_id, r.status," + " r.version_start_incl, r.version_start_excl," + " r.version_end_incl, r.version_end_excl" + " FROM scap.cpe_match_strings r" + " JOIN scap.cpe_nodes_match_criteria n" + " ON r.match_criteria_id = n.match_criteria_id" + " WHERE n.node_id = %llu;", node); } /** - * @brief Return if the CPE of the actual match node is vulnerable. + * @brief Return if the match criteria is vulnerable. * * @param[in] iterator Iterator. * - * @return 1 if the match node is vulnerable, 0 otherwise. + * @return 1 if the match criteria is vulnerable, 0 otherwise. */ int -cpe_match_range_iterator_vulnerable (iterator_t* iterator) +cpe_match_string_iterator_vulnerable (iterator_t* iterator) { return iterator_int64 (iterator, 0); } /** - * @brief Return the CPE of the actual match node. + * @brief Return the criteria of the CPE match string. * * @param[in] iterator Iterator. * - * @return The CPE of the actual match node. + * @return The criteria of the match string. */ -DEF_ACCESS (cpe_match_range_iterator_cpe, 1); +DEF_ACCESS (cpe_match_string_iterator_criteria, 1); /** - * @brief Return the start included version of the actual match node. + * @brief Return the match criteria id of the CPE match string. * * @param[in] iterator Iterator. * - * @return The start included version of the actual match node, if any. + * @return The match criteria id, if any. NULL otherwise. + */ +DEF_ACCESS (cpe_match_string_iterator_match_criteria_id, 2); + +/** + * @brief Return the status of the CPE match criteria. + * + * @param[in] iterator Iterator. + * + * @return The status of the CPE match criteria, if any. + * NULL otherwise. + */ +DEF_ACCESS (cpe_match_string_iterator_status, 3); + +/** + * @brief Return the start included version of the match criteria. + * + * @param[in] iterator Iterator. + * + * @return The start included version of the match criteria, if any. * NULL otherwise. */ -DEF_ACCESS (cpe_match_range_iterator_version_start_incl, 2); +DEF_ACCESS (cpe_match_string_iterator_version_start_incl, 4); /** - * @brief Return the start excluded version of the actual match node. + * @brief Return the start excluded version of the match criteria. * * @param[in] iterator Iterator. * - * @return The start excluded version of the actual match node, if any. + * @return The start excluded version of the match criteria, if any. * NULL otherwise. */ -DEF_ACCESS (cpe_match_range_iterator_version_start_excl, 3); +DEF_ACCESS (cpe_match_string_iterator_version_start_excl, 5); /** - * @brief Return the end included version of the actual match node. + * @brief Return the end included version of the match criteria. * * @param[in] iterator Iterator. * - * @return The end included version of the actual match node, if any. + * @return The end included version of the match criteria, if any. * NULL otherwise. */ -DEF_ACCESS (cpe_match_range_iterator_version_end_incl, 4); +DEF_ACCESS (cpe_match_string_iterator_version_end_incl, 6); /** - * @brief Return the end excluded version of the actual match node. + * @brief Return the end excluded version of the match criteria. * * @param[in] iterator Iterator. * - * @return The end excluded version of the actual match node, if any. + * @return The end excluded version of the match criteria, if any. * NULL otherwise. */ -DEF_ACCESS (cpe_match_range_iterator_version_end_excl, 5); +DEF_ACCESS (cpe_match_string_iterator_version_end_excl, 7); + +/** + * @brief Initialize an iterator of CPE matches for a match string + * given a match criteria id. + * + * @param[in] iterator Iterator. + * @param[in] match_criteria_id The match criteria id to get the matches for. + */ +void +init_cpe_match_string_matches_iterator (iterator_t* iterator, + const char *match_criteria_id) +{ + init_iterator (iterator, + "SELECT cpe_name_id, cpe_name" + " FROM scap.cpe_matches" + " WHERE match_criteria_id = '%s'", + match_criteria_id); +} + +/** + * @brief Get the CPE name id from a CPE match string matches iterator. + * + * @param[in] iterator Iterator. + * + * @return The CPE name id. + */ +const char * +cpe_matches_cpe_name_id (iterator_t* iterator) +{ + return iterator_string (iterator, 0); +} + +/** + * @brief Get the CPE name from a CPE match string matches iterator. + * + * @param[in] iterator Iterator. + * + * @return The CPE name id. + */ +const char * +cpe_matches_cpe_name (iterator_t* iterator) +{ + return iterator_string (iterator, 1); +} /** * @brief Initialise a report host prognosis iterator. diff --git a/src/manage_sql_secinfo.c b/src/manage_sql_secinfo.c index ce5714e46..4e6d3d6fc 100644 --- a/src/manage_sql_secinfo.c +++ b/src/manage_sql_secinfo.c @@ -3287,115 +3287,24 @@ insert_cve_from_entry (element_t entry, element_t last_modified, /** * @brief Save the node of a cve match rule tree. * - * @param[in] parent_id The parent_id of the node. If this value is 0, - * the node is the root of the tree. * @param[in] cve_id The id of the CVE to which the tree belongs. * @param[in] operator The operator for the match rules. + * @param[in] negate Whether the operator is negated. * * @return The (database) id of the node. */ static resource_t -save_node (resource_t parent_id, resource_t cve_id, char *operator) +save_node (resource_t cve_id, char *operator, gboolean negate) { return sql_int64_0 ("INSERT INTO scap2.cpe_match_nodes" - " (parent_id, cve_id, operator)" + " (cve_id, operator, negate)" " VALUES" - " (%llu, %llu, '%s')" + " (%llu, '%s', %d)" " RETURNING scap2.cpe_match_nodes.id;", - parent_id, cve_id, - operator); -} - -/** - * @brief Add match rules to a node of a match rule tree* - * - * @param[in] id The id of the node the rules belong to. - * @param[in] match_rules The JSON object that contains the rules. - */ -static void -add_cpe_match_rules (result_t id, cJSON *match_rules) -{ - cJSON *match_rule; - cJSON *ver_se; - cJSON *cpe_js; - - gboolean vulnerable = FALSE; - char * version_start_incl = NULL; - char * version_start_excl = NULL; - char * version_end_incl = NULL; - char * version_end_excl = NULL; - - cJSON_ArrayForEach(match_rule, match_rules) - { - char *sql_cpe = NULL; - vulnerable = FALSE; - version_start_incl = NULL; - version_start_excl = NULL; - version_end_incl = NULL; - version_end_excl = NULL; - - if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(match_rule, "vulnerable"))) - vulnerable = TRUE; - else - vulnerable = FALSE; - - cpe_js = cJSON_GetObjectItemCaseSensitive(match_rule, "cpe23Uri"); - if (cpe_js != NULL && strcmp (cpe_js->valuestring, "(null)")) - { - char *quoted_cpe = sql_quote (cpe_js->valuestring); - sql_cpe = g_strdup_printf ("'%s'", quoted_cpe); - g_free (quoted_cpe); - } - else - sql_cpe = g_strdup ("NULL"); - - ver_se = cJSON_GetObjectItemCaseSensitive(match_rule, "versionStartIncluding"); - if (ver_se != NULL && strcmp (ver_se->valuestring, "(null)")) - version_start_incl = g_strdup_printf ("'%s'", ver_se->valuestring); - else - version_start_incl = g_strdup ("NULL"); - - ver_se = cJSON_GetObjectItemCaseSensitive(match_rule, "versionStartExcluding"); - if (ver_se != NULL && strcmp (ver_se->valuestring, "(null)")) - version_start_excl = g_strdup_printf ("'%s'", ver_se->valuestring); - else - version_start_excl = g_strdup ("NULL"); - - ver_se = cJSON_GetObjectItemCaseSensitive(match_rule, "versionEndIncluding"); - if (ver_se != NULL && strcmp (ver_se->valuestring, "(null)")) - version_end_incl = g_strdup_printf ("'%s'", ver_se->valuestring); - else - version_end_incl = g_strdup ("NULL"); - - ver_se = cJSON_GetObjectItemCaseSensitive(match_rule, "versionEndExcluding"); - if (ver_se != NULL && strcmp (ver_se->valuestring, "(null)")) - version_end_excl = g_strdup_printf ("'%s'", ver_se->valuestring); - else - version_end_excl = g_strdup ("NULL"); - - sql - ("INSERT INTO scap2.cpe_match_range" - " (node_id, vulnerable, cpe," - " version_start_incl, version_start_excl," - " version_end_incl, version_end_excl)" - " VALUES" - " (%llu, %d, %s, %s, %s, %s, %s)", - id, - vulnerable ? 1 : 0, - sql_cpe ? sql_cpe : "", - version_start_incl ? version_start_incl : "", - version_start_excl ? version_start_excl : "", - version_end_incl ? version_end_incl : "", - version_end_excl ? version_end_excl : ""); - - g_free (sql_cpe); - g_free (version_start_incl); - g_free (version_start_excl); - g_free (version_end_incl); - g_free (version_end_excl); - } + operator, + negate ? 1 : 0); } /** @@ -3414,52 +3323,201 @@ set_root_id (long int id, long int root_id) } /** - * @brief Load and add recursively all nodes of a match rule tree for a - * specific CVE. Build a match rule tree. + * @brief Handle the references of a CVE. + * + * @param[in] cve_db_id The id of the CVE the references belong to. + * @param[in] cve_id The id of the CVE. + * @param[in] reference_json JSON array containing the references. * - * @param[in] parent_id The parent id of the nodes to insert - * (0 for the root node). - * @param[in] cveid The id of the CVE the tree belongs to. - * @param[in] root_id The root id of the nodes to insert. - * @param[in] nodes The JSON object that contains the rules for a - * specific tree level. + * @return 0 on success, -1 on error. */ -static void -load_nodes (resource_t parent_id, resource_t cveid, resource_t root_id, cJSON *nodes) +static int +handle_cve_references (resource_t cve_db_id, char * cve_id, + cJSON* reference_json) { - cJSON *node; - resource_t id; - cJSON *operator; - cJSON *cpe_match_rules; - cJSON *child_nodes; - - node = NULL; - id = 0; - operator = NULL; - cpe_match_rules = NULL; - child_nodes = NULL; - - if (nodes == NULL) - return; + cJSON *reference_data; + cJSON *tags; + + cJSON_ArrayForEach (reference_data, reference_json) + { + GString *tags_string; + char *url_value = json_object_item_string (reference_data, "url"); + if (url_value == NULL) + { + g_warning ("%s: url missing in reference for %s.", + __func__, cve_id); + return -1; + } + + tags = cJSON_GetObjectItemCaseSensitive (reference_data, "tags"); + if (cJSON_IsArray (tags)) + { + array_t *tags_array = make_array (); + tags_string = g_string_new ("{"); + + for (int i = 0; i < cJSON_GetArraySize (tags); i++) + { + cJSON *tag = cJSON_GetArrayItem (tags, i); + if (!cJSON_IsString (tag)) + { + g_warning ("%s: tag for %s is NULL or not a string.", + __func__, cve_id); + return -1; + } + if ((strcmp (tag->valuestring, "(null)") == 0) + || strlen (tag->valuestring) == 0) + { + g_warning ("%s: tag for %s is empty string or NULL.", + __func__, cve_id); + return -1; + } + array_add (tags_array, tag->valuestring); + } + + for (int i = 0; i < tags_array->len; i++) + { + gchar *tag = g_ptr_array_index (tags_array, i); + gchar *quoted_tag = sql_quote (tag); + + g_string_append (tags_string, quoted_tag); + + if (i < tags_array->len - 1) + g_string_append (tags_string, ","); + + g_free (quoted_tag); + } + g_string_append (tags_string, "}"); + g_ptr_array_free (tags_array, TRUE); + } + + gchar *quoted_url = sql_quote (url_value); + + sql ("INSERT INTO scap2.cve_references" + " (cve_id, url, tags)" + " VALUES" + " (%llu, '%s', '%s')" + " ON CONFLICT (cve_id, url) DO UPDATE" + " SET tags = EXCLUDED.tags;", + cve_db_id, + quoted_url, + tags_string->str ?: "{}"); + + g_free (quoted_url); + if (tags_string) + g_string_free (tags_string, TRUE); + } + return 0; +} + +/** + * @brief Handle the configurations of a CVE. + * + * @param[in] cve_db_id The id of the CVE the configurations belong to. + * @param[in] cve_id The id of the CVE. + * @param[in] configurations_json JSON array containing the configurations. + * + * @return 0 on success, -1 on error. + */ +static int +handle_cve_configurations (resource_t cve_db_id, char * cve_id, + cJSON* configurations_json) +{ + cJSON *configuration_item; - cJSON_ArrayForEach(node, nodes) + cJSON_ArrayForEach (configuration_item, configurations_json) { - operator = cJSON_GetObjectItemCaseSensitive(node, "operator"); - if (operator && operator->valuestring) - id = save_node (parent_id, cveid, operator->valuestring); - else - return; + cJSON *nodes_array, *node_item; + resource_t id, root_id; + char *config_operator; + int negate; + + nodes_array = cJSON_GetObjectItemCaseSensitive (configuration_item, + "nodes"); + if (!cJSON_IsArray (nodes_array)) + { + g_warning ("%s: 'nodes' field missing or not an array for %s.", + __func__, cve_id); + return -1; + } - if (parent_id == 0) - root_id = id; - set_root_id (id, root_id); + root_id = -1; + config_operator = json_object_item_string (configuration_item, + "operator"); + if (config_operator) + { + negate = json_object_item_boolean (configuration_item, "negate", 0); + id = save_node (cve_db_id, config_operator, negate); + set_root_id (id, id); + root_id = id; + } + + char *node_operator; + cJSON_ArrayForEach(node_item, nodes_array) + { + node_operator = json_object_item_string (node_item, "operator"); + if (node_operator == NULL) + { + g_warning ("%s: operator missing for %s.", __func__, cve_id); + return -1; + } + + negate = json_object_item_boolean (node_item, "negate", 0); - cpe_match_rules = cJSON_GetObjectItemCaseSensitive(node, "cpe_match"); - if (cpe_match_rules) - add_cpe_match_rules (id, cpe_match_rules); - child_nodes = cJSON_GetObjectItemCaseSensitive(node, "children"); - load_nodes (id, cveid, root_id, child_nodes); + cJSON *cpe_matches_array; + cpe_matches_array = cJSON_GetObjectItemCaseSensitive (node_item, + "cpeMatch"); + if (!cJSON_IsArray (cpe_matches_array)) + { + g_warning ("%s: cpeMatch missing or not an array for %s.", + __func__, cve_id); + return -1; + } + + id = save_node (cve_db_id, node_operator, negate); + + if (root_id < 0) + root_id = id; + + set_root_id (id, root_id); + + cJSON *cpe_match_item; + cJSON_ArrayForEach (cpe_match_item, cpe_matches_array) + { + char *match_criteria_id; + int vulnerable; + gchar *quoted_match_criteria_id; + + vulnerable = json_object_item_boolean (cpe_match_item, + "vulnerable", -1); + if (vulnerable == -1) + { + g_warning ("%s: vulnerable missing in cpeMatch for %s.", + __func__, cve_id); + return -1; + } + match_criteria_id = json_object_item_string (cpe_match_item, + "matchCriteriaId"); + if (match_criteria_id == NULL) + { + g_warning ("%s: matchCriteriaId missing in cpeMatch for %s.", + __func__, cve_id); + return -1; + } + quoted_match_criteria_id = sql_quote (match_criteria_id); + + sql ("INSERT INTO scap2.cpe_nodes_match_criteria" + " (node_id, vulnerable, match_criteria_id)" + " VALUES" + " (%llu, %d, '%s')", + id, + vulnerable ? 1 : 0, + quoted_match_criteria_id); + + g_free (quoted_match_criteria_id); + } + } } + return 0; } /** @@ -3474,23 +3532,26 @@ static int handle_json_cve_item (cJSON *item) { cJSON *cve_json; - cJSON *cve_data_meta_json; - - char *cve_id; + char *cve_id, *vector; + double score_dbl; resource_t cve_db_id; - cve_json = cJSON_GetObjectItemCaseSensitive(item, "cve"); - cve_data_meta_json = cJSON_GetObjectItemCaseSensitive(cve_json, "CVE_data_meta"); - cve_id = json_object_item_string (cve_data_meta_json, "ID"); + cve_json = cJSON_GetObjectItemCaseSensitive (item, "cve"); + if (!cJSON_IsObject (cve_json)) + { + g_warning ("%s: 'cve' field is missing or not an object.", __func__); + return -1; + } + cve_id = json_object_item_string (cve_json, "id"); if (cve_id == NULL) { - g_warning("%s: ID missing.", __func__); + g_warning ("%s: cve id missing.", __func__); return -1; } char *published; time_t published_time; - published = json_object_item_string (item, "publishedDate"); + published = json_object_item_string (cve_json, "published"); if (published == NULL) { g_warning("%s: publishedDate missing for %s.", __func__, cve_id); @@ -3500,63 +3561,85 @@ handle_json_cve_item (cJSON *item) char *modified; time_t modified_time; - modified = json_object_item_string (item, "lastModifiedDate"); + modified = json_object_item_string (cve_json, "lastModified"); if (modified == NULL) { - g_warning("%s: lastModifiedDate missing for %s.", __func__, cve_id); + g_warning ("%s: lastModifiedDate missing for %s.", __func__, cve_id); return -1; } modified_time = parse_iso_time (modified); - cJSON *impact_json; - cJSON *base_metric_json; - char * cvss_key; - cJSON *cvss_json; - char * vector; - double score_dbl; + cJSON *metrics_json; + cJSON *cvss_metric_array; - impact_json = cJSON_GetObjectItemCaseSensitive(item, "impact"); - if (impact_json == NULL) + metrics_json = cJSON_GetObjectItemCaseSensitive (cve_json, "metrics"); + if (!cJSON_IsObject (metrics_json)) { - g_warning("%s: Impact missing for %s.", __func__, cve_id); + g_warning ("%s: Metrics missing or not an object for %s.", + __func__, cve_id); return -1; } - base_metric_json = cJSON_GetObjectItemCaseSensitive(impact_json, "baseMetricV4"); - if (base_metric_json != NULL) - cvss_key = "cvssV4"; - else - { - base_metric_json = cJSON_GetObjectItemCaseSensitive(impact_json, "baseMetricV3"); - if (base_metric_json != NULL) - cvss_key = "cvssV3"; - else + + gboolean cvss_metric_found = FALSE; + + const char *cvss_metric_keys[] = { + "cvssMetricV40", + "cvssMetricV31", + "cvssMetricV30", + "cvssMetricV2"}; + + for (int i = 0; i < 4; i++) + { + cvss_metric_array + = cJSON_GetObjectItemCaseSensitive (metrics_json, cvss_metric_keys[i]); + if (cJSON_IsArray (cvss_metric_array) + && cJSON_GetArraySize (cvss_metric_array) > 0) { - base_metric_json = cJSON_GetObjectItemCaseSensitive(impact_json, "baseMetricV2"); - if (base_metric_json != NULL) - cvss_key = "cvssV2"; - else - cvss_key = NULL; + cvss_metric_found = TRUE; + break; } } - if (cvss_key != NULL) + + if (cvss_metric_found) { - cvss_json = cJSON_GetObjectItemCaseSensitive(base_metric_json, cvss_key); - if (cvss_json == NULL) - { - g_warning("%s: %s missing for %s.", __func__, cvss_key, cve_id); - return -1; - } - score_dbl = json_object_item_double (cvss_json, "baseScore", SEVERITY_MISSING); - if (score_dbl == SEVERITY_MISSING) - { - g_warning("%s: baseScore missing in %s for %s.", __func__, cvss_key, cve_id); - return -1; - } - vector = json_object_item_string (cvss_json, "vectorString"); - if (vector == NULL) + cJSON *cvss_json; + cJSON *cvss_metric_item; + char *source_type; + + cJSON_ArrayForEach (cvss_metric_item, cvss_metric_array) { - g_warning("%s: vectorString missing for %s.", __func__, cve_id); - return -1; + source_type = json_object_item_string (cvss_metric_item, "type"); + if (source_type == NULL) + { + g_warning ("%s: type missing in CVSS metric for %s.", + __func__, cve_id); + return -1; + } + else if (strcmp (source_type, "Primary")) + continue; + + cvss_json = cJSON_GetObjectItemCaseSensitive (cvss_metric_item, + "cvssData"); + if (!cJSON_IsObject (cvss_json)) + { + g_warning ("%s: cvssData missing or not an object for %s.", + __func__, cve_id); + return -1; + } + score_dbl = json_object_item_double (cvss_json, + "baseScore", + SEVERITY_MISSING); + if (score_dbl == SEVERITY_MISSING) + { + g_warning ("%s: baseScore missing for %s.", __func__, cve_id); + return -1; + } + vector = json_object_item_string (cvss_json, "vectorString"); + if (vector == NULL) + { + g_warning ("%s: vectorString missing for %s.", __func__, cve_id); + return -1; + } } } else @@ -3565,29 +3648,24 @@ handle_json_cve_item (cJSON *item) vector = NULL; } - cJSON *description_json; - cJSON *description_data_json; + cJSON *descriptions_json; cJSON *description_item_json; char *description_value; - description_json = cJSON_GetObjectItemCaseSensitive(cve_json, "description"); - if (description_json == NULL) - { - g_warning("%s: description missing for %s.", __func__, cve_id); - return -1; - } - - description_data_json = cJSON_GetObjectItemCaseSensitive(description_json, "description_data"); - if (description_data_json == NULL) + descriptions_json = cJSON_GetObjectItemCaseSensitive (cve_json, + "descriptions"); + if (!cJSON_IsArray (descriptions_json)) { - g_warning("%s: description_data missing for %s.", __func__, cve_id); + g_warning ("%s: descriptions for %s is missing or not an array.", + __func__, cve_id); return -1; } - cJSON_ArrayForEach (description_item_json, description_data_json) + cJSON_ArrayForEach (description_item_json, descriptions_json) { char *lang = json_object_item_string (description_item_json, "lang"); - if (lang != NULL && strcmp(lang, "en") == 0) - description_value = json_object_item_string (description_item_json, "value"); + if (lang != NULL && strcmp (lang, "en") == 0) + description_value = json_object_item_string (description_item_json, + "value"); } char *quoted_description = sql_quote (description_value); @@ -3619,24 +3697,30 @@ handle_json_cve_item (cJSON *item) g_free (quoted_description); - cJSON *configurations_json; - cJSON *nodes_json; - - configurations_json = - cJSON_GetObjectItemCaseSensitive(item, "configurations"); - if (configurations_json == NULL) + cJSON *configurations_array; + configurations_array = cJSON_GetObjectItemCaseSensitive (cve_json, + "configurations"); + if (!cJSON_IsArray (configurations_array)) { - g_warning("%s: configurations missing for %s.", __func__, cve_id); + g_warning ("%s: configurations for %s is missing or not an array.", + __func__, cve_id); return -1; } - nodes_json = - cJSON_GetObjectItemCaseSensitive(configurations_json, "nodes"); - if (nodes_json == NULL) + + if (handle_cve_configurations (cve_db_id, cve_id, configurations_array)) + return -1; + + cJSON *references_array; + references_array = cJSON_GetObjectItemCaseSensitive (cve_json, "references"); + if (!cJSON_IsArray (references_array)) { - g_warning("%s: nodes missing for %s.", __func__, cve_id); + g_warning ("%s: references for %s is missing or not an array.", + __func__, cve_id); return -1; } - load_nodes (0, cve_db_id, 0, nodes_json); + + if (handle_cve_references (cve_db_id, cve_id, references_array)) + return -1; return 0; } @@ -3699,7 +3783,7 @@ update_cve_json (const gchar *cve_path, GHashTable *hashed_cpes) gvm_json_pull_parser_next (&parser, &event); gvm_json_path_elem_t *path_tail = g_queue_peek_tail (event.path); if (event.type == GVM_JSON_PULL_EVENT_ARRAY_START && path_tail && - path_tail->key && strcmp (path_tail->key, "CVE_Items") == 0) + path_tail->key && strcmp (path_tail->key, "vulnerabilities") == 0) { cve_items_found = TRUE; } @@ -3727,7 +3811,7 @@ update_cve_json (const gchar *cve_path, GHashTable *hashed_cpes) entry = gvm_json_pull_expand_container (&parser, &error_message); if (error_message) { - g_warning ("%s: Error expanding CVE item: %s", __func__, error_message); + g_warning ("%s: Error expanding vulnerability item: %s", __func__, error_message); gvm_json_pull_event_cleanup (&event); gvm_json_pull_parser_cleanup (&parser); cJSON_Delete (entry); @@ -3912,8 +3996,8 @@ update_scap_cves () gboolean read_json = FALSE; while ((cve_path = g_dir_read_name (dir))) { - if (fnmatch ("nvdcve-1.1-*.json.gz", cve_path, 0) == 0 || - fnmatch ("nvdcve-1.1-*.json", cve_path, 0) == 0) + if (fnmatch ("nvdcve-2.0-*.json.gz", cve_path, 0) == 0 || + fnmatch ("nvdcve-2.0-*.json", cve_path, 0) == 0) { read_json = TRUE; break; @@ -3924,8 +4008,8 @@ update_scap_cves () count = 0; while ((cve_path = g_dir_read_name (dir))) { - if ((fnmatch ("nvdcve-1.1-*.json.gz", cve_path, 0) == 0 || - fnmatch ("nvdcve-1.1-*.json", cve_path, 0) == 0) + if ((fnmatch ("nvdcve-2.0-*.json.gz", cve_path, 0) == 0 || + fnmatch ("nvdcve-2.0-*.json", cve_path, 0) == 0) && read_json) { if (update_cve_json (cve_path, hashed_cpes)) @@ -3959,6 +4043,348 @@ update_scap_cves () return 0; } +/** + * @brief Insert a SCAP CPE match string from JSON. + * + * @param[in] inserts Pointer to SQL buffer for match string entries. + * @param[in] matches_inserts Pointer to SQL buffer for match string matches. + * @param[in] match_string_item JSON object from the matchStrings list. + * + * @return 0 success, -1 error. + */ +static int +handle_json_cpe_match_string (inserts_t *inserts, inserts_t *matches_inserts, + cJSON *match_string_item) +{ + cJSON *match_string, *matches_array; + char *criteria, *match_criteria_id, *status, *ver_se; + gchar *quoted_version_start_incl, *quoted_version_start_excl; + gchar *quoted_version_end_incl, *quoted_version_end_excl; + gchar *quoted_criteria, *quoted_match_criteria_id; + int first; + + assert (inserts); + assert (matches_inserts); + + match_string = cJSON_GetObjectItemCaseSensitive (match_string_item, + "matchString"); + if (!cJSON_IsObject (match_string)) + { + g_warning ("%s: 'matchString' field is missing or not an object", + __func__); + return -1; + } + + criteria = json_object_item_string (match_string, "criteria"); + if (criteria == NULL) + { + g_warning ("%s: 'criteria' field missing or not a string", __func__); + return -1; + } + + match_criteria_id = json_object_item_string (match_string, + "matchCriteriaId"); + if (match_criteria_id == NULL) + { + g_warning ("%s: 'matchCriteriaId' field missing or not a string", + __func__); + return -1; + } + + status = json_object_item_string (match_string, "status"); + if (status == NULL) + { + g_warning ("%s: 'status' field missing or not a string", __func__); + return -1; + } + + ver_se = json_object_item_string (match_string, "versionStartIncluding"); + if (ver_se == NULL) + quoted_version_start_incl = g_strdup ("NULL"); + else + quoted_version_start_incl = g_strdup_printf ("'%s'", ver_se); + + ver_se = json_object_item_string (match_string, "versionStartExcluding"); + if (ver_se == NULL) + quoted_version_start_excl = g_strdup ("NULL"); + else + quoted_version_start_excl = g_strdup_printf ("'%s'", ver_se); + + ver_se = json_object_item_string (match_string, "versionEndIncluding"); + if (ver_se == NULL) + quoted_version_end_incl = g_strdup ("NULL"); + else + quoted_version_end_incl = g_strdup_printf ("'%s'", ver_se); + + ver_se = json_object_item_string (match_string, "versionEndExcluding"); + if (ver_se == NULL) + quoted_version_end_excl = g_strdup ("NULL"); + else + quoted_version_end_excl = g_strdup_printf ("'%s'", ver_se); + + quoted_match_criteria_id = sql_quote (match_criteria_id); + quoted_criteria = fs_to_uri_convert_and_quote_cpe_name (criteria); + + first = inserts_check_size (inserts); + + g_string_append_printf (inserts->statement, + "%s ('%s', '%s', %s, %s, %s, %s, '%s')", + first ? "" : ",", + quoted_match_criteria_id, + quoted_criteria, + quoted_version_start_incl, + quoted_version_start_excl, + quoted_version_end_incl, + quoted_version_end_excl, + status); + + inserts->current_chunk_size++; + + g_free (quoted_criteria); + g_free (quoted_version_start_incl); + g_free (quoted_version_start_excl); + g_free (quoted_version_end_incl); + g_free (quoted_version_end_excl); + + matches_array = cJSON_GetObjectItemCaseSensitive (match_string, "matches"); + + if (cJSON_IsArray (matches_array) && cJSON_GetArraySize (matches_array) > 0) + { + cJSON *match_item; + cJSON_ArrayForEach (match_item, matches_array) + { + char *cpe_name_id, *cpe_name; + gchar *quoted_cpe_name_id, *quoted_cpe_name; + + cpe_name_id = json_object_item_string (match_item, "cpeNameId"); + if (cpe_name_id == NULL) + { + g_warning ("%s: 'cpeNameId' field missing or not a string", + __func__); + g_free (quoted_match_criteria_id); + return -1; + } + + cpe_name = json_object_item_string (match_item, "cpeName"); + if (cpe_name == NULL) + { + g_warning ("%s: 'cpe_name' field missing or not a string", + __func__); + g_free (quoted_match_criteria_id); + return -1; + } + + quoted_cpe_name_id = sql_quote (cpe_name_id); + quoted_cpe_name = fs_to_uri_convert_and_quote_cpe_name (cpe_name); + + first = inserts_check_size (matches_inserts); + + g_string_append_printf (matches_inserts->statement, + "%s ('%s', '%s', '%s')", + first ? "" : ",", + quoted_match_criteria_id, + quoted_cpe_name_id, + quoted_cpe_name); + + matches_inserts->current_chunk_size++; + + g_free (quoted_cpe_name_id); + g_free (quoted_cpe_name); + } + } + + g_free (quoted_match_criteria_id); + return 0; +} + +/** + * @brief Updates the CPE match strings in the SCAP database. + * + * @return 0 success, -1 error. + */ +static int +update_scap_cpe_match_strings () +{ + gchar *current_json_path; + FILE *cpe_match_strings_file; + gvm_json_pull_event_t event; + gvm_json_pull_parser_t parser; + inserts_t inserts, matches_inserts; + + current_json_path = g_build_filename (GVM_SCAP_DATA_DIR, + "cpe_match_strings.json.gz", + NULL); + int fd = open(current_json_path, O_RDONLY); + + if (fd < 0 && errno == ENOENT) + { + g_free (current_json_path); + current_json_path = g_build_filename (GVM_SCAP_DATA_DIR, + "cpe_match_strings.json", + NULL); + fd = open(current_json_path, O_RDONLY); + } + + if (fd < 0) + { + int ret; + if (errno == ENOENT) + { + g_info ("%s: CPE match strings file '%s' not found", + __func__, current_json_path); + ret = 0; + } + else + { + g_warning ("%s: Failed to open CPE match strings file: %s", + __func__, strerror (errno)); + ret = -1; + } + g_free (current_json_path); + return ret; + } + + cpe_match_strings_file = gvm_gzip_open_file_reader_fd (fd); + + if (cpe_match_strings_file == NULL) + { + g_warning ("%s: Failed to convert file descriptor to FILE*: %s", + __func__, + strerror (errno)); + g_free (current_json_path); + close (fd); + return -1; + } + + g_info ("Updating CPE match strings from %s", current_json_path); + g_free (current_json_path); + + gvm_json_pull_event_init (&event); + gvm_json_pull_parser_init (&parser, cpe_match_strings_file); + + gvm_json_pull_parser_next (&parser, &event); + + if (event.type == GVM_JSON_PULL_EVENT_OBJECT_START) + { + gboolean cpe_match_strings_found = FALSE; + while (!cpe_match_strings_found) + { + gvm_json_pull_parser_next (&parser, &event); + gvm_json_path_elem_t *path_tail = g_queue_peek_tail (event.path); + if (event.type == GVM_JSON_PULL_EVENT_ARRAY_START + && path_tail && strcmp (path_tail->key, "matchStrings") == 0) + { + cpe_match_strings_found = TRUE; + } + else if (event.type == GVM_JSON_PULL_EVENT_ERROR) + { + g_warning ("%s: Parser error: %s", __func__, event.error_message); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + else if (event.type == GVM_JSON_PULL_EVENT_OBJECT_END + && g_queue_is_empty (event.path)) + { + g_warning ("%s: Unexpected json object end. Missing matchStrings field", + __func__); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + } + + sql_begin_immediate (); + inserts_init (&inserts, + CPE_MAX_CHUNK_SIZE, + setting_secinfo_sql_buffer_threshold_bytes (), + "INSERT INTO scap2.cpe_match_strings" + " (match_criteria_id, criteria, version_start_incl," + " version_start_excl, version_end_incl, version_end_excl," + " status)" + " VALUES ", + " ON CONFLICT (match_criteria_id) DO UPDATE" + " SET criteria = EXCLUDED.criteria," + " version_start_incl = EXCLUDED.version_start_incl," + " version_start_excl = EXCLUDED.version_start_excl," + " version_end_incl = EXCLUDED.version_end_incl," + " version_end_excl = EXCLUDED.version_end_excl," + " status = EXCLUDED.status"); + + inserts_init (&matches_inserts, 10, + setting_secinfo_sql_buffer_threshold_bytes (), + "INSERT INTO scap2.cpe_matches" + " (match_criteria_id, cpe_name_id, cpe_name)" + " VALUES ", + ""); + + gvm_json_pull_parser_next (&parser, &event); + while (event.type == GVM_JSON_PULL_EVENT_OBJECT_START) + { + gchar *error_message; + cJSON *cpe_match_string_item + = gvm_json_pull_expand_container (&parser, &error_message); + if (error_message) + { + g_warning ("%s: Error expanding match string item: %s", + __func__, error_message); + cJSON_Delete (cpe_match_string_item); + inserts_free (&inserts); + inserts_free (&matches_inserts); + sql_commit (); + g_warning ("Update of CPE match strings failed"); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + if (handle_json_cpe_match_string (&inserts, + &matches_inserts, + cpe_match_string_item)) + { + cJSON_Delete (cpe_match_string_item); + inserts_free (&inserts); + inserts_free (&matches_inserts); + sql_commit (); + g_warning ("Update of CPE match strings failed"); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + cJSON_Delete (cpe_match_string_item); + gvm_json_pull_parser_next (&parser, &event); + } + } + else if (event.type == GVM_JSON_PULL_EVENT_ERROR) + { + g_warning ("%s: Parser error: %s", __func__, event.error_message); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + else + { + g_warning ("%s: CVE match strings file is not a JSON object.", + __func__); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return -1; + } + + inserts_run (&inserts, TRUE); + inserts_run (&matches_inserts, TRUE); + sql_commit (); + gvm_json_pull_event_cleanup (&event); + gvm_json_pull_parser_cleanup (&parser); + fclose (cpe_match_strings_file); + return 0; +} + /** * @brief Adds a EPSS score entry to an SQL inserts buffer. * @@ -5162,6 +5588,15 @@ update_scap (gboolean reset_scap_db) return -1; } + g_debug ("%s: update cpe match strings", __func__); + setproctitle ("Syncing SCAP: Updating CPE Match Strings"); + + if (update_scap_cpe_match_strings () == -1) + { + abort_scap_update (); + return -1; + } + g_debug ("%s: update cves", __func__); setproctitle ("Syncing SCAP: Updating CVEs"); diff --git a/src/schema_formats/XML/GMP.xml.in b/src/schema_formats/XML/GMP.xml.in index 5f2ca6573..986a61a21 100644 --- a/src/schema_formats/XML/GMP.xml.in +++ b/src/schema_formats/XML/GMP.xml.in @@ -13113,6 +13113,8 @@ END:VCALENDAR epss nvts cert + configuration_nodes + references raw_data A CVE info element @@ -13233,6 +13235,169 @@ END:VCALENDAR + + configuration_nodes + List of configuration nodes. Only when details were requested + + node + + + node + A configuration node for the CVE + + operator + negate + match_string + node + + + operator + The operator for the match criteria + + text + + + + negate + A true or false value, whether the operator is negated + + text + + + + match_string + The match string for the node + + criteria + vulnerable + version_start_including + version_start_excluding + version_end_including + version_end_excluding + matched_cpes + + + criteria + A CPE match criteria + + text + + + + vulnerable + A true or false value, whether matching CPEs are considered vulnerable + + text + + + + version_start_including + From version (including) + + text + + + + version_start_excluding + From version (excluding) + + text + + + + version_end_including + Up to version (including) + + text + + + + version_end_excluding + Up to version (exluding) + + text + + + + matched_cpes + List of matching CPEs + + name + deprecated + deprecated_by + + + name + Name of the matching CPE + + text + + + + deprecated + A true or false value, whether the CPE is deprecated + + text + + + + deprecated_by + CPE ID the current CPE is deprecated by. Only when the CPE is deprecated + + + cpe_id + uuid + CPE ID the current CPE is deprecated by + + + + + + + node + Nodes can contain other nested nodes + + node + + + + + + references + References of this CVE. Only when details were requested + + reference + + + reference + Reference of the CVE + + url + tags + + + url + The URL of the reference + + text + + + + tags + List of tags for the reference + + tag + + + tag + Tag for a reference + + text + + + + + raw_data Source representation of the information. Only when details were requested