/* Copyright (C) 2009-2021 Greenbone Networks GmbH * * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /** * @file manage_sql.c * @brief The Greenbone Vulnerability Manager management library. */ /** * @brief Enable extra GNU functions. */ #define _GNU_SOURCE #include "manage_sql.h" #include "manage_port_lists.h" #include "manage_report_formats.h" #include "manage_sql_secinfo.h" #include "manage_sql_nvts.h" #include "manage_tickets.h" #include "manage_sql_configs.h" #include "manage_sql_port_lists.h" #include "manage_sql_report_formats.h" #include "manage_sql_tickets.h" #include "manage_sql_tls_certificates.h" #include "manage_acl.h" #include "manage_authentication.h" #include "lsc_user.h" #include "sql.h" #include "utils.h" /* TODO This is for buffer_get_filter_xml, for print_report_xml_start. We * should not be generating XML in here, that should be done in gmp_*.c. */ #include "gmp_get.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef G_LOG_DOMAIN /** * @brief GLib log domain. */ #define G_LOG_DOMAIN "md manage" /** * @brief Number of retries for * LOCK TABLE .. IN ACCESS EXLUSIVE MODE NOWAIT * statements. */ #define LOCK_RETRIES 16 /** * @brief Time of delay between two lock retries. */ #define LOCK_RETRY_DELAY 2 #ifdef DEBUG_FUNCTION_NAMES #include void __cyg_profile_func_enter (void *, void *) __attribute__((no_instrument_function)); void __cyg_profile_func_enter (void *func, void *caller) { Dl_info info; if (dladdr (func, &info)) g_debug ("TTT: enter %p %s", (int*) func, info.dli_sname ? info.dli_sname : "?"); else g_debug ("TTT: enter %p", (int*) func); } void __cyg_profile_func_exit (void *, void *) __attribute__((no_instrument_function)); void __cyg_profile_func_exit (void *func, void *caller) { Dl_info info; if (dladdr (func, &info)) g_debug ("TTT: exit %p %s", (int*) func, info.dli_sname ? info.dli_sname : "?"); else g_debug ("TTT: exit %p", (int*) func); } #endif /* Headers from backend specific manage_xxx.c file. */ int manage_create_sql_functions (); void create_tables (); void check_db_sequences (); int check_db_extensions (); static int check_db_encryption_key (); void manage_attach_databases (); /* Headers for symbols defined in manage.c which are private to libmanage. */ /** * @brief Flag to force authentication to succeed. * * 1 if set via scheduler, 2 if set via event, else 0. */ extern int authenticate_allow_all; const char *threat_message_type (const char *); int delete_reports (task_t); int stop_task_internal (task_t); int validate_username (const gchar *); void set_task_interrupted (task_t, const gchar *); /* Static headers. */ static int report_counts_cache_exists (report_t, int, int); static void report_severity_data (report_t, const char *, const get_data_t *, severity_data_t*, severity_data_t*); static int cache_report_counts (report_t, int, int, severity_data_t*); static char* task_owner_uuid (task_t); gchar* clean_hosts (const char *, int*); static gboolean find_user_by_name (const char *, user_t *user); static gboolean find_role_with_permission (const char *, role_t *, const char *); static int user_ensure_in_db (const gchar *, const gchar *); static int set_password (const gchar *, const gchar *, const gchar *, gchar **); static void permissions_set_subjects (const char *, resource_t, resource_t, int); static resource_t permission_resource (permission_t); static resource_t permission_subject (permission_t); static char * permission_subject_type (permission_t); static int role_is_predefined (role_t); static int role_is_predefined_id (const char *); static int task_second_last_report (task_t, report_t *); static gchar * new_secinfo_message (event_t, const void*, alert_t); static gchar * new_secinfo_list (event_t, const void*, alert_t, int*); static void check_for_new_scap (); static void check_for_new_cert (); static void check_for_updated_scap (); static void check_for_updated_cert (); static int report_counts_id_full (report_t, int *, int *, int *, int *, int *, double *, const get_data_t*, const char* , int *, int *, int *, int *, int *, double *); static gboolean find_group_with_permission (const char *, group_t *, const char *); static gchar* vulns_extra_where (); static int task_last_report_any_status (task_t, report_t *); static int task_report_previous (task_t task, report_t, report_t *); static gboolean find_trash_task (const char*, task_t*); static gboolean find_trash_report_with_permission (const char *, report_t *, const char *); static int cleanup_schedule_times (); static char * permission_name (permission_t); static void cache_permissions_for_resource (const char *, resource_t, GArray*); static void cache_all_permissions_for_users (GArray*); static void report_cache_counts (report_t, int, int, const char*); static int report_host_dead (report_host_t); static int report_host_result_count (report_host_t); static int set_credential_data (credential_t, const char*, const char*); static void set_credential_name (credential_t, const char *); static void set_credential_comment (credential_t, const char *); static void set_credential_login (credential_t, const char *); static void set_credential_certificate (credential_t, const char *); static void set_credential_auth_algorithm (credential_t, const char *); static void set_credential_private_key (credential_t, const char *, const char *); static void set_credential_password (credential_t, const char *); static void set_credential_snmp_secret (credential_t, const char *, const char *, const char *); static int setting_value_int (const char *, int *); static int setting_auto_cache_rebuild_int (); static double setting_default_severity_dbl (); static int setting_dynamic_severity_int (); static char * setting_timezone (); static double task_severity_double (task_t, int, int, int); static char* target_comment (target_t); static column_t * type_select_columns (const char *type); static column_t * type_where_columns (const char *type); static char* trash_filter_uuid (filter_t); static char* trash_filter_name (filter_t); static char* trash_target_comment (target_t); static int user_resources_in_use (user_t, const char *, int(*)(resource_t), const char *, int(*)(resource_t)); static const char** type_filter_columns (const char *); static int type_build_select (const char *, const char *, const get_data_t *, gboolean, gboolean, const char *, const char *, const char *, gchar **); /* Variables. */ /** * @brief Function to fork a connection that will accept GMP requests. */ static manage_connection_forker_t manage_fork_connection; /** * @brief Max number of hosts per target. */ static int max_hosts = MANAGE_MAX_HOSTS; /** * @brief Default max number of bytes of reports included in email alerts. */ #define MAX_CONTENT_LENGTH 20000 /** * @brief Maximum number of bytes of reports included in email alerts. * * A value less or equal to 0 allows any size. */ static int max_content_length = MAX_CONTENT_LENGTH; /** * @brief Default max number of bytes of reports attached to email alerts. */ #define MAX_ATTACH_LENGTH 1048576 /** * @brief Maximum number of bytes of reports attached to email alerts. * * A value less or equal to 0 allows any size. */ static int max_attach_length = MAX_ATTACH_LENGTH; /** * @brief Default max number of bytes of user-defined message in email alerts. */ #define MAX_EMAIL_MESSAGE_LENGTH 2000 /** * @brief Maximum number of bytes of user-defined message text in email alerts. * * A value less or equal to 0 allows any size. */ static int max_email_message_length = MAX_EMAIL_MESSAGE_LENGTH; /** * @brief Memory cache of NVT information from the database. */ static nvtis_t* nvti_cache = NULL; /** * @brief Name of the database file. */ db_conn_info_t gvmd_db_conn_info = { NULL, NULL, NULL }; /** * @brief Whether a transaction has been opened and not committed yet. */ static gboolean in_transaction; /** * @brief Time of reception of the currently processed message. */ static struct timeval last_msg; /** * @brief The VT verification collation override */ static gchar *vt_verification_collation = NULL; /* GMP commands. */ /** * @brief The GMP command list. */ command_t gmp_commands[] = {{"AUTHENTICATE", "Authenticate with the manager." }, {"CREATE_ALERT", "Create an alert."}, {"CREATE_ASSET", "Create an asset."}, {"CREATE_CONFIG", "Create a config."}, {"CREATE_CREDENTIAL", "Create a credential."}, {"CREATE_FILTER", "Create a filter."}, {"CREATE_GROUP", "Create a group."}, {"CREATE_NOTE", "Create a note."}, {"CREATE_OVERRIDE", "Create an override."}, {"CREATE_PERMISSION", "Create a permission."}, {"CREATE_PORT_LIST", "Create a port list."}, {"CREATE_PORT_RANGE", "Create a port range in a port list."}, {"CREATE_REPORT", "Create a report."}, {"CREATE_REPORT_FORMAT", "Create a report format."}, {"CREATE_ROLE", "Create a role."}, {"CREATE_SCANNER", "Create a scanner."}, {"CREATE_SCHEDULE", "Create a schedule."}, {"CREATE_TAG", "Create a tag."}, {"CREATE_TARGET", "Create a target."}, {"CREATE_TASK", "Create a task."}, {"CREATE_TICKET", "Create a ticket."}, {"CREATE_TLS_CERTIFICATE", "Create a TLS certificate."}, {"CREATE_USER", "Create a new user."}, {"DELETE_ALERT", "Delete an alert."}, {"DELETE_ASSET", "Delete an asset."}, {"DELETE_CONFIG", "Delete a config."}, {"DELETE_CREDENTIAL", "Delete a credential."}, {"DELETE_FILTER", "Delete a filter."}, {"DELETE_GROUP", "Delete a group."}, {"DELETE_NOTE", "Delete a note."}, {"DELETE_OVERRIDE", "Delete an override."}, {"DELETE_PERMISSION", "Delete a permission."}, {"DELETE_PORT_LIST", "Delete a port list."}, {"DELETE_PORT_RANGE", "Delete a port range."}, {"DELETE_REPORT", "Delete a report."}, {"DELETE_REPORT_FORMAT", "Delete a report format."}, {"DELETE_ROLE", "Delete a role."}, {"DELETE_SCANNER", "Delete a scanner."}, {"DELETE_SCHEDULE", "Delete a schedule."}, {"DELETE_TAG", "Delete a tag."}, {"DELETE_TARGET", "Delete a target."}, {"DELETE_TASK", "Delete a task."}, {"DELETE_TICKET", "Delete a ticket."}, {"DELETE_TLS_CERTIFICATE", "Delete a TLS certificate."}, {"DELETE_USER", "Delete an existing user."}, {"DESCRIBE_AUTH", "Get details about the used authentication methods."}, {"EMPTY_TRASHCAN", "Empty the trashcan."}, {"GET_AGGREGATES", "Get aggregates of resources."}, {"GET_ALERTS", "Get all alerts."}, {"GET_ASSETS", "Get all assets."}, {"GET_CONFIGS", "Get all configs."}, {"GET_CREDENTIALS", "Get all credentials."}, {"GET_FEEDS", "Get details of one or all feeds this Manager uses."}, {"GET_FILTERS", "Get all filters."}, {"GET_GROUPS", "Get all groups."}, {"GET_INFO", "Get raw information for a given item."}, {"GET_NOTES", "Get all notes."}, {"GET_NVTS", "Get one or all available NVTs."}, {"GET_NVT_FAMILIES", "Get a list of all NVT families."}, {"GET_OVERRIDES", "Get all overrides."}, {"GET_PERMISSIONS", "Get all permissions."}, {"GET_PORT_LISTS", "Get all port lists."}, {"GET_PREFERENCES", "Get preferences for all available NVTs."}, {"GET_REPORTS", "Get all reports."}, {"GET_REPORT_FORMATS", "Get all report formats."}, {"GET_RESULTS", "Get results."}, {"GET_ROLES", "Get all roles."}, {"GET_SCANNERS", "Get all scanners."}, {"GET_SCHEDULES", "Get all schedules."}, {"GET_SETTINGS", "Get all settings."}, {"GET_SYSTEM_REPORTS", "Get all system reports."}, {"GET_TAGS", "Get all tags."}, {"GET_TARGETS", "Get all targets."}, {"GET_TASKS", "Get all tasks."}, {"GET_TICKETS", "Get all tickets."}, {"GET_TLS_CERTIFICATES", "Get all TLS certificates."}, {"GET_USERS", "Get all users."}, {"GET_VERSION", "Get the Greenbone Management Protocol version."}, {"GET_VULNS", "Get all vulnerabilities."}, {"HELP", "Get this help text."}, {"MODIFY_ALERT", "Modify an existing alert."}, {"MODIFY_ASSET", "Modify an existing asset."}, {"MODIFY_AUTH", "Modify the authentication methods."}, {"MODIFY_CONFIG", "Update an existing config."}, {"MODIFY_CREDENTIAL", "Modify an existing credential."}, {"MODIFY_FILTER", "Modify an existing filter."}, {"MODIFY_GROUP", "Modify an existing group."}, {"MODIFY_NOTE", "Modify an existing note."}, {"MODIFY_OVERRIDE", "Modify an existing override."}, {"MODIFY_PERMISSION", "Modify an existing permission."}, {"MODIFY_PORT_LIST", "Modify an existing port list."}, {"MODIFY_REPORT_FORMAT", "Modify an existing report format."}, {"MODIFY_ROLE", "Modify an existing role."}, {"MODIFY_SCANNER", "Modify an existing scanner."}, {"MODIFY_SCHEDULE", "Modify an existing schedule."}, {"MODIFY_SETTING", "Modify an existing setting."}, {"MODIFY_TAG", "Modify an existing tag."}, {"MODIFY_TARGET", "Modify an existing target."}, {"MODIFY_TASK", "Update an existing task."}, {"MODIFY_TICKET", "Modify an existing ticket."}, {"MODIFY_TLS_CERTIFICATE", "Modify an existing TLS certificate."}, {"MODIFY_USER", "Modify a user."}, {"MOVE_TASK", "Assign task to another slave scanner, even while running."}, {"RESTORE", "Restore a resource."}, {"RESUME_TASK", "Resume a stopped task."}, {"RUN_WIZARD", "Run a wizard."}, {"START_TASK", "Manually start an existing task."}, {"STOP_TASK", "Stop a running task."}, {"SYNC_CONFIG", "Synchronize a config with a scanner."}, {"TEST_ALERT", "Run an alert."}, {"VERIFY_REPORT_FORMAT", "Verify a report format."}, {"VERIFY_SCANNER", "Verify a scanner."}, {NULL, NULL}}; /** * @brief Check whether a command name is valid. * * @param[in] name Command name. * * @return 1 yes, 0 no. */ int valid_gmp_command (const char* name) { command_t *command; command = gmp_commands; while (command[0].name) if (strcasecmp (command[0].name, name) == 0) return 1; else command++; return 0; } /** * @brief Get the type associated with a GMP command. * * @param[in] name Command name. * * @return Freshly allocated type name if any, else NULL. */ static gchar * gmp_command_type (const char* name) { const char *under; under = strchr (name, '_'); if (under && (strlen (under) > 1)) { gchar *command; under++; command = g_strdup (under); if (command[strlen (command) - 1] == 's') command[strlen (command) - 1] = '\0'; if (valid_type (command)) return command; g_free (command); } return NULL; } /** * @brief Check whether a GMP command takes a resource. * * MODIFY_TARGET, for example, takes a target. * * @param[in] name Command name. * * @return 1 if takes resource, else 0. */ static int gmp_command_takes_resource (const char* name) { assert (name); return strcasecmp (name, "AUTHENTICATE") && strcasestr (name, "CREATE_") != name && strcasestr (name, "DESCRIBE_") != name && strcasecmp (name, "EMPTY_TRASHCAN") && strcasecmp (name, "GET_VERSION") && strcasecmp (name, "HELP") && strcasecmp (name, "RUN_WIZARD") && strcasestr (name, "SYNC_") != name; } /* General helpers. */ /** * @brief Check if a resource with a certain name exists already. * * Conflicting resource can be global or owned by the current user. * * @param[in] name Name of resource to check for. * @param[in] type Type of resource. * @param[in] resource Resource to ignore, 0 otherwise. * * @return Whether resource with name exists. */ gboolean resource_with_name_exists (const char *name, const char *type, resource_t resource) { int ret; char *quoted_name, *quoted_type; assert (type); if (!name) return 0; quoted_name = sql_quote (name); quoted_type = sql_quote (type); if (resource) ret = sql_int ("SELECT COUNT(*) FROM %ss" " WHERE name = '%s'" " AND id != %llu" " AND " ACL_USER_OWNS () ";", quoted_type, quoted_name, resource, current_credentials.uuid); else ret = sql_int ("SELECT COUNT(*) FROM %ss" " WHERE name = '%s'" " AND " ACL_USER_OWNS () ";", quoted_type, quoted_name, current_credentials.uuid); g_free (quoted_name); g_free (quoted_type); return !!ret; } /** * @brief Check if a resource with a certain name exists already. * * Conflicting resource can be owned by anybody. * * @param[in] name Name of resource to check for. * @param[in] type Type of resource. * @param[in] resource Resource to ignore, 0 otherwise. * * @return Whether resource with name exists. */ static gboolean resource_with_name_exists_global (const char *name, const char *type, resource_t resource) { int ret; char *quoted_name, *quoted_type; assert (type); if (!name) return 0; quoted_name = sql_quote (name); quoted_type = sql_quote (type); if (resource) ret = sql_int ("SELECT COUNT(*) FROM %ss" " WHERE name = '%s'" " AND id != %llu;", quoted_type, quoted_name, resource); else ret = sql_int ("SELECT COUNT(*) FROM %ss" " WHERE name = '%s';", quoted_type, quoted_name); g_free (quoted_name); g_free (quoted_type); return !!ret; } /** * @brief Ensure a string is in an array. * * @param[in] array Array. * @param[in] string String. Copied into array. */ static void array_add_new_string (array_t *array, const gchar *string) { guint index; for (index = 0; index < array->len; index++) if (strcmp (g_ptr_array_index (array, index), string) == 0) return; array_add (array, g_strdup (string)); } /** * @brief Find a resource in the trashcan given a UUID. * * @param[in] type Type of resource. * @param[in] uuid UUID of resource. * @param[out] resource Resource return, 0 if successfully failed to find * resource. * * @return FALSE on success (including if failed to find resource), TRUE on * error. */ gboolean find_trash (const char *type, const char *uuid, resource_t *resource) { gchar *quoted_uuid; if (!uuid) return FALSE; quoted_uuid = sql_quote (uuid); if (acl_user_owns_trash_uuid (type, quoted_uuid) == 0) { g_free (quoted_uuid); *resource = 0; return FALSE; } switch (sql_int64 (resource, "SELECT id FROM %ss_trash WHERE uuid = '%s';", type, quoted_uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return TRUE; break; } g_free (quoted_uuid); return FALSE; } /** * @brief Convert an ISO time into seconds since epoch. * * If no offset is specified, the timezone of the current user is used. * If there is no current user timezone, UTC is used. * * @param[in] text_time Time as text in ISO format: 2011-11-03T09:23:28+02:00. * * @return Time since epoch. 0 on error. */ int parse_iso_time (const char *text_time) { return parse_iso_time_tz (text_time, current_credentials.timezone); } /** * @brief Find a string in an array. * * @param[in] array Array. * @param[in] string String. * * @return The string from the array if found, else NULL. */ static gchar* array_find_string (array_t *array, const gchar *string) { guint index; for (index = 0; index < array->len; index++) { gchar *ele; ele = (gchar*) g_ptr_array_index (array, index); if (ele && (strcmp (ele, string) == 0)) return ele; } return NULL; } /** * @brief Find a string in a glib style string vector. * * @param[in] vector Vector. * @param[in] string String. * * @return The string from the vector if found, else NULL. */ static const gchar* vector_find_string (const gchar **vector, const gchar *string) { if (vector == NULL) return NULL; while (*vector) if (strcmp (*vector, string) == 0) return *vector; else vector++; return NULL; } /** * @brief Find a filter string in a glib style string vector. * * @param[in] vector Vector. * @param[in] string String. * * @return 1 if found, 2 if found with underscore prefix, else NULL. */ static int vector_find_filter (const gchar **vector, const gchar *string) { gchar *underscore; if (vector_find_string (vector, string)) return 1; underscore = g_strdup_printf ("_%s", string); if (vector_find_string (vector, underscore)) { g_free (underscore); return 2; } g_free (underscore); return 0; } /** * @brief Get last time NVT alerts were checked. * * @return Last check time. */ static int nvts_check_time () { return sql_int ("SELECT" " CASE WHEN EXISTS (SELECT * FROM meta" " WHERE name = 'nvts_check_time')" " THEN CAST ((SELECT value FROM meta" " WHERE name = 'nvts_check_time')" " AS INTEGER)" " ELSE 0" " END;"); } /** * @brief Get last time SCAP SecInfo alerts were checked. * * @return Last time SCAP was checked. */ static int scap_check_time () { return sql_int ("SELECT" " CASE WHEN EXISTS (SELECT * FROM meta" " WHERE name = 'scap_check_time')" " THEN CAST ((SELECT value FROM meta" " WHERE name = 'scap_check_time')" " AS INTEGER)" " ELSE 0" " END;"); } /** * @brief Get last time CERT SecInfo alerts were checked. * * @return Last time CERT was checked. */ static int cert_check_time () { return sql_int ("SELECT" " CASE WHEN EXISTS (SELECT * FROM meta" " WHERE name = 'cert_check_time')" " THEN CAST ((SELECT value FROM meta" " WHERE name = 'cert_check_time')" " AS INTEGER)" " ELSE 0" " END;"); } /** * @brief Setup for an option process. * * @param[in] log_config Log configuration. * @param[in] database Database. * * @return 0 success, -1 error, -2 database is wrong version, * -3 database needs to be initialised from server. */ int manage_option_setup (GSList *log_config, const db_conn_info_t *database) { int ret; if (gvm_auth_init ()) { fprintf (stderr, "Authentication init failed\n"); return -1; } ret = init_manage_helper (log_config, database, MANAGE_ABSOLUTE_MAX_IPS_PER_TARGET); assert (ret != -4); switch (ret) { case 0: break; case -2: fprintf (stderr, "Database is wrong version.\n"); return ret; case -3: fprintf (stderr, "Database must be initialised.\n"); return ret; default: fprintf (stderr, "Internal error.\n"); return ret; } init_manage_process (database); return 0; } /** * @brief Cleanup for an option process. */ void manage_option_cleanup () { cleanup_manage_process (TRUE); } /** * @brief Copy an array of columns. * * @param[in] columns Columns. * * @return Freshly allocated array. */ static column_t * column_array_copy (column_t *columns) { column_t *point, *start; int count; count = 1; point = columns; while (point->select) { point++; count++; } g_debug ("%s: %i", __func__, count); start = g_malloc0 (sizeof (column_t) * count); point = start; while (columns->select) { point->select = g_strdup (columns->select); point->filter = columns->filter ? g_strdup (columns->filter) : NULL; point->type = columns->type; point++; columns++; } return start; } /** * @brief Free an array of columns. * * @param[in] columns Columns. */ static void column_array_free (column_t *columns) { while (columns->filter) { g_free (columns->select); g_free (columns->filter); columns++; } g_free (columns); } /** * @brief Set the select clause of a column in an array of columns. * * Frees the existing select clause. * * @param[in] columns Columns. * @param[in] filter Filter term name. * @param[in] select Select clause. */ static void column_array_set (column_t *columns, const gchar *filter, gchar *select) { while (columns->select) { if (columns->filter && (strcmp (columns->filter, filter) == 0)) { g_free (columns->select); columns->select = select; break; } columns++; } } /* Filter utilities. */ /** * @brief Get the symbol of a keyword relation. * * @param[in] relation Relation. * * @return Relation symbol. */ const char * keyword_relation_symbol (keyword_relation_t relation) { switch (relation) { case KEYWORD_RELATION_APPROX: return "~"; case KEYWORD_RELATION_COLUMN_ABOVE: return ">"; case KEYWORD_RELATION_COLUMN_APPROX: return "~"; case KEYWORD_RELATION_COLUMN_EQUAL: return "="; case KEYWORD_RELATION_COLUMN_BELOW: return "<"; case KEYWORD_RELATION_COLUMN_REGEXP: return ":"; default: return ""; } } /** * @brief Free a keyword. * * @param[in] keyword Filter keyword. */ static void keyword_free (keyword_t* keyword) { g_free (keyword->string); g_free (keyword->column); } /** * @brief Get whether a keyword is special (like "and"). * * @param[in] keyword Keyword. * * @return 1 if special, else 0. */ int keyword_special (keyword_t *keyword) { if (keyword->string) return (strcmp (keyword->string, "and") == 0) || (strcmp (keyword->string, "or") == 0) || (strcmp (keyword->string, "not") == 0) || (strcmp (keyword->string, "re") == 0) || (strcmp (keyword->string, "regexp") == 0); return 0; } /** * @brief Parse a filter column relation. * * @param[in] relation Filter relation. * * @return keyword relation */ static keyword_relation_t parse_column_relation (const char relation) { switch (relation) { case '=': return KEYWORD_RELATION_COLUMN_EQUAL; case '~': return KEYWORD_RELATION_COLUMN_APPROX; case '>': return KEYWORD_RELATION_COLUMN_ABOVE; case '<': return KEYWORD_RELATION_COLUMN_BELOW; case ':': return KEYWORD_RELATION_COLUMN_REGEXP; default: return KEYWORD_RELATION_COLUMN_APPROX; } } /** * @brief Parse a filter keyword. * * @param[in] keyword Filter keyword. */ static void parse_keyword (keyword_t* keyword) { gchar *string; int digits; if (keyword->column == NULL && keyword->equal == 0) { keyword->relation = KEYWORD_RELATION_APPROX; keyword->type = KEYWORD_TYPE_STRING; return; } /* Special values to substitute */ if (keyword->column && (strcasecmp (keyword->column, "severity") == 0 || strcasecmp (keyword->column, "new_severity") == 0)) { if (strcasecmp (keyword->string, "Log") == 0) { keyword->double_value = SEVERITY_LOG; keyword->type = KEYWORD_TYPE_DOUBLE; return; } if (strcasecmp (keyword->string, "False Positive") == 0) { keyword->double_value = SEVERITY_FP; keyword->type = KEYWORD_TYPE_DOUBLE; return; } else if (strcasecmp (keyword->string, "Error") == 0) { keyword->double_value = SEVERITY_ERROR; keyword->type = KEYWORD_TYPE_DOUBLE; return; } } /* The type. */ string = keyword->string; if (*string == '\0') { keyword->type = KEYWORD_TYPE_STRING; return; } if (*string && *string == '-' && strlen (string) > 1) string++; digits = 0; while (*string && isdigit (*string)) { digits = 1; string++; } if (digits == 0) keyword->type = KEYWORD_TYPE_STRING; else if (*string) { struct tm date; gchar next; int parsed_integer; double parsed_double; char dummy[2]; memset (&date, 0, sizeof (date)); next = *(string + 1); if (next == '\0' && *string == 's') { time_t now; now = time (NULL); keyword->integer_value = now + atoi (keyword->string); keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'm') { time_t now; now = time (NULL); keyword->integer_value = now + (atoi (keyword->string) * 60); keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'h') { time_t now; now = time (NULL); keyword->integer_value = now + (atoi (keyword->string) * 3600); keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'd') { time_t now; now = time (NULL); keyword->integer_value = now + (atoi (keyword->string) * 86400); keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'w') { time_t now; now = time (NULL); keyword->integer_value = now + atoi (keyword->string) * 604800; keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'M') { time_t now; now = time (NULL); keyword->integer_value = add_months (now, atoi (keyword->string)); keyword->type = KEYWORD_TYPE_INTEGER; } else if (next == '\0' && *string == 'y') { time_t now; now = time (NULL); keyword->integer_value = add_months (now, atoi (keyword->string) * 12); keyword->type = KEYWORD_TYPE_INTEGER; } // Add cases for t%H:%M although it is incorrect sometimes it is easier // to call filter.lower on the frontend then it can happen that the // T indicator is lowered as well. else if (strptime (keyword->string, "%Y-%m-%dt%H:%M", &date)) { keyword->integer_value = mktime (&date); keyword->type = KEYWORD_TYPE_INTEGER; g_debug ("Parsed Y-m-dtH:M %s to timestamp %d.", keyword->string, keyword->integer_value); } else if (strptime (keyword->string, "%Y-%m-%dt%Hh%M", &date)) { keyword->integer_value = mktime (&date); keyword->type = KEYWORD_TYPE_INTEGER; g_debug ("Parsed Y-m-dtHhM %s to timestamp %d.", keyword->string, keyword->integer_value); } else if (strptime (keyword->string, "%Y-%m-%dT%H:%M", &date)) { keyword->integer_value = mktime (&date); keyword->type = KEYWORD_TYPE_INTEGER; g_debug ("Parsed Y-m-dTH:M %s to timestamp %d.", keyword->string, keyword->integer_value); } // Add T%Hh%M for downwards compatible filter else if (strptime (keyword->string, "%Y-%m-%dT%Hh%M", &date)) { keyword->integer_value = mktime (&date); keyword->type = KEYWORD_TYPE_INTEGER; g_debug ("Parsed Y-m-dTHhM %s to timestamp %d.", keyword->string, keyword->integer_value); } else if (memset (&date, 0, sizeof (date)), strptime (keyword->string, "%Y-%m-%d", &date)) { keyword->integer_value = mktime (&date); keyword->type = KEYWORD_TYPE_INTEGER; g_debug ("Parsed Y-m-d %s to timestamp %d.", keyword->string, keyword->integer_value); } else if (sscanf (keyword->string, "%d%1s", &parsed_integer, dummy) == 1) { keyword->integer_value = parsed_integer; keyword->type = KEYWORD_TYPE_INTEGER; } else if (sscanf (keyword->string, "%lf%1s", &parsed_double, dummy) == 1 && parsed_double <= DBL_MAX) { keyword->double_value = parsed_double; keyword->type = KEYWORD_TYPE_DOUBLE; } else keyword->type = KEYWORD_TYPE_STRING; } else { keyword->integer_value = atoi (keyword->string); keyword->type = KEYWORD_TYPE_INTEGER; } } /** * @brief Cleans up keywords with special conditions and relations. * * @param[in] keyword Keyword to clean up. */ static void cleanup_keyword (keyword_t *keyword) { if (keyword->column == NULL) return; if (strcasecmp (keyword->column, "first") == 0) { /* "first" must be >= 1 */ if (keyword->integer_value <= 0) { g_free (keyword->string); keyword->integer_value = 1; keyword->string = g_strdup ("1"); } keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; } else if (strcasecmp (keyword->column, "rows") == 0) { /* rows must be >= 1 or a special value (-1 or -2) */ if (keyword->integer_value == 0) { g_free (keyword->string); keyword->integer_value = 1; keyword->string = g_strdup ("1"); } else if (keyword->integer_value < -2) { g_free (keyword->string); keyword->integer_value = -1; keyword->string = g_strdup ("-1"); } keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; } else if (strcasecmp (keyword->column, "min_qod") == 0) { /* min_qod must be a percentage (between 0 and 100) */ if (keyword->integer_value < 0) { g_free (keyword->string); keyword->integer_value = 0; keyword->string = g_strdup ("0"); } else if (keyword->integer_value > 100) { g_free (keyword->string); keyword->integer_value = 100; keyword->string = g_strdup ("100"); } keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; } else if (strcasecmp (keyword->column, "apply_overrides") == 0 || strcasecmp (keyword->column, "overrides") == 0 || strcasecmp (keyword->column, "notes") == 0 || strcasecmp (keyword->column, "result_hosts_only") == 0) { /* Boolean options (0 or 1) */ if (keyword->integer_value != 0 && keyword->integer_value != 1) { g_free (keyword->string); keyword->integer_value = 1; keyword->string = g_strdup ("1"); } keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; } else if (strcasecmp (keyword->column, "delta_states") == 0 || strcasecmp (keyword->column, "levels") == 0 || strcasecmp (keyword->column, "sort") == 0 || strcasecmp (keyword->column, "sort-reverse") == 0) { /* Text options */ keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; } } /** * @brief Check whether a keyword has any effect in the filter. * * Some keywords are redundant, like a second sort= keyword. * * @param[in] array Array of existing keywords. * @param[in] keyword Keyword under consideration. * * @return 0 no, 1 yes. */ static int keyword_applies (array_t *array, const keyword_t *keyword) { if (keyword->column && ((strcmp (keyword->column, "sort") == 0) || (strcmp (keyword->column, "sort-reverse") == 0)) && (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && ((strcmp (item->column, "sort") == 0) || (strcmp (item->column, "sort-reverse") == 0))) return 0; } return 1; } if (keyword->column && (strcmp (keyword->column, "first") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "first") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "rows") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "rows") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "apply_overrides") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "apply_overrides") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "delta_states") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "delta_states") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "levels") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "levels") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "min_qod") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "min_qod") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "notes") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "notes") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "overrides") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "overrides") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "result_hosts_only") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "result_hosts_only") == 0)) return 0; } } if (keyword->column && (strcmp (keyword->column, "timezone") == 0)) { int index; index = array->len; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (array, index); if (item->column && (strcmp (item->column, "timezone") == 0)) return 0; } } return 1; } /** * @brief Free a split filter. * * @param[in] split Split filter. */ void filter_free (array_t *split) { keyword_t **point; for (point = (keyword_t**) split->pdata; *point; point++) keyword_free (*point); array_free (split); } /** * @brief Flag to control the default sorting produced by split_filter. * * If this is true, and the filter does not specify a sort field, then * split_filter will not insert a default sort term, so that the random * (and fast) table order in the database will be used. */ static int table_order_if_sort_not_specified = 0; /** * @brief Ensure filter parts contains the special keywords. * * @param[in] parts Array of keyword strings. * @param[in] given_filter Filter term. */ void split_filter_add_specials (array_t *parts, const gchar* given_filter) { int index, first, max, sort; keyword_t *keyword; index = parts->len; first = max = sort = 0; while (index--) { keyword_t *item; item = (keyword_t*) g_ptr_array_index (parts, index); if (item->column && (strcmp (item->column, "first") == 0)) first = 1; else if (item->column && (strcmp (item->column, "rows") == 0)) max = 1; else if (item->column && ((strcmp (item->column, "sort") == 0) || (strcmp (item->column, "sort-reverse") == 0))) sort = 1; } if (first == 0) { keyword = g_malloc0 (sizeof (keyword_t)); keyword->column = g_strdup ("first"); keyword->string = g_strdup ("1"); keyword->type = KEYWORD_TYPE_STRING; keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; array_add (parts, keyword); } if (max == 0) { keyword = g_malloc0 (sizeof (keyword_t)); keyword->column = g_strdup ("rows"); keyword->string = g_strdup ("-2"); keyword->type = KEYWORD_TYPE_STRING; keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; array_add (parts, keyword); } if (table_order_if_sort_not_specified == 0 && sort == 0) { keyword = g_malloc0 (sizeof (keyword_t)); keyword->column = g_strdup ("sort"); keyword->string = g_strdup ("name"); keyword->type = KEYWORD_TYPE_STRING; keyword->relation = KEYWORD_RELATION_COLUMN_EQUAL; array_add (parts, keyword); } } /** * @brief Split the filter term into parts. * * @param[in] given_filter Filter term. * * @return Array of strings, the parts. */ array_t * split_filter (const gchar* given_filter) { int in_quote, between; array_t *parts; const gchar *current_part, *filter; keyword_t *keyword; assert (given_filter); /* Collect the filter terms in an array. */ filter = given_filter; parts = make_array (); in_quote = 0; between = 1; keyword = NULL; current_part = filter; /* To silence compiler warning. */ while (*filter) { switch (*filter) { case '=': case '~': if (between) { /* Empty index. Start a part. */ keyword = g_malloc0 (sizeof (keyword_t)); if (*filter == '=') keyword->equal = 1; else keyword->approx = 1; current_part = filter + 1; between = 0; break; } case ':': case '>': case '<': if (between) { /* Empty index. Start a part. */ keyword = g_malloc0 (sizeof (keyword_t)); current_part = filter; between = 0; break; } if (in_quote) break; /* End of an index. */ if (keyword == NULL) { assert (0); break; } if (keyword->column) /* Already had an index char. */ break; if (filter <= (current_part - 1)) { assert (0); break; } keyword->column = g_strndup (current_part, filter - current_part); current_part = filter + 1; keyword->relation = parse_column_relation(*filter); break; case ' ': case '\t': case '\n': case '\r': if (in_quote || between) break; /* End of a part. */ if (keyword == NULL) { assert (0); break; } keyword->string = g_strndup (current_part, filter - current_part); parse_keyword (keyword); cleanup_keyword (keyword); if (keyword_applies (parts, keyword)) array_add (parts, keyword); keyword = NULL; between = 1; break; case '"': if (in_quote) { /* End of a quoted part. */ if (keyword == NULL) { assert (0); break; } keyword->quoted = 1; keyword->string = g_strndup (current_part, filter - current_part); parse_keyword (keyword); cleanup_keyword (keyword); if (keyword_applies (parts, keyword)) array_add (parts, keyword); keyword = NULL; in_quote = 0; between = 1; } else if (between) { /* Start of a quoted part. */ keyword = g_malloc0 (sizeof (keyword_t)); in_quote = 1; current_part = filter + 1; between = 0; } else if (keyword->column && filter == current_part) { /* A quoted index. */ in_quote = 1; current_part++; } else if ((keyword->equal || keyword->approx) && filter == current_part) { /* A quoted exact term, like ="abc" * or a prefixed approximate term, like ~"abc". */ in_quote = 1; current_part++; } /* Else just a quote in a keyword, like ab"cd. */ break; default: if (between) { /* Start of a part. */ keyword = g_malloc0 (sizeof (keyword_t)); current_part = filter; between = 0; } break; } filter++; } if (between == 0) { if (keyword == NULL) assert (0); else { keyword->quoted = in_quote; keyword->string = g_strdup (current_part); parse_keyword (keyword); cleanup_keyword (keyword); if (keyword_applies (parts, keyword)) array_add (parts, keyword); keyword = NULL; } } assert (keyword == NULL); /* Make sure the special keywords appear in the array. */ split_filter_add_specials (parts, given_filter); array_add (parts, NULL); return parts; } /** * @brief Get info from a filter. * * It's up to the caller to ensure that max is adjusted for Max Rows Per Page * (by calling manage_max_rows). * * @param[in] filter Filter. * @param[out] first Number of first item. * @param[out] max Max number of rows. * @param[out] sort_field Sort field. * @param[out] sort_order Sort order. */ void manage_filter_controls (const gchar *filter, int *first, int *max, gchar **sort_field, int *sort_order) { keyword_t **point; array_t *split; if (filter == NULL) { if (first) *first = 1; if (max) *max = -2; if (sort_field) *sort_field = g_strdup ("name"); if (sort_order) *sort_order = 1; return; } split = split_filter (filter); point = (keyword_t**) split->pdata; if (first) { *first = 1; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "first") == 0)) { *first = atoi (keyword->string); if (*first < 0) *first = 0; break; } point++; } } point = (keyword_t**) split->pdata; if (max) { *max = -2; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "rows") == 0)) { *max = atoi (keyword->string); if (*max == -2) setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max); else if (*max < 1) *max = -1; break; } point++; } } point = (keyword_t**) split->pdata; if (sort_field || sort_order) { if (sort_field) *sort_field = NULL; if (sort_order) *sort_order = 1; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "sort") == 0)) { if (sort_field) *sort_field = g_strdup (keyword->string); if (sort_order) *sort_order = 1; break; } if (keyword->column && (strcmp (keyword->column, "sort-reverse") == 0)) { if (sort_field) *sort_field = g_strdup (keyword->string); if (sort_order) *sort_order = 0; break; } point++; } if (sort_field && (*sort_field == NULL)) *sort_field = g_strdup ("name"); } filter_free (split); return; } /** * @brief Get an int column from a filter split. * * @param[in] point Filter split. * @param[in] column Name of column. * @param[out] val Value of column. * * @return 0 success, 1 fail. */ static int filter_control_int (keyword_t **point, const char *column, int *val) { if (val) while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, column) == 0)) { *val = atoi (keyword->string); return 0; } point++; } return 1; } /** * @brief Get a string column from a filter split. * * @param[in] point Filter split. * @param[in] column Name of column. * @param[out] string Value of column, freshly allocated. * * @return 0 success, 1 fail. */ static int filter_control_str (keyword_t **point, const char *column, gchar **string) { if (string) while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, column) == 0)) { *string = g_strdup (keyword->string); return 0; } point++; } return 1; } /** * @brief Get info from a result filter for a report. * * It's up to the caller to ensure that max is adjusted for Max Rows Per Page * (by calling manage_max_rows). * * @param[in] filter Filter. * @param[out] first Number of first item. * @param[out] max Max number of rows. * @param[out] sort_field Sort field. * @param[out] sort_order Sort order. * @param[out] result_hosts_only Whether to show only hosts with results. * @param[out] min_qod Minimum QoD base of included results. All * results if NULL. * @param[out] levels String describing threat levels (message types) * to include in count (for example, "hmlg" for * High, Medium, Low and loG). All levels if NULL. * @param[out] delta_states String describing delta states to include in count * (for example, "sngc" Same, New, Gone and Changed). * All levels if NULL. * @param[out] search_phrase Phrase that results must include. All results * if NULL or "". * @param[out] search_phrase_exact Whether search phrase is exact. * @param[out] notes Whether to include notes. * @param[out] overrides Whether to include overrides. * @param[out] apply_overrides Whether to apply overrides. * @param[out] zone Timezone. */ void manage_report_filter_controls (const gchar *filter, int *first, int *max, gchar **sort_field, int *sort_order, int *result_hosts_only, gchar **min_qod, gchar **levels, gchar **delta_states, gchar **search_phrase, int *search_phrase_exact, int *notes, int *overrides, int *apply_overrides, gchar **zone) { keyword_t **point; array_t *split; int val; gchar *string; if (filter == NULL) return; split = split_filter (filter); point = (keyword_t**) split->pdata; if (first) { *first = 1; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "first") == 0)) { *first = atoi (keyword->string); if (*first < 0) *first = 0; break; } point++; } /* Switch from 1 to 0 indexing. */ (*first)--; } point = (keyword_t**) split->pdata; if (max) { *max = 100; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "rows") == 0)) { *max = atoi (keyword->string); if (*max == -2) setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max); else if (*max < 1) *max = -1; break; } point++; } } point = (keyword_t**) split->pdata; if (sort_field || sort_order) { if (sort_field) *sort_field = NULL; if (sort_order) *sort_order = 1; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && (strcmp (keyword->column, "sort") == 0)) { if (sort_field) *sort_field = g_strdup (keyword->string); if (sort_order) *sort_order = 1; break; } if (keyword->column && (strcmp (keyword->column, "sort-reverse") == 0)) { if (sort_field) *sort_field = g_strdup (keyword->string); if (sort_order) *sort_order = 0; break; } point++; } if (sort_field && (*sort_field == NULL)) *sort_field = g_strdup ("name"); /* NVT name. */ } if (search_phrase) { GString *phrase; phrase = g_string_new (""); point = (keyword_t**) split->pdata; if (search_phrase_exact) *search_phrase_exact = 0; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column == NULL) { if (search_phrase_exact && keyword->equal) /* If one term is "exact" then the search is "exact", because * for reports the filter terms are combined into a single * search term. */ *search_phrase_exact = 1; g_string_append_printf (phrase, "%s ", keyword->string); } point++; } *search_phrase = g_strchomp (phrase->str); g_string_free (phrase, FALSE); } if (result_hosts_only) { if (filter_control_int ((keyword_t **) split->pdata, "result_hosts_only", &val)) *result_hosts_only = 1; else *result_hosts_only = val; } if (notes) { if (filter_control_int ((keyword_t **) split->pdata, "notes", &val)) *notes = 1; else *notes = val; } if (overrides) { if (filter_control_int ((keyword_t **) split->pdata, "overrides", &val)) *overrides = 1; else *overrides = val; } if (apply_overrides) { if (filter_control_int ((keyword_t **) split->pdata, "apply_overrides", &val)) { if (filter_control_int ((keyword_t **) split->pdata, "overrides", &val)) *apply_overrides = 1; else *apply_overrides = val; } else *apply_overrides = val; } if (delta_states) { if (filter_control_str ((keyword_t **) split->pdata, "delta_states", &string)) *delta_states = NULL; else *delta_states = string; } if (levels) { if (filter_control_str ((keyword_t **) split->pdata, "levels", &string)) *levels = NULL; else *levels = string; } if (min_qod) { if (filter_control_str ((keyword_t **) split->pdata, "min_qod", &string)) *min_qod = NULL; else *min_qod = string; } if (zone) { if (filter_control_str ((keyword_t **) split->pdata, "timezone", &string)) *zone = NULL; else *zone = string; } filter_free (split); return; } /** * @brief Append relation to filter. * * @param[in] clean Filter. * @param[in] keyword Keyword * @param[in] relation Relation char. */ static void append_relation (GString *clean, keyword_t *keyword, const char relation) { if (strcmp (keyword->column, "rows") == 0) { int max; if (strcmp (keyword->string, "-2") == 0) setting_value_int (SETTING_UUID_ROWS_PER_PAGE, &max); else max = atoi (keyword->string); g_string_append_printf (clean, " %s%c%i", keyword->column, relation, manage_max_rows (max)); } else if (keyword->quoted) g_string_append_printf (clean, " %s%c\"%s\"", keyword->column, relation, keyword->string); else g_string_append_printf (clean, " %s%c%s", keyword->column, relation, keyword->string); } /** * @brief Clean a filter, removing a keyword in the process. * * @param[in] filter Filter. * @param[in] column Keyword to remove, or NULL. * * @return Cleaned filter. */ gchar * manage_clean_filter_remove (const gchar *filter, const gchar *column) { GString *clean; keyword_t **point; array_t *split; if (filter == NULL) return g_strdup (""); clean = g_string_new (""); split = split_filter (filter); point = (keyword_t**) split->pdata; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && column && strlen (column) && ((strcasecmp (keyword->column, column) == 0) || (keyword->column[0] == '_' && strcasecmp (keyword->column + 1, column) == 0))) { /* Remove this keyword. */; } else if (keyword->column) switch (keyword->relation) { case KEYWORD_RELATION_COLUMN_EQUAL: append_relation (clean, keyword, '='); break; case KEYWORD_RELATION_COLUMN_APPROX: append_relation (clean, keyword, '~'); break; case KEYWORD_RELATION_COLUMN_ABOVE: append_relation (clean, keyword, '>'); break; case KEYWORD_RELATION_COLUMN_BELOW: append_relation (clean, keyword, '<'); break; case KEYWORD_RELATION_COLUMN_REGEXP: append_relation (clean, keyword, ':'); break; case KEYWORD_RELATION_APPROX: if (keyword->quoted) g_string_append_printf (clean, " \"%s\"", keyword->string); else g_string_append_printf (clean, " %s", keyword->string); break; } else { const char *relation_symbol; if (keyword->equal) relation_symbol = "="; else if (keyword->approx) relation_symbol = "~"; else relation_symbol = ""; if (keyword->quoted) g_string_append_printf (clean, " %s\"%s\"", relation_symbol, keyword->string); else g_string_append_printf (clean, " %s%s", relation_symbol, keyword->string); } point++; } filter_free (split); return g_strstrip (g_string_free (clean, FALSE)); } /** * @brief Clean a filter. * * @param[in] filter Filter. * * @return Cleaned filter. */ gchar * manage_clean_filter (const gchar *filter) { return manage_clean_filter_remove (filter, NULL); } /** * @brief Return SQL join words for filter_clause. * * @param[in] first Whether keyword is first. * @param[in] last_was_and Whether last keyword was "and". * @param[in] last_was_not Whether last keyword was "not". * * @return SQL join words. */ static const char * get_join (int first, int last_was_and, int last_was_not) { const char *pre; if (first) { if (last_was_not) pre = "NOT "; else pre = ""; } else { if (last_was_and) { if (last_was_not) pre = " AND NOT "; else pre = " AND "; } else { if (last_was_not) pre = " OR NOT "; else pre = " OR "; } } return pre; } /** * @brief Get the column expression for a filter column. * * @param[in] select_columns SELECT columns. * @param[in] filter_column Filter column. * @param[out] type Type of returned column. * * @return Column for the SELECT statement. */ static gchar * columns_select_column_single (column_t *select_columns, const char *filter_column, keyword_type_t* type) { column_t *columns; if (type) *type = KEYWORD_TYPE_UNKNOWN; if (select_columns == NULL) return NULL; columns = select_columns; while ((*columns).select) { if ((*columns).filter && strcmp ((*columns).filter, filter_column) == 0) { if (type) *type = (*columns).type; return (*columns).select; } if ((*columns).filter && *((*columns).filter) && *((*columns).filter) == '_' && strcmp (((*columns).filter) + 1, filter_column) == 0) { if (type) *type = (*columns).type; return (*columns).select; } columns++; } columns = select_columns; while ((*columns).select) { if (strcmp ((*columns).select, filter_column) == 0) { if (type) *type = (*columns).type; return (*columns).select; } columns++; } return NULL; } /** * @brief Get the selection term for a filter column. * * @param[in] select_columns SELECT columns. * @param[in] where_columns WHERE "columns". * @param[in] filter_column Filter column. * * @return Column for the SELECT statement. */ static gchar * columns_select_column (column_t *select_columns, column_t *where_columns, const char *filter_column) { gchar *column; column = columns_select_column_single (select_columns, filter_column, NULL); if (column) return column; return columns_select_column_single (where_columns, filter_column, NULL); } /** * @brief Get the selection term for a filter column. * * @param[in] select_columns SELECT columns. * @param[in] where_columns WHERE "columns". * @param[in] filter_column Filter column. * @param[out] type Type of the returned column. * * @return Column for the SELECT statement. */ static gchar * columns_select_column_with_type (column_t *select_columns, column_t *where_columns, const char *filter_column, keyword_type_t* type) { gchar *column; column = columns_select_column_single (select_columns, filter_column, type); if (column) return column; return columns_select_column_single (where_columns, filter_column, type); } /** * @brief Return column list for SELECT statement. * * @param[in] select_columns SELECT columns. * * @return Column list for the SELECT statement. */ gchar * columns_build_select (column_t *select_columns) { if (select_columns == NULL) return g_strdup ("''"); if ((*select_columns).select) { column_t *columns; GString *select; columns = select_columns; select = g_string_new (""); g_string_append (select, (*columns).select); if ((*columns).filter) g_string_append_printf (select, " AS %s", (*columns).filter); columns++; while ((*columns).select) { g_string_append_printf (select, ", %s", (*columns).select); if ((*columns).filter) g_string_append_printf (select, " AS %s", (*columns).filter); columns++; } return g_string_free (select, FALSE); } return g_strdup ("''"); } /** * @brief Check whether a keyword applies to a column. * * @param[in] keyword Keyword. * @param[in] column Column. * * @return 1 if applies, else 0. */ static int keyword_applies_to_column (keyword_t *keyword, const char* column) { if ((strcmp (column, "threat") == 0) && (strstr ("None", keyword->string) == NULL) && (strstr ("False Positive", keyword->string) == NULL) && (strstr ("Error", keyword->string) == NULL) && (strstr ("Alarm", keyword->string) == NULL) && (strstr ("High", keyword->string) == NULL) && (strstr ("Medium", keyword->string) == NULL) && (strstr ("Low", keyword->string) == NULL) && (strstr ("Log", keyword->string) == NULL)) return 0; if ((strcmp (column, "trend") == 0) && (strstr ("more", keyword->string) == NULL) && (strstr ("less", keyword->string) == NULL) && (strstr ("up", keyword->string) == NULL) && (strstr ("down", keyword->string) == NULL) && (strstr ("same", keyword->string) == NULL)) return 0; if ((strcmp (column, "status") == 0) && (strstr ("Delete Requested", keyword->string) == NULL) && (strstr ("Ultimate Delete Requested", keyword->string) == NULL) && (strstr ("Done", keyword->string) == NULL) && (strstr ("New", keyword->string) == NULL) && (strstr ("Running", keyword->string) == NULL) && (strstr ("Queued", keyword->string) == NULL) && (strstr ("Stop Requested", keyword->string) == NULL) && (strstr ("Stopped", keyword->string) == NULL) && (strstr ("Interrupted", keyword->string) == NULL)) return 0; return 1; } /** * @brief Append parts for a "tag" keyword to a filter clause. * * @param[in,out] clause Buffer for the filter clause to append to. * @param[in] keyword The keyword to create the filter clause part for. * @param[in] type The resource type. * @param[in] first_keyword Whether keyword is first. * @param[in] last_was_and Whether last keyword was "and". * @param[in] last_was_not Whether last keyword was "not". */ static void filter_clause_append_tag (GString *clause, keyword_t *keyword, const char *type, int first_keyword, int last_was_and, int last_was_not) { gchar *quoted_keyword; gchar **tag_split, *tag_name, *tag_value; int value_given; quoted_keyword = sql_quote (keyword->string); tag_split = g_strsplit (quoted_keyword, "=", 2); tag_name = g_strdup (tag_split[0] ? tag_split[0] : ""); if (tag_split[0] && tag_split[1]) { tag_value = g_strdup (tag_split[1]); value_given = 1; } else { tag_value = g_strdup (""); value_given = 0; } if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL || keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE || keyword->relation == KEYWORD_RELATION_COLUMN_BELOW) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.name = '%s'" " AND tags.active != 0" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)" " %s%s%s))", get_join (first_keyword, last_was_and, last_was_not), tag_name, type, type, (value_given ? "AND tags.value = '" : ""), value_given ? tag_value : "", (value_given ? "'" : "")); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.name %s '%%%%%s%%%%'" " AND tags.active != 0" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)" " AND tags.value %s '%%%%%s%%%%'))", get_join (first_keyword, last_was_and, last_was_not), sql_ilike_op (), tag_name, type, type, sql_ilike_op (), tag_value); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.name %s '%s'" " AND tags.active != 0" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)" " AND tags.value" " %s '%s'))", get_join (first_keyword, last_was_and, last_was_not), sql_regexp_op (), tag_name, type, type, sql_regexp_op (), tag_value); } g_free (quoted_keyword); g_strfreev(tag_split); g_free(tag_name); g_free(tag_value); } /** * @brief Append parts for a "tag_id" keyword to a filter clause. * * @param[in,out] clause Buffer for the filter clause to append to. * @param[in] keyword The keyword to create the filter clause part for. * @param[in] type The resource type. * @param[in] first_keyword Whether keyword is first. * @param[in] last_was_and Whether last keyword was "and". * @param[in] last_was_not Whether last keyword was "not". */ static void filter_clause_append_tag_id (GString *clause, keyword_t *keyword, const char *type, int first_keyword, int last_was_and, int last_was_not) { gchar *quoted_keyword; quoted_keyword = sql_quote (keyword->string); if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL || keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE || keyword->relation == KEYWORD_RELATION_COLUMN_BELOW) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.uuid = '%s'" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)))", get_join (first_keyword, last_was_and, last_was_not), quoted_keyword, type, type); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.uuid %s '%%%%%s%%%%'" " AND tags.active != 0" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)))", get_join (first_keyword, last_was_and, last_was_not), sql_ilike_op (), quoted_keyword, type, type); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP) { g_string_append_printf (clause, "%s" "(EXISTS" " (SELECT * FROM tags" " WHERE tags.uuid %s '%s'" " AND tags.active != 0" " AND user_has_access_uuid (CAST ('tag' AS text)," " CAST (tags.uuid AS text)," " CAST ('get_tags' AS text)," " 0)" " AND EXISTS (SELECT * FROM tag_resources" " WHERE tag_resources.resource_uuid" " = %ss.uuid" " AND tag_resources.resource_type" " = '%s'" " AND tag = tags.id)))", get_join (first_keyword, last_was_and, last_was_not), sql_regexp_op (), quoted_keyword, type, type); } g_free (quoted_keyword); } /** * @brief Return SQL WHERE clause for restricting a SELECT to a filter term. * * @param[in] type Resource type. * @param[in] filter Filter term. * @param[in] filter_columns Filter columns. * @param[in] select_columns SELECT columns. * @param[in] where_columns Columns in SQL that only appear in WHERE clause. * @param[out] trash Whether the trash table is being queried. * @param[out] order_return If given then order clause. * @param[out] first_return If given then first row. * @param[out] max_return If given then max rows. * @param[out] permissions When given then permissions string vector. * @param[out] owner_filter When given then value of owner keyword. * * @return WHERE clause for filter if one is required, else NULL. */ gchar * filter_clause (const char* type, const char* filter, const char **filter_columns, column_t *select_columns, column_t *where_columns, int trash, gchar **order_return, int *first_return, int *max_return, array_t **permissions, gchar **owner_filter) { GString *clause, *order; keyword_t **point; int first_keyword, first_order, last_was_and, last_was_not, last_was_re, skip; array_t *split; if (filter == NULL) filter = ""; while (*filter && isspace (*filter)) filter++; if (permissions) *permissions = make_array (); if (owner_filter) *owner_filter = NULL; /* Add SQL to the clause for each keyword or phrase. */ if (max_return) *max_return = -2; clause = g_string_new (""); order = g_string_new (""); /* NB This may add terms that are missing, like "sort". */ split = split_filter (filter); point = (keyword_t**) split->pdata; first_keyword = 1; last_was_and = 0; last_was_not = 0; last_was_re = 0; first_order = 1; while (*point) { gchar *quoted_keyword; int index; keyword_t *keyword; skip = 0; keyword = *point; if ((keyword->column == NULL) && (strlen (keyword->string) == 0)) { point++; continue; } if ((keyword->column == NULL) && (strcasecmp (keyword->string, "or") == 0)) { point++; continue; } if ((keyword->column == NULL) && (strcasecmp (keyword->string, "and") == 0)) { last_was_and = 1; point++; continue; } if ((keyword->column == NULL) && (strcasecmp (keyword->string, "not") == 0)) { last_was_not = 1; point++; continue; } if ((keyword->column == NULL) && (strcasecmp (keyword->string, "re") == 0)) { last_was_re = 1; point++; continue; } if ((keyword->column == NULL) && (strcasecmp (keyword->string, "regexp") == 0)) { last_was_re = 1; point++; continue; } /* Check for ordering parts, like sort=name or sort-reverse=string. */ if (keyword->column && (strcasecmp (keyword->column, "sort") == 0)) { if (vector_find_filter (filter_columns, keyword->string) == 0) { point++; continue; } if (first_order) { if ((strcmp (type, "report") == 0) && (strcmp (keyword->string, "status") == 0)) g_string_append_printf (order, " ORDER BY" " (CASE WHEN (SELECT target = 0 FROM tasks" " WHERE tasks.id = task)" " THEN 'Container'" " ELSE run_status_name (scan_run_status)" " || (SELECT CAST (temp / 100 AS text)" " || CAST (temp / 10 AS text)" " || CAST (temp %% 10 as text)" " FROM (SELECT report_progress (id) AS temp)" " AS temp_sub)" " END)" " ASC"); else if ((strcmp (type, "task") == 0) && (strcmp (keyword->string, "status") == 0)) g_string_append_printf (order, " ORDER BY" " (CASE WHEN target = 0" " THEN 'Container'" " ELSE run_status_name (run_status)" " || (SELECT CAST (temp / 100 AS text)" " || CAST (temp / 10 AS text)" " || CAST (temp %% 10 as text)" " FROM (SELECT report_progress (id) AS temp" " FROM reports" " WHERE task = tasks.id" " ORDER BY date DESC LIMIT 1)" " AS temp_sub)" " END)" " ASC"); else if ((strcmp (type, "task") == 0) && (strcmp (keyword->string, "threat") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY order_threat (%s) ASC", column); } else if (strcmp (keyword->string, "severity") == 0 || strcmp (keyword->string, "original_severity") == 0 || strcmp (keyword->string, "cvss") == 0 || strcmp (keyword->string, "cvss_base") == 0 || strcmp (keyword->string, "max_cvss") == 0 || strcmp (keyword->string, "fp_per_host") == 0 || strcmp (keyword->string, "log_per_host") == 0 || strcmp (keyword->string, "low_per_host") == 0 || strcmp (keyword->string, "medium_per_host") == 0 || strcmp (keyword->string, "high_per_host") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); g_string_append_printf (order, " ORDER BY CASE CAST (%s AS text)" " WHEN '' THEN '-Infinity'::real" " ELSE coalesce(%s::real," " '-Infinity'::real)" " END ASC", column, column); } else if (strcmp (keyword->string, "roles") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY" " CASE WHEN %s %s 'Admin.*'" " THEN '0' || %s" " ELSE '1' || %s END ASC", column, sql_regexp_op (), column, column); } else if ((strcmp (keyword->string, "created") == 0) || (strcmp (keyword->string, "modified") == 0) || (strcmp (keyword->string, "published") == 0) || (strcmp (keyword->string, "qod") == 0) || (strcmp (keyword->string, "cves") == 0) || (strcmp (keyword->string, "high") == 0) || (strcmp (keyword->string, "medium") == 0) || (strcmp (keyword->string, "low") == 0) || (strcmp (keyword->string, "log") == 0) || (strcmp (keyword->string, "false_positive") == 0) || (strcmp (keyword->string, "hosts") == 0) || (strcmp (keyword->string, "result_hosts") == 0) || (strcmp (keyword->string, "results") == 0) || (strcmp (keyword->string, "latest_severity") == 0) || (strcmp (keyword->string, "highest_severity") == 0) || (strcmp (keyword->string, "average_severity") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY %s ASC", column); } else if ((strcmp (keyword->string, "ips") == 0) || (strcmp (keyword->string, "total") == 0) || (strcmp (keyword->string, "tcp") == 0) || (strcmp (keyword->string, "udp") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY CAST (%s AS INTEGER) ASC", column); } else if (strcmp (keyword->string, "ip") == 0 || strcmp (keyword->string, "host") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY order_inet (%s) ASC", column); } else if ((strcmp (type, "note") && strcmp (type, "override")) || (strcmp (keyword->string, "nvt") && strcmp (keyword->string, "name"))) { gchar *column; keyword_type_t column_type; column = columns_select_column_with_type (select_columns, where_columns, keyword->string, &column_type); assert (column); if (column_type == KEYWORD_TYPE_INTEGER) g_string_append_printf (order, " ORDER BY" " cast (%s AS bigint) ASC", column); else if (column_type == KEYWORD_TYPE_DOUBLE) g_string_append_printf (order, " ORDER BY" " cast (%s AS real) ASC", column); else g_string_append_printf (order, " ORDER BY lower (%s) ASC", column); } else /* Special case for notes text sorting. */ g_string_append_printf (order, " ORDER BY nvt ASC," " lower (%ss%s.text) ASC", type, trash ? "_trash" : ""); first_order = 0; } else /* To help the client split_filter restricts the filter to one * sorting term, preventing this from happening. */ g_string_append_printf (order, ", %s ASC", keyword->string); point++; continue; } else if (keyword->column && (strcasecmp (keyword->column, "sort-reverse") == 0)) { if (vector_find_filter (filter_columns, keyword->string) == 0) { point++; continue; } if (first_order) { if ((strcmp (type, "report") == 0) && (strcmp (keyword->string, "status") == 0)) g_string_append_printf (order, " ORDER BY" " (CASE WHEN (SELECT target = 0 FROM tasks" " WHERE tasks.id = task)" " THEN 'Container'" " ELSE run_status_name (scan_run_status)" " || (SELECT CAST (temp / 100 AS text)" " || CAST (temp / 10 AS text)" " || CAST (temp %% 10 as text)" " FROM (SELECT report_progress (id) AS temp)" " AS temp_sub)" " END)" " DESC"); else if ((strcmp (type, "task") == 0) && (strcmp (keyword->string, "status") == 0)) g_string_append_printf (order, " ORDER BY" " (CASE WHEN target = 0" " THEN 'Container'" " ELSE run_status_name (run_status)" " || (SELECT CAST (temp / 100 AS text)" " || CAST (temp / 10 AS text)" " || CAST (temp %% 10 as text)" " FROM (SELECT report_progress (id) AS temp" " FROM reports" " WHERE task = tasks.id" " ORDER BY date DESC LIMIT 1)" " AS temp_sub)" " END)" " DESC"); else if ((strcmp (type, "task") == 0) && (strcmp (keyword->string, "threat") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY order_threat (%s) DESC", column); } else if (strcmp (keyword->string, "severity") == 0 || strcmp (keyword->string, "original_severity") == 0 || strcmp (keyword->string, "cvss") == 0 || strcmp (keyword->string, "cvss_base") == 0 || strcmp (keyword->string, "max_cvss") == 0 || strcmp (keyword->string, "fp_per_host") == 0 || strcmp (keyword->string, "log_per_host") == 0 || strcmp (keyword->string, "low_per_host") == 0 || strcmp (keyword->string, "medium_per_host") == 0 || strcmp (keyword->string, "high_per_host") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); g_string_append_printf (order, " ORDER BY CASE CAST (%s AS text)" " WHEN '' THEN '-Infinity'::real" " ELSE coalesce(%s::real," " '-Infinity'::real)" " END DESC", column, column); } else if (strcmp (keyword->string, "roles") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY" " CASE WHEN %s %s 'Admin.*'" " THEN '0' || %s" " ELSE '1' || %s END DESC", column, sql_regexp_op (), column, column); } else if ((strcmp (keyword->string, "created") == 0) || (strcmp (keyword->string, "modified") == 0) || (strcmp (keyword->string, "published") == 0) || (strcmp (keyword->string, "qod") == 0) || (strcmp (keyword->string, "cves") == 0) || (strcmp (keyword->string, "high") == 0) || (strcmp (keyword->string, "medium") == 0) || (strcmp (keyword->string, "low") == 0) || (strcmp (keyword->string, "log") == 0) || (strcmp (keyword->string, "false_positive") == 0) || (strcmp (keyword->string, "hosts") == 0) || (strcmp (keyword->string, "result_hosts") == 0) || (strcmp (keyword->string, "results") == 0) || (strcmp (keyword->string, "latest_severity") == 0) || (strcmp (keyword->string, "highest_severity") == 0) || (strcmp (keyword->string, "average_severity") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY %s DESC", column); } else if ((strcmp (keyword->string, "ips") == 0) || (strcmp (keyword->string, "total") == 0) || (strcmp (keyword->string, "tcp") == 0) || (strcmp (keyword->string, "udp") == 0)) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY CAST (%s AS INTEGER) DESC", column); } else if (strcmp (keyword->string, "ip") == 0 || strcmp (keyword->string, "host") == 0) { gchar *column; column = columns_select_column (select_columns, where_columns, keyword->string); assert (column); g_string_append_printf (order, " ORDER BY order_inet (%s) DESC", column); } else if ((strcmp (type, "note") && strcmp (type, "override")) || (strcmp (keyword->string, "nvt") && strcmp (keyword->string, "name"))) { gchar *column; keyword_type_t column_type; column = columns_select_column_with_type (select_columns, where_columns, keyword->string, &column_type); assert (column); if (column_type == KEYWORD_TYPE_INTEGER) g_string_append_printf (order, " ORDER BY" " cast (%s AS bigint) DESC", column); else if (column_type == KEYWORD_TYPE_DOUBLE) g_string_append_printf (order, " ORDER BY" " cast (%s AS real) DESC", column); else g_string_append_printf (order, " ORDER BY lower (%s) DESC", column); } else /* Special case for notes text sorting. */ g_string_append_printf (order, " ORDER BY nvt DESC," " lower (%ss%s.text) DESC", type, trash ? "_trash" : ""); first_order = 0; } else /* To help the client split_filter restricts the filter to one * sorting term, preventing this from happening. */ g_string_append_printf (order, ", %s DESC", keyword->string); point++; continue; } else if (keyword->column && (strcasecmp (keyword->column, "first") == 0)) { if (first_return) { /* Subtract 1 to switch from 1 to 0 indexing. */ *first_return = atoi (keyword->string) - 1; if (*first_return < 0) *first_return = 0; } point++; continue; } else if (keyword->column && (strcasecmp (keyword->column, "rows") == 0)) { if (max_return) *max_return = atoi (keyword->string); point++; continue; } else if (keyword->column && (strcasecmp (keyword->column, "permission") == 0)) { if (permissions) array_add (*permissions, g_strdup (keyword->string)); point++; continue; } /* Add tag criteria to clause: tag name with optional value */ else if (keyword->column && (strcasecmp (keyword->column, "tag") == 0)) { quoted_keyword = NULL; filter_clause_append_tag (clause, keyword, type, first_keyword, last_was_and, last_was_not); first_keyword = 0; last_was_and = 0; last_was_not = 0; point++; continue; } /* Add criteria for tag_id to clause */ else if (keyword->column && (strcasecmp (keyword->column, "tag_id") == 0)) { quoted_keyword = NULL; filter_clause_append_tag_id (clause, keyword, type, first_keyword, last_was_and, last_was_not); first_keyword = 0; last_was_and = 0; last_was_not = 0; point++; continue; } /* Add SQL to the clause for each column name. */ quoted_keyword = NULL; if (keyword->relation == KEYWORD_RELATION_COLUMN_EQUAL) { if (vector_find_filter (filter_columns, keyword->column) == 0) { last_was_and = 0; last_was_not = 0; point++; continue; } if (keyword->column && (strlen (keyword->column) > 3) && (strcmp (keyword->column + strlen (keyword->column) - 3, "_id") == 0) && strcasecmp (keyword->column, "nvt_id") /* Tickets have a custom result_id column. */ && strcasecmp (keyword->column, "result_id")) { gchar *type_term; type_term = g_strndup (keyword->column, strlen (keyword->column) - 3); if (valid_type (type_term) == 0) { g_free (type_term); last_was_and = 0; last_was_not = 0; point++; continue; } quoted_keyword = sql_quote (keyword->string); if (strcmp (quoted_keyword, "")) g_string_append_printf (clause, "%s(((SELECT id FROM %ss" " WHERE %ss.uuid = '%s')" " = %ss.%s" " OR %ss.%s IS NULL" " OR %ss.%s = 0)", get_join (first_keyword, last_was_and, last_was_not), type_term, type_term, quoted_keyword, type, type_term, type, type_term, type, type_term); else g_string_append_printf (clause, "%s((%ss.%s IS NULL" " OR %ss.%s = 0)", get_join (first_keyword, last_was_and, last_was_not), type, type_term, type, type_term); g_free (type_term); } else if (keyword->column && strcmp (keyword->column, "owner")) { gchar *column; keyword_type_t column_type; quoted_keyword = sql_quote (keyword->string); column = columns_select_column_with_type (select_columns, where_columns, keyword->column, &column_type); assert (column); if (keyword->type == KEYWORD_TYPE_INTEGER && (column_type == KEYWORD_TYPE_INTEGER || column_type == KEYWORD_TYPE_DOUBLE)) g_string_append_printf (clause, "%s(CAST (%s AS NUMERIC) = %i", get_join (first_keyword, last_was_and, last_was_not), column, keyword->integer_value); else if (keyword->type == KEYWORD_TYPE_DOUBLE && (column_type == KEYWORD_TYPE_DOUBLE || column_type == KEYWORD_TYPE_INTEGER)) g_string_append_printf (clause, "%s(CAST (%s AS REAL)" " = CAST (%f AS REAL)", get_join (first_keyword, last_was_and, last_was_not), column, keyword->double_value); else if (strcmp (quoted_keyword, "")) g_string_append_printf (clause, "%s(CAST (%s AS TEXT) = '%s'", get_join (first_keyword, last_was_and, last_was_not), column, quoted_keyword); else g_string_append_printf (clause, "%s((%s IS NULL OR CAST (%s AS TEXT) = '%s')", get_join (first_keyword, last_was_and, last_was_not), column, column, quoted_keyword); } else { /* Skip term. Owner filtering is done via where_owned. */ skip = 1; if (owner_filter && (*owner_filter == NULL)) *owner_filter = g_strdup (keyword->string); } } else if (keyword->relation == KEYWORD_RELATION_COLUMN_APPROX) { gchar *column; if (vector_find_filter (filter_columns, keyword->column) == 0) { last_was_and = 0; last_was_not = 0; point++; continue; } quoted_keyword = sql_quote (keyword->string); column = columns_select_column (select_columns, where_columns, keyword->column); assert (column); g_string_append_printf (clause, "%s(CAST (%s AS TEXT) %s '%%%%%s%%%%'", get_join (first_keyword, last_was_and, last_was_not), column, sql_ilike_op (), quoted_keyword); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_ABOVE) { gchar *column; keyword_type_t column_type; if (vector_find_filter (filter_columns, keyword->column) == 0) { last_was_and = 0; last_was_not = 0; point++; continue; } quoted_keyword = sql_quote (keyword->string); column = columns_select_column_with_type (select_columns, where_columns, keyword->column, &column_type); assert (column); if (keyword->type == KEYWORD_TYPE_INTEGER && (column_type == KEYWORD_TYPE_INTEGER || column_type == KEYWORD_TYPE_DOUBLE)) g_string_append_printf (clause, "%s(CAST (%s AS NUMERIC) > %i", get_join (first_keyword, last_was_and, last_was_not), column, keyword->integer_value); else if (keyword->type == KEYWORD_TYPE_DOUBLE && (column_type == KEYWORD_TYPE_DOUBLE || column_type == KEYWORD_TYPE_INTEGER)) g_string_append_printf (clause, "%s(CAST (%s AS REAL)" " > CAST (%f AS REAL)", get_join (first_keyword, last_was_and, last_was_not), column, keyword->double_value); else g_string_append_printf (clause, "%s(CAST (%s AS TEXT) > '%s'", get_join (first_keyword, last_was_and, last_was_not), column, quoted_keyword); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_BELOW) { gchar *column; keyword_type_t column_type; if (vector_find_filter (filter_columns, keyword->column) == 0) { last_was_and = 0; last_was_not = 0; point++; continue; } quoted_keyword = sql_quote (keyword->string); column = columns_select_column_with_type (select_columns, where_columns, keyword->column, &column_type); assert (column); if (keyword->type == KEYWORD_TYPE_INTEGER && (column_type == KEYWORD_TYPE_INTEGER || column_type == KEYWORD_TYPE_DOUBLE)) g_string_append_printf (clause, "%s(CAST (%s AS NUMERIC) < %i", get_join (first_keyword, last_was_and, last_was_not), column, keyword->integer_value); else if (keyword->type == KEYWORD_TYPE_DOUBLE && (column_type == KEYWORD_TYPE_DOUBLE || column_type == KEYWORD_TYPE_INTEGER)) g_string_append_printf (clause, "%s(CAST (%s AS REAL)" " < CAST (%f AS REAL)", get_join (first_keyword, last_was_and, last_was_not), column, keyword->double_value); else g_string_append_printf (clause, "%s(CAST (%s AS TEXT) < '%s'", get_join (first_keyword, last_was_and, last_was_not), column, quoted_keyword); } else if (keyword->relation == KEYWORD_RELATION_COLUMN_REGEXP) { gchar *column; if (vector_find_filter (filter_columns, keyword->column) == 0) { last_was_and = 0; last_was_not = 0; point++; continue; } quoted_keyword = sql_quote (keyword->string); column = columns_select_column (select_columns, where_columns, keyword->column); assert (column); g_string_append_printf (clause, "%s(CAST (%s AS TEXT) %s '%s'", get_join (first_keyword, last_was_and, last_was_not), column, sql_regexp_op (), quoted_keyword); } else if (keyword->equal) { const char *filter_column; /* Keyword like "=example". */ g_string_append_printf (clause, "%s(", (first_keyword ? "" : (last_was_and ? " AND " : " OR "))); quoted_keyword = sql_quote (keyword->string); if (last_was_not) for (index = 0; (filter_column = filter_columns[index]) != NULL; index++) { gchar *select_column; keyword_type_t column_type; select_column = columns_select_column_with_type (select_columns, where_columns, filter_column, &column_type); assert (select_column); if (keyword->type == KEYWORD_TYPE_INTEGER && (column_type == KEYWORD_TYPE_INTEGER || column_type == KEYWORD_TYPE_DOUBLE)) g_string_append_printf (clause, "%s" "(%s IS NULL" " OR CAST (%s AS NUMERIC)" " != %i)", (index ? " AND " : ""), select_column, select_column, keyword->integer_value); else if (keyword->type == KEYWORD_TYPE_DOUBLE && (column_type == KEYWORD_TYPE_DOUBLE || column_type == KEYWORD_TYPE_INTEGER)) g_string_append_printf (clause, "%s" "(%s IS NULL" " OR CAST (%s AS REAL)" " != CAST (%f AS REAL))", (index ? " AND " : ""), select_column, select_column, keyword->double_value); else g_string_append_printf (clause, "%s" "(%s IS NULL" " OR CAST (%s AS TEXT)" " != '%s')", (index ? " AND " : ""), select_column, select_column, quoted_keyword); } else for (index = 0; (filter_column = filter_columns[index]) != NULL; index++) { gchar *select_column; keyword_type_t column_type; select_column = columns_select_column_with_type (select_columns, where_columns, filter_column, &column_type); assert (select_column); if (keyword->type == KEYWORD_TYPE_INTEGER && (column_type == KEYWORD_TYPE_INTEGER || column_type == KEYWORD_TYPE_DOUBLE)) g_string_append_printf (clause, "%sCAST (%s AS NUMERIC)" " = %i", (index ? " OR " : ""), select_column, keyword->integer_value); else if (keyword->type == KEYWORD_TYPE_DOUBLE && (column_type == KEYWORD_TYPE_DOUBLE || column_type == KEYWORD_TYPE_INTEGER)) g_string_append_printf (clause, "%sCAST (%s AS REAL)" " = CAST (%f AS REAL)", (index ? " OR " : ""), select_column, keyword->double_value); else g_string_append_printf (clause, "%sCAST (%s AS TEXT)" " = '%s'", (index ? " OR " : ""), select_column, quoted_keyword); } } else { const char *filter_column; g_string_append_printf (clause, "%s(", (first_keyword ? "" : (last_was_and ? " AND " : " OR "))); quoted_keyword = sql_quote (keyword->string); if (last_was_not) for (index = 0; (filter_column = filter_columns[index]) != NULL; index++) { gchar *select_column; keyword_type_t column_type; int column_type_matches = 0; select_column = columns_select_column_with_type (select_columns, where_columns, filter_column, &column_type); if (column_type != KEYWORD_TYPE_INTEGER && column_type != KEYWORD_TYPE_DOUBLE) column_type_matches = 1; if (keyword_applies_to_column (keyword, filter_column) && select_column && column_type_matches) { if (last_was_re) g_string_append_printf (clause, "%s" "(%s IS NULL" " OR NOT (CAST (%s AS TEXT)" " %s '%s'))", (index ? " AND " : ""), select_column, select_column, sql_regexp_op (), quoted_keyword); else g_string_append_printf (clause, "%s" "(%s IS NULL" " OR CAST (%s AS TEXT)" " NOT %s '%%%s%%')", (index ? " AND " : ""), select_column, select_column, sql_ilike_op (), quoted_keyword); } else g_string_append_printf (clause, "%s t ()", (index ? " AND " : "")); } else for (index = 0; (filter_column = filter_columns[index]) != NULL; index++) { gchar *select_column; keyword_type_t column_type; int column_type_matches = 0; select_column = columns_select_column_with_type (select_columns, where_columns, filter_column, &column_type); if (column_type != KEYWORD_TYPE_INTEGER && column_type != KEYWORD_TYPE_DOUBLE) column_type_matches = 1; if (keyword_applies_to_column (keyword, filter_column) && select_column && column_type_matches) g_string_append_printf (clause, "%sCAST (%s AS TEXT)" " %s '%s%s%s'", (index ? " OR " : ""), select_column, last_was_re ? sql_regexp_op () : sql_ilike_op (), last_was_re ? "" : "%%", quoted_keyword, last_was_re ? "" : "%%"); else g_string_append_printf (clause, "%snot t ()", (index ? " OR " : "")); } } if (skip == 0) { g_string_append (clause, ")"); first_keyword = 0; last_was_and = 0; last_was_not = 0; last_was_re = 0; } g_free (quoted_keyword); point++; } filter_free (split); if (order_return) *order_return = g_string_free (order, FALSE); else g_string_free (order, TRUE); if (max_return) { if (*max_return == -2) setting_value_int (SETTING_UUID_ROWS_PER_PAGE, max_return); else if (*max_return < 1) *max_return = -1; *max_return = manage_max_rows (*max_return); } if (strlen (clause->str)) return g_string_free (clause, FALSE); g_string_free (clause, TRUE); return NULL; } /* Resources. */ /** * @brief Check whether a resource type name is valid. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ int valid_type (const char* type) { return (strcasecmp (type, "alert") == 0) || (strcasecmp (type, "asset") == 0) || (strcasecmp (type, "config") == 0) || (strcasecmp (type, "credential") == 0) || (strcasecmp (type, "filter") == 0) || (strcasecmp (type, "group") == 0) || (strcasecmp (type, "host") == 0) || (strcasecmp (type, "info") == 0) || (strcasecmp (type, "note") == 0) || (strcasecmp (type, "os") == 0) || (strcasecmp (type, "override") == 0) || (strcasecmp (type, "permission") == 0) || (strcasecmp (type, "port_list") == 0) || (strcasecmp (type, "report") == 0) || (strcasecmp (type, "report_format") == 0) || (strcasecmp (type, "result") == 0) || (strcasecmp (type, "role") == 0) || (strcasecmp (type, "scanner") == 0) || (strcasecmp (type, "schedule") == 0) || (strcasecmp (type, "tag") == 0) || (strcasecmp (type, "target") == 0) || (strcasecmp (type, "task") == 0) || (strcasecmp (type, "ticket") == 0) || (strcasecmp (type, "tls_certificate") == 0) || (strcasecmp (type, "user") == 0) || (strcasecmp (type, "vuln") == 0); } /** * @brief Return DB name of type. * * @param[in] type Database or pretty name. * * @return Database name of type if possible, else NULL. */ static const char * type_db_name (const char* type) { if (type == NULL) return NULL; if (valid_type (type)) return type; if (strcasecmp (type, "Alert") == 0) return "alert"; if (strcasecmp (type, "Asset") == 0) return "asset"; if (strcasecmp (type, "Config") == 0) return "config"; if (strcasecmp (type, "Credential") == 0) return "credential"; if (strcasecmp (type, "Filter") == 0) return "filter"; if (strcasecmp (type, "Note") == 0) return "note"; if (strcasecmp (type, "Override") == 0) return "override"; if (strcasecmp (type, "Permission") == 0) return "permission"; if (strcasecmp (type, "Port List") == 0) return "port_list"; if (strcasecmp (type, "Report") == 0) return "report"; if (strcasecmp (type, "Report Format") == 0) return "report_format"; if (strcasecmp (type, "Result") == 0) return "result"; if (strcasecmp (type, "Role") == 0) return "role"; if (strcasecmp (type, "Scanner") == 0) return "scanner"; if (strcasecmp (type, "Schedule") == 0) return "schedule"; if (strcasecmp (type, "Tag") == 0) return "tag"; if (strcasecmp (type, "Target") == 0) return "target"; if (strcasecmp (type, "Task") == 0) return "task"; if (strcasecmp (type, "Ticket") == 0) return "ticket"; if (strcasecmp (type, "TLS Certificate") == 0) return "tls_certificate"; if (strcasecmp (type, "SecInfo") == 0) return "info"; return NULL; } /** * @brief Check whether a resource type is an asset subtype. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_is_asset_subtype (const char *type) { return (strcasecmp (type, "host") && strcasecmp (type, "os")) == 0; } /** * @brief Check whether a resource type is an info subtype. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_is_info_subtype (const char *type) { return (strcasecmp (type, "nvt") && strcasecmp (type, "cve") && strcasecmp (type, "cpe") && strcasecmp (type, "ovaldef") && strcasecmp (type, "cert_bund_adv") && strcasecmp (type, "dfn_cert_adv")) == 0; } /** * @brief Check whether a type has a name and comment. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_named (const char *type) { return strcasecmp (type, "note") && strcasecmp (type, "override"); } /** * @brief Check whether a type must have globally unique names. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_globally_unique (const char *type) { if (strcasecmp (type, "user") == 0) return 1; else return 0; } /** * @brief Check whether a type has a comment. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_has_comment (const char *type) { return strcasecmp (type, "report_format"); } /** * @brief Check whether a resource type uses the trashcan. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_has_trash (const char *type) { return strcasecmp (type, "report") && strcasecmp (type, "result") && strcasecmp (type, "info") && type_is_info_subtype (type) == 0 && strcasecmp (type, "vuln") && strcasecmp (type, "user") && strcasecmp (type, "tls_certificate"); } /** * @brief Check whether a resource type has an owner. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_owned (const char* type) { return strcasecmp (type, "info") && type_is_info_subtype (type) == 0 && strcasecmp (type, "vuln"); } /** * @brief Check whether the trash is in the real table. * * @param[in] type Type of resource. * * @return 1 yes, 0 no. */ static int type_trash_in_table (const char *type) { return strcasecmp (type, "task") == 0; } /* TODO Only used by find_scanner, find_permission and check_permission_args. */ /** * @brief Find a resource given a UUID. * * This only looks for resources owned (or effectively owned) by the current user. * So no shared resources and no globals. * * @param[in] type Type of resource. * @param[in] uuid UUID of resource. * @param[out] resource Resource return, 0 if successfully failed to find resource. * * @return FALSE on success (including if failed to find resource), TRUE on error. */ gboolean find_resource (const char* type, const char* uuid, resource_t* resource) { gchar *quoted_uuid; quoted_uuid = sql_quote (uuid); if (acl_user_owns_uuid (type, quoted_uuid, 0) == 0) { g_free (quoted_uuid); *resource = 0; return FALSE; } // TODO should really check type switch (sql_int64 (resource, "SELECT id FROM %ss WHERE uuid = '%s'%s;", type, quoted_uuid, strcmp (type, "task") ? "" : " AND hidden < 2")) { case 0: break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return TRUE; break; } g_free (quoted_uuid); return FALSE; } /** * @brief Find a resource given a UUID and a permission. * * @param[in] type Type of resource. * @param[in] uuid UUID of resource. * @param[out] resource Resource return, 0 if successfully failed to find * resource. * @param[in] permission Permission. * @param[in] trash Whether resource is in trashcan. * * @return FALSE on success (including if failed to find resource), TRUE on * error. */ gboolean find_resource_with_permission (const char* type, const char* uuid, resource_t* resource, const char *permission, int trash) { gchar *quoted_uuid; if (uuid == NULL) return TRUE; if ((type == NULL) || (valid_db_resource_type (type) == 0)) return TRUE; quoted_uuid = sql_quote (uuid); if (acl_user_has_access_uuid (type, quoted_uuid, permission, trash) == 0) { g_free (quoted_uuid); *resource = 0; return FALSE; } switch (sql_int64 (resource, "SELECT id FROM %ss%s WHERE uuid = '%s'%s%s;", type, (trash && strcmp (type, "task") && strcmp (type, "report")) ? "_trash" : "", quoted_uuid, strcmp (type, "task") ? "" : (trash ? " AND hidden = 2" : " AND hidden < 2"), strcmp (type, "report") ? "" : (trash ? " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 2" : " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 0"))) { case 0: break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return TRUE; break; } g_free (quoted_uuid); return FALSE; } /** * @brief Find a resource given a name. * * @param[in] type Type of resource. * @param[in] name A resource name. * @param[out] resource Resource return, 0 if successfully failed to find * resource. * * @return FALSE on success (including if failed to find resource), TRUE on * error. */ static gboolean find_resource_by_name (const char* type, const char* name, resource_t *resource) { gchar *quoted_name; quoted_name = sql_quote (name); // TODO should really check type switch (sql_int64 (resource, "SELECT id FROM %ss WHERE name = '%s'" " ORDER BY id DESC;", type, quoted_name)) { case 0: break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_name); return TRUE; break; } g_free (quoted_name); return FALSE; } /** * @brief Find a resource given a UUID and a permission. * * @param[in] type Type of resource. * @param[in] name Name of resource. * @param[out] resource Resource return, 0 if successfully failed to find * resource. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find resource), TRUE on * error. */ static gboolean find_resource_by_name_with_permission (const char *type, const char *name, resource_t *resource, const char *permission) { gchar *quoted_name; assert (strcmp (type, "task")); if (name == NULL) return TRUE; quoted_name = sql_quote (name); // TODO should really check type switch (sql_int64 (resource, "SELECT id FROM %ss WHERE name = '%s'" " ORDER BY id DESC;", type, quoted_name)) { case 0: { gchar *uuid; uuid = sql_string ("SELECT uuid FROM %ss WHERE id = %llu;", type, *resource); if (acl_user_has_access_uuid (type, uuid, permission, 0) == 0) { g_free (uuid); g_free (quoted_name); *resource = 0; return FALSE; } g_free (uuid); } break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_name); return TRUE; break; } g_free (quoted_name); return FALSE; } /** * @brief Create a resource from an existing resource. * * @param[in] type Type of resource. * @param[in] name Name of new resource. NULL to copy from existing. * @param[in] comment Comment on new resource. NULL to copy from existing. * @param[in] resource_id UUID of existing resource. * @param[in] columns Extra columns in resource. * @param[in] make_name_unique When name NULL, whether to make existing name * unique. * @param[out] new_resource Address for new resource, or NULL. * @param[out] old_resource Address for existing resource, or NULL. * * @return 0 success, 1 resource exists already, 2 failed to find existing * resource, 99 permission denied, -1 error. */ int copy_resource_lock (const char *type, const char *name, const char *comment, const char *resource_id, const char *columns, int make_name_unique, resource_t* new_resource, resource_t *old_resource) { gchar *quoted_name, *quoted_uuid, *uniquify, *command; int named, globally_unique; user_t owner; resource_t resource; resource_t new; int ret = -1; if (resource_id == NULL) return -1; command = g_strdup_printf ("create_%s", type); if (acl_user_may (command) == 0) { g_free (command); return 99; } g_free (command); command = g_strdup_printf ("get_%ss", type); if (find_resource_with_permission (type, resource_id, &resource, command, 0)) { g_free (command); return -1; } g_free (command); if (resource == 0) return 2; if (find_user_by_name (current_credentials.username, &owner) || owner == 0) { return -1; } if (strcmp (type, "permission") == 0) { resource_t perm_resource; perm_resource = permission_resource (resource); if ((perm_resource == 0) && (acl_user_can_everything (current_credentials.uuid) == 0)) /* Only admins can copy permissions that apply to whole commands. */ return 99; } named = type_named (type); globally_unique = type_globally_unique (type); if (named && name && *name && resource_with_name_exists (name, type, 0)) return 1; if ((strcmp (type, "tls_certificate") == 0) && user_has_tls_certificate (resource, owner)) return 1; if (name && *name) quoted_name = sql_quote (name); else quoted_name = NULL; quoted_uuid = sql_quote (resource_id); /* Copy the existing resource. */ if (globally_unique && make_name_unique) uniquify = g_strdup_printf ("uniquify ('%s', name, NULL, '%cClone')", type, strcmp (type, "user") ? ' ' : '_'); else if (make_name_unique) uniquify = g_strdup_printf ("uniquify ('%s', name, %llu, ' Clone')", type, owner); else uniquify = g_strdup ("name"); if (named && comment && strlen (comment)) { gchar *quoted_comment; quoted_comment = sql_nquote (comment, strlen (comment)); ret = sql_error ("INSERT INTO %ss" " (uuid, owner, name, comment," " creation_time, modification_time%s%s)" " SELECT make_uuid ()," " (SELECT id FROM users" " where users.uuid = '%s')," " %s%s%s, '%s', m_now (), m_now ()%s%s" " FROM %ss WHERE uuid = '%s';", type, columns ? ", " : "", columns ? columns : "", current_credentials.uuid, quoted_name ? "'" : "", quoted_name ? quoted_name : uniquify, quoted_name ? "'" : "", quoted_comment, columns ? ", " : "", columns ? columns : "", type, quoted_uuid); g_free (quoted_comment); } else if (named) ret = sql_error ("INSERT INTO %ss" " (uuid, owner, name%s," " creation_time, modification_time%s%s)" " SELECT make_uuid ()," " (SELECT id FROM users where users.uuid = '%s')," " %s%s%s%s, m_now (), m_now ()%s%s" " FROM %ss WHERE uuid = '%s';", type, type_has_comment (type) ? ", comment" : "", columns ? ", " : "", columns ? columns : "", current_credentials.uuid, quoted_name ? "'" : "", quoted_name ? quoted_name : uniquify, quoted_name ? "'" : "", type_has_comment (type) ? ", comment" : "", columns ? ", " : "", columns ? columns : "", type, quoted_uuid); else ret = sql_error ("INSERT INTO %ss" " (uuid, owner, creation_time, modification_time%s%s)" " SELECT make_uuid ()," " (SELECT id FROM users where users.uuid = '%s')," " m_now (), m_now ()%s%s" " FROM %ss WHERE uuid = '%s';", type, columns ? ", " : "", columns ? columns : "", current_credentials.uuid, columns ? ", " : "", columns ? columns : "", type, quoted_uuid); if (ret == 3) { g_free (quoted_uuid); g_free (quoted_name); g_free (uniquify); return 1; } else if (ret) { g_free (quoted_uuid); g_free (quoted_name); g_free (uniquify); return -1; } new = sql_last_insert_id (); /* Copy attached tags */ sql ("INSERT INTO tag_resources" " (tag, resource_type, resource, resource_uuid, resource_location)" " SELECT tag, resource_type, %llu," " (SELECT uuid FROM %ss WHERE id = %llu)," " resource_location" " FROM tag_resources" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) ";", new, type, new, type, resource); if (new_resource) *new_resource = new; if (old_resource) *old_resource = resource; g_free (quoted_uuid); g_free (quoted_name); g_free (uniquify); if (sql_last_insert_id () == 0) return -1; return 0; } /** * @brief Create a resource from an existing resource. * * @param[in] type Type of resource. * @param[in] name Name of new resource. NULL to copy from existing. * @param[in] comment Comment on new resource. NULL to copy from existing. * @param[in] resource_id UUID of existing resource. * @param[in] columns Extra columns in resource. * @param[in] make_name_unique When name NULL, whether to make existing name * unique. * @param[out] new_resource New resource. * @param[out] old_resource Address for existing resource, or NULL. * * @return 0 success, 1 resource exists already, 2 failed to find existing * resource, 99 permission denied, -1 error. */ int copy_resource (const char *type, const char *name, const char *comment, const char *resource_id, const char *columns, int make_name_unique, resource_t* new_resource, resource_t *old_resource) { int ret; assert (current_credentials.uuid); sql_begin_immediate (); ret = copy_resource_lock (type, name, comment, resource_id, columns, make_name_unique, new_resource, old_resource); if (ret) sql_rollback (); else sql_commit (); return ret; } /** * @brief Get whether a resource exists. * * @param[in] type Type. * @param[in] resource Resource. * @param[in] location Location. * * @return 1 yes, 0 no, -1 error in type. */ int resource_exists (const char *type, resource_t resource, int location) { if (valid_db_resource_type (type) == 0) return -1; if (location == LOCATION_TABLE) return sql_int ("SELECT EXISTS (SELECT id FROM %ss WHERE id = %llu);", type, resource); return sql_int ("SELECT EXISTS (SELECT id FROM %ss%s WHERE id = %llu);", type, strcmp (type, "task") ? "_trash" : "", resource); } /** * @brief Get the name of a resource. * * @param[in] type Type. * @param[in] uuid UUID. * @param[in] location Location. * @param[out] name Return for freshly allocated name. * * @return 0 success, 1 error in type. */ int resource_name (const char *type, const char *uuid, int location, char **name) { if (valid_db_resource_type (type) == 0) return 1; if (strcasecmp (type, "note") == 0) *name = sql_string ("SELECT 'Note for: '" " || (SELECT name" " FROM nvts" " WHERE nvts.uuid = notes%s.nvt)" " FROM notes%s" " WHERE uuid = '%s';", location == LOCATION_TABLE ? "" : "_trash", location == LOCATION_TABLE ? "" : "_trash", uuid); else if (strcasecmp (type, "override") == 0) *name = sql_string ("SELECT 'Override for: '" " || (SELECT name" " FROM nvts" " WHERE nvts.uuid = overrides%s.nvt)" " FROM overrides%s" " WHERE uuid = '%s';", location == LOCATION_TABLE ? "" : "_trash", location == LOCATION_TABLE ? "" : "_trash", uuid); else if (strcasecmp (type, "report") == 0) *name = sql_string ("SELECT (SELECT name FROM tasks WHERE id = task)" " || ' - '" " || (SELECT" " CASE (SELECT end_time FROM tasks" " WHERE id = task)" " WHEN 0 THEN 'N/A'" " ELSE (SELECT iso_time (end_time)" " FROM tasks WHERE id = task)" " END)" " FROM reports" " WHERE uuid = '%s';", uuid); else if (strcasecmp (type, "result") == 0) *name = sql_string ("SELECT (SELECT name FROM tasks WHERE id = task)" " || ' - '" " || (SELECT name FROM nvts WHERE oid = nvt)" " || ' - '" " || (SELECT" " CASE (SELECT end_time FROM tasks" " WHERE id = task)" " WHEN 0 THEN 'N/A'" " ELSE (SELECT iso_time (end_time)" " FROM tasks WHERE id = task)" " END)" " FROM results" " WHERE uuid = '%s';", uuid); else if (location == LOCATION_TABLE) *name = sql_string ("SELECT name" " FROM %ss" " WHERE uuid = '%s';", type, uuid); else if (type_has_trash (type)) *name = sql_string ("SELECT name" " FROM %ss%s" " WHERE uuid = '%s';", type, strcmp (type, "task") ? "_trash" : "", uuid); else *name = NULL; return 0; } /** * @brief Get the name of a resource. * * @param[in] type Type. * @param[in] uuid UUID. * @param[out] name Return for freshly allocated name. * * @return 0 success, 1 error in type. */ int manage_resource_name (const char *type, const char *uuid, char **name) { return resource_name (type, uuid, LOCATION_TABLE, name); } /** * @brief Get the name of a trashcan resource. * * @param[in] type Type. * @param[in] uuid UUID. * @param[out] name Return for freshly allocated name. * * @return 0 success, 1 error in type. */ int manage_trash_resource_name (const char *type, const char *uuid, char **name) { return resource_name (type, uuid, LOCATION_TRASH, name); } /** * @brief Get the UUID of a resource. * * @param[in] type Type. * @param[in] resource Resource. * * @return Freshly allocated UUID on success, else NULL. */ gchar * resource_uuid (const gchar *type, resource_t resource) { assert (valid_db_resource_type (type)); return sql_string ("SELECT uuid FROM %ss WHERE id = %llu;", type, resource); } /** * @brief Initialise a GET iterator, including observed resources. * * This version includes the extra_with arg. * * @param[in] iterator Iterator. * @param[in] type Type of resource. * @param[in] get GET data. * @param[in] select_columns Columns for SQL. * @param[in] trash_select_columns Columns for SQL trash case. * @param[in] where_columns WHERE columns. These are columns that * can be used for filtering and searching, * but are not accessed (so column has no * iterator access function). * @param[in] trash_where_columns WHERE columns for trashcan. * @param[in] filter_columns Columns for filter. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] extra_tables Extra tables to join in FROM clause. * @param[in] extra_where Extra WHERE clauses. Skipped for single * resource. * @param[in] extra_where_single Extra WHERE clauses. Used for single * resource. * @param[in] owned Only get items owned by the current user. * @param[in] ignore_id Whether to ignore id (e.g. for report results). * @param[in] extra_order Extra ORDER clauses. * @param[in] extra_with Extra WITH clauses. * @param[in] acl_with_optional Whether default permission WITH clauses are * optional. * @param[in] assume_permitted Whether to skip permission checks. * * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1 * error. */ static int init_get_iterator2_with (iterator_t* iterator, const char *type, const get_data_t *get, column_t *select_columns, column_t *trash_select_columns, column_t *where_columns, column_t *trash_where_columns, const char **filter_columns, int distinct, const char *extra_tables, const char *extra_where, const char *extra_where_single, int owned, int ignore_id, const char *extra_order, const char *extra_with, int acl_with_optional, int assume_permitted) { int first, max; gchar *clause, *order, *filter, *owned_clause, *with_clause; array_t *permissions; resource_t resource = 0; gchar *owner_filter; gchar *columns; assert (get); if (select_columns == NULL) { assert (0); return -1; } if (ignore_id) { resource = 0; } else if (get->id && (owned == 0 || (current_credentials.uuid == NULL))) { gchar *quoted_uuid = sql_quote (get->id); switch (sql_int64 (&resource, "SELECT id FROM %ss WHERE uuid = '%s';", type, quoted_uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ g_free (quoted_uuid); return 1; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return -1; break; } g_free (quoted_uuid); } else if (get->id && owned) { /* For now assume that the permission is "get_". Callers wishing * to iterate over a single resource with other permissions can use * uuid= in the filter (instead of passing get->id). */ const char* permission; /* Special case: "get_assets" subtypes */ if (type_is_asset_subtype (type)) permission = "get_assets"; else permission = NULL; if (find_resource_with_permission (type, get->id, &resource, permission, get->trash)) return -1; if (resource == 0) return 1; } if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { if (get->filter_replacement) /* Replace the filter term with one given by the caller. This is * used by GET_REPORTS to use the default filter with any task (when * given the special value of -3 in filt_id). */ filter = g_strdup (get->filter_replacement); else filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; clause = filter_clause (type, filter ? filter : get->filter, filter_columns, (get->trash && trash_select_columns) ? trash_select_columns : select_columns, (get->trash && trash_where_columns) ? trash_where_columns : where_columns, get->trash, &order, &first, &max, &permissions, &owner_filter); g_free (filter); with_clause = NULL; if (resource || assume_permitted) /* Ownership test of single resources is done above by find function * but acl_where_owned has to be called to generate WITH clause * in case subqueries depend on it. */ owned_clause = acl_where_owned (type, get, 0, owner_filter, resource, permissions, acl_with_optional, &with_clause); else owned_clause = acl_where_owned (type, get, owned, owner_filter, resource, permissions, acl_with_optional, &with_clause); if (extra_with) { if (with_clause) { gchar *old_with; old_with = with_clause; with_clause = g_strdup_printf ("%s, %s", old_with, extra_with); g_free (old_with); } else with_clause = g_strdup_printf ("WITH %s", extra_with); } g_free (owner_filter); array_free (permissions); if (get->trash && trash_select_columns) columns = columns_build_select (trash_select_columns); else columns = columns_build_select (select_columns); if (get->ignore_pagination && ((strcmp (type, "host") == 0) || (strcmp (type, "os") == 0) || (strcmp (type, "task") == 0) || (strcmp (type, "report") == 0) || (strcmp (type, "result") == 0))) { first = 0; max = -1; } if (strlen (order) == 0) { g_free (order); order = NULL; } if (resource && get->trash) init_iterator (iterator, "%sSELECT %s" " FROM %ss%s %s" " WHERE %ss.id = %llu" " AND %s%s" "%s%s;", with_clause ? with_clause : "", columns, type, type_trash_in_table (type) ? "" : "_trash", extra_tables ? extra_tables : "", type, resource, owned_clause, extra_where_single ? extra_where_single : "", order ? order : "", order ? (extra_order ? extra_order : "") : ""); else if (get->trash) init_iterator (iterator, "%sSELECT %s" " FROM %ss%s %s" " WHERE" "%s" "%s" "%s%s;", with_clause ? with_clause : "", columns, type, type_trash_in_table (type) ? "" : "_trash", extra_tables ? extra_tables : "", owned_clause, extra_where ? extra_where : "", order ? order : "", order ? (extra_order ? extra_order : "") : ""); else if (resource) init_iterator (iterator, "%sSELECT %s" " FROM %ss %s" " WHERE %ss.id = %llu" " AND %s%s" "%s%s;", with_clause ? with_clause : "", columns, type, extra_tables ? extra_tables : "", type, resource, owned_clause, extra_where_single ? extra_where_single : "", order ? order : "", order ? (extra_order ? extra_order : "") : ""); else init_iterator (iterator, "%s%sSELECT %s" " FROM %ss %s" " WHERE" " %s%s%s%s%s%s%s" " LIMIT %s OFFSET %i%s;", with_clause ? with_clause : "", distinct ? "SELECT DISTINCT * FROM (" : "", columns, type, extra_tables ? extra_tables : "", owned_clause, clause ? " AND (" : "", clause ? clause : "", clause ? ")" : "", extra_where ? extra_where : "", order ? order : "", order ? (extra_order ? extra_order : "") : "", sql_select_limit (max), first, distinct ? ") AS subquery_for_distinct" : ""); g_free (columns); g_free (with_clause); g_free (owned_clause); g_free (order); g_free (clause); return 0; } /** * @brief Initialise a GET iterator, including observed resources. * * @param[in] iterator Iterator. * @param[in] type Type of resource. * @param[in] get GET data. * @param[in] select_columns Columns for SQL. * @param[in] trash_select_columns Columns for SQL trash case. * @param[in] where_columns WHERE columns. These are columns that * can be used for filtering and searching, * but are not accessed (so column has no * iterator access function). * @param[in] trash_where_columns WHERE columns for trashcan. * @param[in] filter_columns Columns for filter. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] extra_tables Extra tables to join in FROM clause. * @param[in] extra_where Extra WHERE clauses. Skipped for single * resource. * @param[in] extra_where_single Extra WHERE clauses. Used for single * resource. * @param[in] owned Only get items owned by the current user. * @param[in] ignore_id Whether to ignore id (e.g. for report results). * @param[in] extra_order Extra ORDER clauses. * * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1 * error. */ static int init_get_iterator2 (iterator_t* iterator, const char *type, const get_data_t *get, column_t *select_columns, column_t *trash_select_columns, column_t *where_columns, column_t *trash_where_columns, const char **filter_columns, int distinct, const char *extra_tables, const char *extra_where, const char *extra_where_single, int owned, int ignore_id, const char *extra_order) { return init_get_iterator2_with (iterator, type, get, select_columns, trash_select_columns, where_columns, trash_where_columns, filter_columns, distinct, extra_tables, extra_where, extra_where_single, owned, ignore_id, extra_order, NULL, 0, 0); } /** * @brief Initialise a GET iterator, including observed resources. * * @param[in] iterator Iterator. * @param[in] type Type of resource. * @param[in] get GET data. * @param[in] select_columns Columns for SQL. * @param[in] trash_select_columns Columns for SQL trash case. * @param[in] filter_columns Columns for filter. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] extra_tables Extra tables to join in FROM clause. * @param[in] extra_where Extra WHERE clauses. Skipped for single * resource. * @param[in] owned Only get items owned by the current user. * * @return 0 success, 1 failed to find resource, 2 failed to find filter, -1 * error. */ int init_get_iterator (iterator_t* iterator, const char *type, const get_data_t *get, column_t *select_columns, column_t *trash_select_columns, const char **filter_columns, int distinct, const char *extra_tables, const char *extra_where, int owned) { return init_get_iterator2 (iterator, type, get, select_columns, trash_select_columns, NULL, NULL, filter_columns, distinct, extra_tables, extra_where, NULL, owned, FALSE, NULL); } /** * @brief Append expression for a column to an array. * * @param[in] columns Array. * @param[in] column_name Name of column. * @param[in] select_columns Definition of "SELECT" columns. * @param[in] where_columns Definition of "WHERE" columns. */ static void append_column (GArray *columns, const gchar *column_name, column_t *select_columns, column_t *where_columns) { int i = 0; while (select_columns[i].select != NULL) { gchar *select = NULL; if (strcmp (select_columns[i].select, column_name) == 0 || (select_columns[i].filter && strcmp (select_columns[i].filter, column_name) == 0)) { select = g_strdup (select_columns[i].select); g_array_append_val (columns, select); break; } i++; } if (select_columns[i].select == NULL && where_columns) { i = 0; while (where_columns[i].select != NULL) { gchar *select = NULL; if (strcmp (where_columns[i].select, column_name) == 0 || (where_columns[i].filter && strcmp (where_columns[i].filter, column_name) == 0)) { select = g_strdup (where_columns[i].select); g_array_append_val (columns, select); break; } i++; } } } /** * @brief Initialise a GET_AGGREGATES iterator, including observed resources. * * @param[in] iterator Iterator. * @param[in] type Type of resource. * @param[in] get GET data. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] data_columns Columns to calculate statistics for. * @param[in] group_column Column to group data by. * @param[in] subgroup_column Column to further group data by. * @param[in] text_columns Columns to get text from. * @param[in] sort_data GArray of sorting data. * @param[in] first_group Row number to start iterating from. * @param[in] max_groups Maximum number of rows. * @param[in] extra_tables Join tables. Skipped for trash and single * resource. * @param[in] given_extra_where Extra WHERE clauses. Skipped for single * resource. * * @return 0 success, 1 failed to find resource, 2 failed to find filter, * 3 invalid stat_column, 4 invalid group_column, 5 invalid type, * 6 trashcan not used by type, 7 invalid text column, 8 invalid * subgroup_column, -1 error. */ int init_aggregate_iterator (iterator_t* iterator, const char *type, const get_data_t *get, int distinct, GArray *data_columns, const char *group_column, const char *subgroup_column, GArray *text_columns, GArray *sort_data, int first_group, int max_groups, const char *extra_tables, const char *given_extra_where) { column_t *select_columns, *where_columns; const char **filter_columns; GString *aggregate_select, *outer_col_select; gchar *aggregate_group_by; gchar *outer_group_by_column, *outer_subgroup_column; gchar *select_group_column, *select_subgroup_column; GArray *select_data_columns, *select_text_columns; GString *order_clause; gchar *inner_select; int build_select_ret; assert (get); if (get->id) g_warning ("%s: Called with an id parameter", __func__); if ((manage_scap_loaded () == FALSE && (strcmp (type, "cve") == 0 || strcmp (type, "cpe") == 0 || strcmp (type, "ovaldef") == 0)) || (manage_cert_loaded () == FALSE && (strcmp (type, "cert_bund_adv") == 0 || strcmp (type, "dfn_cert_adv") == 0))) { // Init a dummy iterator if SCAP or CERT DB is required but unavailable. init_iterator (iterator, "SELECT NULL LIMIT %s", sql_select_limit (0)); return 0; } select_columns = type_select_columns (type); where_columns = type_where_columns (type); filter_columns = type_filter_columns (type); if (filter_columns == NULL) return 5; if (get->trash && type_has_trash (type) == 0) return 6; if (data_columns && data_columns->len > 0) { int i; for (i = 0; i < data_columns->len; i++) { if (vector_find_filter (filter_columns, g_array_index (data_columns, gchar*, i)) == 0) { return 3; } } } if (text_columns && text_columns->len > 0) { int i; for (i = 0; i < text_columns->len; i++) { if (vector_find_filter (filter_columns, g_array_index (text_columns, gchar*, i)) == 0) { return 7; } } } if (group_column && vector_find_filter (filter_columns, group_column) == 0) { return 4; } if (subgroup_column && vector_find_filter (filter_columns, subgroup_column) == 0) { return 8; } select_data_columns = g_array_new (TRUE, TRUE, sizeof (gchar*)); select_text_columns = g_array_new (TRUE, TRUE, sizeof (gchar*)); select_group_column = NULL; select_subgroup_column = NULL; if (group_column) { select_group_column = g_strdup (columns_select_column (select_columns, where_columns, group_column)); if (subgroup_column) { select_subgroup_column = g_strdup (columns_select_column (select_columns, where_columns, subgroup_column)); } } if (data_columns && data_columns->len > 0) { int column_index; for (column_index = 0; column_index < data_columns->len; column_index++) append_column (select_data_columns, g_array_index (data_columns, gchar*, column_index), select_columns, where_columns); } if (text_columns && text_columns->len > 0) { int column_index; for (column_index = 0; column_index < text_columns->len; column_index++) append_column (select_text_columns, g_array_index (text_columns, gchar*, column_index), select_columns, where_columns); } /* Round time fields to the next day to reduce amount of rows returned * This returns "pseudo-UTC" dates which are used by the GSA charts because * the JavaScript Date objects do not support setting the timezone. */ if (column_is_timestamp (group_column)) outer_group_by_column = g_strdup_printf ("EXTRACT (EPOCH FROM" " date_trunc ('day'," " TIMESTAMP WITH TIME ZONE 'epoch'" " + (%s) * INTERVAL '1 second'))" " :: integer", "aggregate_group_value"); else outer_group_by_column = g_strdup ("aggregate_group_value"); if (column_is_timestamp (subgroup_column)) outer_subgroup_column = g_strdup_printf ("EXTRACT (EPOCH FROM" " date_trunc ('day'," " TIMESTAMP WITH TIME ZONE 'epoch'" " + (%s) * INTERVAL '1 second'))" " :: integer", "aggregate_subgroup_value"); else outer_subgroup_column = g_strdup ("aggregate_subgroup_value"); if (sort_data) { order_clause = g_string_new ("ORDER BY"); int sort_index; for (sort_index = 0; sort_index < sort_data->len; sort_index++) { sort_data_t *sort_data_item; const gchar *sort_field, *sort_stat; int sort_order; gchar *order_column; sort_data_item = g_array_index (sort_data, sort_data_t*, sort_index); sort_field = sort_data_item->field; sort_stat = sort_data_item->stat; sort_order = sort_data_item->order; if (sort_stat && strcmp (sort_stat, "count") == 0) order_column = g_strdup ("outer_count"); else if (sort_stat && strcmp (sort_stat, "value") == 0) order_column = g_strdup ("outer_group_column"); else if (sort_field && group_column && strcmp (sort_field, "") && strcmp (sort_field, group_column) && (subgroup_column == NULL || strcmp (sort_field, subgroup_column))) { int index; order_column = NULL; for (index = 0; data_columns && index < data_columns->len && order_column == NULL; index++) { gchar *column = g_array_index (data_columns, gchar*, index); if (strcmp (column, sort_field) == 0) { if (sort_stat == NULL || strcmp (sort_stat, "") == 0 || ( strcmp (sort_stat, "min") && strcmp (sort_stat, "max") && strcmp (sort_stat, "mean") && strcmp (sort_stat, "sum"))) order_column = g_strdup_printf ("max (aggregate_max_%d)", index); else if (strcmp (sort_stat, "mean") == 0) order_column = g_strdup_printf ("sum (aggregate_sum_%d)" " / sum(aggregate_count)", index); else order_column = g_strdup_printf ("%s (aggregate_%s_%d)", sort_stat, sort_stat, index); } } for (index = 0; text_columns && index < text_columns->len && order_column == NULL; index++) { gchar *column = g_array_index (text_columns, gchar*, index); if (strcmp (column, sort_field) == 0) { order_column = g_strdup_printf ("max (text_column_%d)", index); } } } else if (sort_field && subgroup_column && strcmp (sort_field, subgroup_column) == 0) order_column = g_strdup ("outer_subgroup_column"); else order_column = g_strdup ("outer_group_column"); if (subgroup_column && sort_index == 0) { xml_string_append (order_clause, " outer_group_column %s, %s %s", sort_order ? "ASC" : "DESC", order_column, sort_order ? "ASC" : "DESC"); } else { xml_string_append (order_clause, "%s %s %s", sort_index > 0 ? "," : "", order_column, sort_order ? "ASC" : "DESC"); } g_free (order_column); } if (sort_data->len == 0) g_string_append (order_clause, " outer_group_column ASC"); } else order_clause = g_string_new (""); aggregate_select = g_string_new (""); outer_col_select = g_string_new (""); if (group_column && strcmp (group_column, "")) { if (subgroup_column && strcmp (subgroup_column, "")) { g_string_append_printf (aggregate_select, " count(*) AS aggregate_count," " %s AS aggregate_group_value," " %s AS aggregate_subgroup_value", select_group_column, select_subgroup_column); aggregate_group_by = g_strdup_printf ("%s, %s", select_group_column, select_subgroup_column); } else { g_string_append_printf (aggregate_select, " count(*) AS aggregate_count," " %s AS aggregate_group_value," " CAST (NULL AS TEXT) AS aggregate_subgroup_value", select_group_column); aggregate_group_by = g_strdup (select_group_column); } } else { g_string_append_printf (aggregate_select, " count(*) AS aggregate_count," " CAST (NULL AS TEXT) AS aggregate_group_value," " CAST (NULL AS TEXT) AS aggregate_subgroup_value"); aggregate_group_by = NULL; } int col_index; for (col_index = 0; col_index < select_data_columns->len; col_index ++) { gchar *select_data_column = g_array_index (select_data_columns, gchar*, col_index); // TODO: Test type of column (string, number, timestamp) g_string_append_printf (aggregate_select, "," " min(CAST (%s AS real)) AS aggregate_min_%d," " max(CAST (%s AS real)) AS aggregate_max_%d," " avg(CAST (%s AS real)) * count(*)" " AS aggregate_avg_%d," " sum(CAST (%s AS real))" " AS aggregate_sum_%d", select_data_column, col_index, select_data_column, col_index, select_data_column, col_index, select_data_column, col_index); g_string_append_printf (outer_col_select, ", min(aggregate_min_%d)," " max (aggregate_max_%d)," " sum (aggregate_avg_%d) / sum(aggregate_count)," " sum (aggregate_sum_%d)", col_index, col_index, col_index, col_index); } for (col_index = 0; col_index < select_text_columns->len; col_index ++) { gchar *select_text_column = g_array_index (select_text_columns, gchar*, col_index); g_string_append_printf (aggregate_select, ", max (%s) as text_column_%d", select_text_column, col_index); g_string_append_printf (outer_col_select, ", max (text_column_%d)", col_index); } inner_select = NULL; build_select_ret = type_build_select (type, aggregate_select->str, get, distinct, 0, extra_tables, given_extra_where, aggregate_group_by, &inner_select); if (build_select_ret == 0) { init_iterator (iterator, "SELECT sum(aggregate_count) AS outer_count," " %s AS outer_group_column," " %s AS outer_subgroup_column" " %s" " FROM (%s)" " AS agg_sub" " GROUP BY outer_group_column, outer_subgroup_column" " %s" " LIMIT %s OFFSET %d;", outer_group_by_column, outer_subgroup_column, outer_col_select->str, inner_select, order_clause->str, sql_select_limit (max_groups), first_group); } g_string_free (order_clause, TRUE); g_free (aggregate_group_by); g_string_free (aggregate_select, TRUE); g_string_free (outer_col_select, TRUE); g_free (outer_group_by_column); g_free (outer_subgroup_column); g_free (select_group_column); g_free (select_subgroup_column); g_free (inner_select); switch (build_select_ret) { case 0: break; case 1: return 2; default: return -1; } return 0; } /** * @brief Offset for aggregate iterator. */ #define AGGREGATE_ITERATOR_OFFSET 3 /** * @brief Number of stats, for aggregate iterator. */ #define AGGREGATE_ITERATOR_N_STATS 4 /** * @brief Get the count from an aggregate iterator. * * @param[in] iterator Iterator. * * @return The count of resources in the current group. */ int aggregate_iterator_count (iterator_t* iterator) { return iterator_int (iterator, 0); } /** * @brief Get the minimum from an aggregate iterator. * * @param[in] iterator Iterator. * @param[in] data_column_index Index of the data column to get min of. * * @return The minimum value in the current group. */ double aggregate_iterator_min (iterator_t* iterator, int data_column_index) { return iterator_double (iterator, AGGREGATE_ITERATOR_OFFSET + data_column_index * AGGREGATE_ITERATOR_N_STATS); } /** * @brief Get the maximum from an aggregate iterator. * * @param[in] iterator Iterator. * @param[in] data_column_index Index of the data column to get max of. * * @return The maximum value in the current group. */ double aggregate_iterator_max (iterator_t* iterator, int data_column_index) { return iterator_double (iterator, AGGREGATE_ITERATOR_OFFSET + 1 + data_column_index * AGGREGATE_ITERATOR_N_STATS); } /** * @brief Get the mean from an aggregate iterator. * * @param[in] iterator Iterator. * @param[in] data_column_index Index of the data column to get mean of. * * @return The mean value in the current group. */ double aggregate_iterator_mean (iterator_t* iterator, int data_column_index) { return iterator_double (iterator, AGGREGATE_ITERATOR_OFFSET + 2 + data_column_index * AGGREGATE_ITERATOR_N_STATS); } /** * @brief Get the sum from a statistics iterator. * * @param[in] iterator Iterator. * @param[in] data_column_index Index of the data column to get sum of. * * @return The sum of values in the current group. */ double aggregate_iterator_sum (iterator_t* iterator, int data_column_index) { return iterator_double (iterator, AGGREGATE_ITERATOR_OFFSET + 3 + data_column_index * AGGREGATE_ITERATOR_N_STATS); } /** * @brief Get the value of a text column from an aggregate iterator. * * @param[in] iterator Iterator. * @param[in] text_column_index Index of the text column to get. * @param[in] data_columns Number of data columns. * * @return The value, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* aggregate_iterator_text (iterator_t* iterator, int text_column_index, int data_columns) { const char *ret; if (iterator->done) return NULL; ret = (const char*) iterator_string (iterator, AGGREGATE_ITERATOR_OFFSET + (data_columns * AGGREGATE_ITERATOR_N_STATS) + text_column_index); return ret; } /** * @brief Get the value of the group column from a statistics iterator. * * @param[in] iterator Iterator. * * @return The value, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* aggregate_iterator_value (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = (const char*) iterator_string (iterator, 1); return ret; } /** * @brief Get the value of the subgroup column from an aggregate iterator. * * @param[in] iterator Iterator. * * @return The value, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* aggregate_iterator_subgroup_value (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = (const char*) iterator_string (iterator, 2); return ret; } /** * @brief Count number of a particular resource. * * @param[in] type Type of resource. * @param[in] get GET params. * @param[in] select_columns SELECT columns. * @param[in] trash_select_columns SELECT columns for trashcan. * @param[in] where_columns WHERE columns. * @param[in] trash_where_columns WHERE columns for trashcan. * @param[in] filter_columns Extra columns. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] extra_tables Join tables. Skipped for trash and single * resource. * @param[in] extra_where Extra WHERE clauses. Skipped for trash and * single resource. * @param[in] extra_with Extra WITH clauses. * @param[in] owned Only count items owned by current user. * * @return Total number of resources in filtered set. */ static int count2 (const char *type, const get_data_t *get, column_t *select_columns, column_t *trash_select_columns, column_t *where_columns, column_t *trash_where_columns, const char **filter_columns, int distinct, const char *extra_tables, const char *extra_where, const char *extra_with, int owned) { int ret; gchar *clause, *owned_clause, *owner_filter, *columns, *filter, *with; array_t *permissions; assert (get); if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return -1; } else filter = NULL; g_debug ("%s", __func__); clause = filter_clause (type, filter ? filter : get->filter, filter_columns, get->trash && trash_select_columns ? trash_select_columns : select_columns, get->trash && trash_where_columns ? trash_where_columns : where_columns, get->trash, NULL, NULL, NULL, &permissions, &owner_filter); g_free (filter); owned_clause = acl_where_owned (type, get, owned, owner_filter, 0, permissions, 0, &with); if (extra_with) { if (with) { gchar *old_with; old_with = with; with = g_strdup_printf ("%s, %s", old_with, extra_with); g_free (old_with); } else with = g_strdup_printf ("WITH %s", extra_with); } g_free (owner_filter); array_free (permissions); if (get->trash && trash_select_columns) columns = columns_build_select (trash_select_columns); else columns = columns_build_select (select_columns); if ((distinct == 0) && (extra_tables == NULL) && (clause == NULL) && (extra_where == NULL) && (strcmp (owned_clause, " t ()") == 0)) ret = sql_int ("%sSELECT count (*) FROM %ss%s;", with ? with : "", type, get->trash && strcmp (type, "task") ? "_trash" : ""); else ret = sql_int ("%sSELECT count (%scount_id)" " FROM (SELECT %ss%s.id AS count_id" " FROM %ss%s%s" " WHERE %s" " %s%s%s%s) AS subquery;", with ? with : "", distinct ? "DISTINCT " : "", type, get->trash && strcmp (type, "task") ? "_trash" : "", type, get->trash && strcmp (type, "task") ? "_trash" : "", extra_tables ? extra_tables : "", owned_clause, clause ? " AND (" : "", clause ? clause : "", clause ? ") " : "", extra_where ? extra_where : ""); g_free (with); g_free (columns); g_free (owned_clause); g_free (clause); g_debug ("%s: done", __func__); return ret; } /** * @brief Count number of a particular resource. * * @param[in] type Type of resource. * @param[in] get GET params. * @param[in] select_columns SELECT columns. * @param[in] trash_select_columns SELECT columns for trashcan. * @param[in] filter_columns Extra columns. * @param[in] distinct Whether the query should be distinct. Skipped * for trash and single resource. * @param[in] extra_tables Join tables. Skipped for trash and single * resource. * @param[in] extra_where Extra WHERE clauses. Skipped for trash and * single resource. * @param[in] owned Only count items owned by current user. * * @return Total number of resources in filtered set. */ int count (const char *type, const get_data_t *get, column_t *select_columns, column_t *trash_select_columns, const char **filter_columns, int distinct, const char *extra_tables, const char *extra_where, int owned) { return count2 (type, get, select_columns, trash_select_columns, NULL, NULL, filter_columns, distinct, extra_tables, extra_where, NULL, owned); } /** * @brief Count number of info of a given subtype with a given name. * * @param[in] type GET_INFO subtype. * @param[out] name Name of the info item. * * @return Total number of get_info items of given type, -1 on error. */ int info_name_count (const char *type, const char *name) { gchar *quoted_name; int count; assert(type); assert(name); quoted_name = sql_quote (name); count = sql_int ("SELECT COUNT(id)" " FROM %ss" " WHERE name = '%s';", type, quoted_name); g_free (quoted_name); return count; } /** * @brief Return the database version supported by this manager. * * @return Database version supported by this manager. */ int manage_db_supported_version () { return GVMD_DATABASE_VERSION; } /** * @brief Return the database version of the actual database. * * @return Database version read from database, -2 if database is empty, * -1 on error. */ int manage_db_version () { int number; char *version; if (manage_db_empty ()) return -2; version = sql_string ("SELECT value FROM %s.meta" " WHERE name = 'database_version' LIMIT 1;", sql_schema ()); if (version) { number = atoi (version); free (version); return number; } return -1; } /** * @brief Return the database version supported by this manager. * * @return Database version supported by this manager. */ int manage_scap_db_supported_version () { return GVMD_SCAP_DATABASE_VERSION; } /** * @brief Return the database version of the actual database. * * @return Database version read from database if possible, else -1. */ int manage_scap_db_version () { if (manage_scap_loaded () == 0) return -1; int number; char *version = sql_string ("SELECT value FROM scap.meta" " WHERE name = 'database_version' LIMIT 1;"); if (version) { number = atoi (version); free (version); return number; } return -1; } /** * @brief Return the database version supported by this manager. * * @return Database version supported by this manager. */ int manage_cert_db_supported_version () { return GVMD_CERT_DATABASE_VERSION; } /** * @brief Return the database version of the actual database. * * @return Database version read from database if possible, else -1. */ int manage_cert_db_version () { if (manage_cert_loaded () == 0) return -1; int number; char *version = sql_string ("SELECT value FROM cert.meta" " WHERE name = 'database_version' LIMIT 1;"); if (version) { number = atoi (version); free (version); return number; } return -1; } /** * @brief Set the database version of the actual database. * * Caller must organise transaction. * * @param version New version number. */ void set_db_version (int version) { sql ("DELETE FROM %s.meta WHERE name = 'database_version';", sql_schema ()); sql ("INSERT INTO %s.meta (name, value)" " VALUES ('database_version', '%i');", sql_schema (), version); } /** * @brief Encrypt, re-encrypt or decrypt all credentials * * All plaintext credentials in the credentials table are * encrypted, all already encrypted credentials are encrypted again * using the latest key. * * @param[in] decrypt_flag If true decrypt all credentials. * * @return 0 success, -1 error. */ static int encrypt_all_credentials (gboolean decrypt_flag) { iterator_t iterator; unsigned long ntotal, nencrypted, nreencrypted, ndecrypted; init_iterator (&iterator, "SELECT id," " (SELECT value FROM credentials_data" " WHERE credential = credentials.id" " AND type = 'secret')," " (SELECT value FROM credentials_data" " WHERE credential = credentials.id" " AND type = 'password')," " (SELECT value FROM credentials_data" " WHERE credential = credentials.id" " AND type = 'private_key')" " FROM credentials"); iterator.crypt_ctx = lsc_crypt_new (); sql_begin_immediate (); ntotal = nencrypted = nreencrypted = ndecrypted = 0; while (next (&iterator)) { long long int rowid; const char *secret, *password, *privkey; ntotal++; if (!(ntotal % 10)) g_message (" %lu credentials so far processed", ntotal); rowid = iterator_int64 (&iterator, 0); secret = iterator_string (&iterator, 1); password = iterator_string (&iterator, 2); privkey = iterator_string (&iterator, 3); /* If there is no secret, password or private key, skip the row. */ if (!secret && !password && !privkey) continue; if (secret) { lsc_crypt_flush (iterator.crypt_ctx); password = lsc_crypt_get_password (iterator.crypt_ctx, secret); privkey = lsc_crypt_get_private_key (iterator.crypt_ctx, secret); /* If there is no password or private key, skip the row. */ if (!password && !privkey) continue; nreencrypted++; } else { if (decrypt_flag) continue; /* Skip non-encrypted rows. */ nencrypted++; } if (decrypt_flag) { set_credential_data (rowid, "password", password); set_credential_data (rowid, "private_key", privkey); set_credential_data (rowid, "secret", NULL); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", rowid); ndecrypted++; } else { char *encblob; if (password && privkey) encblob = lsc_crypt_encrypt (iterator.crypt_ctx, "password", password, "private_key", privkey, NULL); else if (password) encblob = lsc_crypt_encrypt (iterator.crypt_ctx, "password", password, NULL); else encblob = lsc_crypt_encrypt (iterator.crypt_ctx, "private_key", privkey, NULL); if (!encblob) { sql_rollback (); cleanup_iterator (&iterator); return -1; } set_credential_data (rowid, "password", NULL); set_credential_data (rowid, "private_key", NULL); set_credential_data (rowid, "secret", encblob); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", rowid); g_free (encblob); } } sql_commit (); if (decrypt_flag) g_message ("%lu out of %lu credentials decrypted", ndecrypted, ntotal); else g_message ("%lu out of %lu credentials encrypted and %lu re-encrypted", nencrypted, ntotal, nreencrypted); cleanup_iterator (&iterator); return 0; } /** * @brief Encrypt or re-encrypt all credentials * * All plaintext credentials in the credentials table are * encrypted, all already encrypted credentials are encrypted again * using the latest key. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * * @return 0 success, -1 error, * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_encrypt_all_credentials (GSList *log_config, const db_conn_info_t *database) { int ret; g_info (" (Re-)encrypting all credentials."); ret = manage_option_setup (log_config, database); if (ret) return ret; ret = encrypt_all_credentials (FALSE); if (ret) printf ("Encryption failed.\n"); else printf ("Encryption succeeded.\n"); manage_option_cleanup (); return ret; } /** * @brief Decrypt all credentials * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * * @return 0 success, -1 error, * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_decrypt_all_credentials (GSList *log_config, const db_conn_info_t *database) { int ret; g_info (" Decrypting all credentials."); ret = manage_option_setup (log_config, database); if (ret) return ret; ret = encrypt_all_credentials (TRUE); if (ret) printf ("Decryption failed.\n"); else printf ("Decryption succeeded.\n"); manage_option_cleanup (); return ret; } /* Collation. */ /** * @brief Compare two number strings for collate_ip. * * @param[in] one_arg First string. * @param[in] two_arg Second string. * * @return -1, 0 or 1 if first is less than, equal to or greater than second. */ static int collate_ip_compare (const char *one_arg, const char *two_arg) { int one = atoi (one_arg); int two = atoi (two_arg); return one == two ? 0 : (one < two ? -1 : 1); } /** * @brief Collate two IP addresses. * * For example, 127.0.0.2 is less than 127.0.0.3 and 127.0.0.10. * * Only works correctly for IPv4 addresses. * * @param[in] data Dummy for callback. * @param[in] one_len Length of first IP (a string). * @param[in] arg_one First string. * @param[in] two_len Length of second IP (a string). * @param[in] arg_two Second string. * * @return -1, 0 or 1 if first is less than, equal to or greater than second. */ static int collate_ip (void* data, int one_len, const void* arg_one, int two_len, const void* arg_two) { int ret, one_dot, two_dot; char one_a[4], one_b[4], one_c[4], one_d[4]; char two_a[4], two_b[4], two_c[4], two_d[4]; const char* one = (const char*) arg_one; const char* two = (const char*) arg_two; if ((sscanf (one, "%3[0-9].%3[0-9].%3[0-9].%n%3[0-9]", one_a, one_b, one_c, &one_dot, one_d) == 4) && (sscanf (two, "%3[0-9].%3[0-9].%3[0-9].%n%3[0-9]", two_a, two_b, two_c, &two_dot, two_d) == 4)) { ret = collate_ip_compare (one_a, two_a); if (ret) return ret < 0 ? -1 : 1; ret = collate_ip_compare (one_b, two_b); if (ret) return ret < 0 ? -1 : 1; ret = collate_ip_compare (one_c, two_c); if (ret) return ret < 0 ? -1 : 1; /* Ensure that the last number is limited to digits in the arg. */ one_d[one_len - one_dot] = '\0'; two_d[two_len - two_dot] = '\0'; ret = collate_ip_compare (one_d, two_d); if (ret) return ret < 0 ? -1 : 1; return 0; } ret = strncmp (one, two, MIN (one_len, two_len)); return ret == 0 ? 0 : (ret < 0 ? -1 : 1); } /* Task subject iterators. */ /** * @brief Initialise a task user iterator. * * @param[in] iterator Iterator. * @param[in] task Task. */ static void init_task_user_iterator (iterator_t *iterator, task_t task) { init_iterator (iterator, "SELECT DISTINCT 1, resource, subject," " (SELECT name FROM users" " WHERE users.id = permissions.subject)" " FROM permissions" /* Any permission implies 'get_tasks'. */ " WHERE resource_type = 'task'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject_type = 'user'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", task); } static DEF_ACCESS (task_user_iterator_name, 3); /** * @brief Initialise a task group iterator. * * @param[in] iterator Iterator. * @param[in] task Task. */ void init_task_group_iterator (iterator_t *iterator, task_t task) { init_iterator (iterator, "SELECT DISTINCT 1, resource, subject," " (SELECT name FROM groups" " WHERE groups.id = permissions.subject)," " (SELECT uuid FROM groups" " WHERE groups.id = permissions.subject)" " FROM permissions" /* Any permission implies 'get_tasks'. */ " WHERE resource_type = 'task'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject_type = 'group'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", task); } DEF_ACCESS (task_group_iterator_name, 3); DEF_ACCESS (task_group_iterator_uuid, 4); /** * @brief Initialise a task role iterator. * * @param[in] iterator Iterator. * @param[in] task Task. */ void init_task_role_iterator (iterator_t *iterator, task_t task) { init_iterator (iterator, "SELECT DISTINCT 1, resource, subject," " (SELECT name FROM roles" " WHERE roles.id = permissions.subject)," " (SELECT uuid FROM roles" " WHERE roles.id = permissions.subject)" " FROM permissions" /* Any permission implies 'get'. */ " WHERE resource_type = 'task'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject_type = 'role'", task); } DEF_ACCESS (task_role_iterator_name, 3); DEF_ACCESS (task_role_iterator_uuid, 4); /* Events and Alerts. */ /** * @brief Check if any SecInfo alerts are due. */ void check_alerts () { if (manage_scap_loaded ()) { int max_time; max_time = sql_int ("SELECT %s" " ((SELECT max (modification_time) FROM scap.cves)," " (SELECT max (modification_time) FROM scap.cpes)," " (SELECT max (modification_time) FROM scap.ovaldefs)," " (SELECT max (creation_time) FROM scap.cves)," " (SELECT max (creation_time) FROM scap.cpes)," " (SELECT max (creation_time) FROM scap.ovaldefs));", sql_greatest ()); if (sql_int ("SELECT NOT EXISTS (SELECT * FROM meta" " WHERE name = 'scap_check_time')")) sql ("INSERT INTO meta (name, value)" " VALUES ('scap_check_time', %i);", max_time); else if (sql_int ("SELECT value = '0' FROM meta" " WHERE name = 'scap_check_time';")) sql ("UPDATE meta SET value = %i" " WHERE name = 'scap_check_time';", max_time); else { check_for_new_scap (); check_for_updated_scap (); sql ("UPDATE meta SET value = %i" " WHERE name = 'scap_check_time';", max_time); } } if (manage_cert_loaded ()) { int max_time; max_time = sql_int ("SELECT" " %s" " ((SELECT max (modification_time) FROM cert.cert_bund_advs)," " (SELECT max (modification_time) FROM cert.dfn_cert_advs)," " (SELECT max (creation_time) FROM cert.cert_bund_advs)," " (SELECT max (creation_time) FROM cert.dfn_cert_advs));", sql_greatest ()); if (sql_int ("SELECT NOT EXISTS (SELECT * FROM meta" " WHERE name = 'cert_check_time')")) sql ("INSERT INTO meta (name, value)" " VALUES ('cert_check_time', %i);", max_time); else if (sql_int ("SELECT value = '0' FROM meta" " WHERE name = 'cert_check_time';")) sql ("UPDATE meta SET value = %i" " WHERE name = 'cert_check_time';", max_time); else { check_for_new_cert (); check_for_updated_cert (); sql ("UPDATE meta SET value = %i" " WHERE name = 'cert_check_time';", max_time); } } } /** * @brief Check if any SecInfo alerts are due. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * * @return 0 success, -1 error, * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_check_alerts (GSList *log_config, const db_conn_info_t *database) { int ret; g_info (" Checking alerts."); ret = manage_option_setup (log_config, database); if (ret) return ret; /* Setup a dummy user, so that create_user will work. */ current_credentials.uuid = ""; check_alerts (); current_credentials.uuid = NULL; manage_option_cleanup (); return ret; } /** * @brief Find a alert for a specific permission, given a UUID. * * @param[in] uuid UUID of alert. * @param[out] alert Alert return, 0 if successfully failed to find alert. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find alert), TRUE on error. */ gboolean find_alert_with_permission (const char* uuid, alert_t* alert, const char *permission) { return find_resource_with_permission ("alert", uuid, alert, permission, 0); } /** * @brief Validate an email address. * * @param[in] address Email address. * * @return 0 success, 1 failure. */ static int validate_email (const char* address) { gchar **split, *point; assert (address); split = g_strsplit (address, "@", 0); if (split[0] == NULL || split[1] == NULL || split[2]) { g_strfreev (split); return 1; } /* Local part. */ point = split[0]; while (*point) if (isalnum (*point) || strchr ("!#$%&'*+-/=?^_`{|}~", *point) || ((*point == '.') && (point > split[0]) && point[1] && (point[1] != '.') && (point[-1] != '.'))) point++; else { g_strfreev (split); return 1; } /* Domain. */ point = split[1]; while (*point) if (isalnum (*point) || strchr ("-_", *point) /* RFC actually forbids _. */ || ((*point == '.') && (point > split[1]) && point[1] && (point[1] != '.') && (point[-1] != '.'))) point++; else { g_strfreev (split); return 1; } g_strfreev (split); return 0; } /** * @brief Validate an email address list. * * @param[in] list Comma separated list of email addresses. * * @return 0 success, 1 failure. */ static int validate_email_list (const char *list) { gchar **split, **point; assert (list); split = g_strsplit (list, ",", 0); if (split[0] == NULL) { g_strfreev (split); return 1; } point = split; while (*point) { const char *address; address = *point; while (*address && (*address == ' ')) address++; if (validate_email (address)) { g_strfreev (split); return 1; } point++; } g_strfreev (split); return 0; } /** * @brief Validate condition data for an alert. * * @param[in] name Name. * @param[in] data Data to validate. * @param[in] condition The condition. * * @return 0 on success, 1 unexpected data name, 2 syntax error in data, * 3 failed to find filter for condition, -1 internal error. */ static int validate_alert_condition_data (gchar *name, gchar* data, alert_condition_t condition) { if (condition == ALERT_CONDITION_ALWAYS) return 1; if (condition == ALERT_CONDITION_SEVERITY_AT_LEAST) { if (strcmp (name, "severity")) return 1; if (g_regex_match_simple ("^(-1(\\.0)?|[0-9](\\.[0-9])?|10(\\.0))$", data ? data : "", 0, 0) == 0) return 2; } else if (condition == ALERT_CONDITION_SEVERITY_CHANGED) { if (strcmp (name, "direction")) return 1; if (g_regex_match_simple ("^(increased|decreased|changed)$", data ? data : "", 0, 0) == 0) return 2; } else if (condition == ALERT_CONDITION_FILTER_COUNT_AT_LEAST) { if (strcmp (name, "filter_id") == 0) { filter_t filter; if (data == NULL) return 3; filter = 0; if (find_filter_with_permission (data, &filter, "get_filters")) return -1; if (filter == 0) return 3; return 0; } if (strcmp (name, "count")) return 1; } else if (condition == ALERT_CONDITION_FILTER_COUNT_CHANGED) { if (strcmp (name, "filter_id") == 0) { filter_t filter; if (data == NULL) return 3; filter = 0; if (find_filter_with_permission (data, &filter, "get_filters")) return -1; if (filter == 0) return 3; return 0; } if (strcmp (name, "direction") && strcmp (name, "count")) return 1; if (strcmp (name, "direction") == 0 && g_regex_match_simple ("^(increased|decreased|changed)$", data ? data : "", 0, 0) == 0) return 2; } return 0; } /** * @brief Validate event data for an alert. * * @param[in] name Name. * @param[in] data Data to validate. * @param[in] event The event. * * @return 0 on success, 1 unexpected data name, 2 syntax error in data. */ static int validate_alert_event_data (gchar *name, gchar* data, event_t event) { if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { if (strcmp (name, "secinfo_type")) return 1; if (data == NULL) return 2; if (strcasecmp (data, "nvt") && strcasecmp (data, "cve") && strcasecmp (data, "cpe") && strcasecmp (data, "cert_bund_adv") && strcasecmp (data, "dfn_cert_adv") && strcasecmp (data, "ovaldef")) return 2; } return 0; } /** * @brief Validate method data for the email method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * @param[in] for_modify Whether to return error codes for modify_alert. * * @return 0 valid, 2 or 6: validation of email address failed, * 7 or 9 subject too long, 8 or 10 message too long, * 60 recipient credential not found, 61 invalid recipient credential * type, -1 error. When for_modify is 0, the first code is returned, * otherwise the second one. */ int validate_email_data (alert_method_t method, const gchar *name, gchar **data, int for_modify) { if (method == ALERT_METHOD_EMAIL && strcmp (name, "to_address") == 0 && validate_email_list (*data)) return for_modify ? 6 : 2; if (method == ALERT_METHOD_EMAIL && strcmp (name, "from_address") == 0 && validate_email (*data)) return for_modify ? 6 : 2; if (method == ALERT_METHOD_EMAIL && strcmp (name, "subject") == 0 && strlen (*data) > 80) return for_modify ? 9 : 7; if (method == ALERT_METHOD_EMAIL && strcmp (name, "message") == 0 && strlen (*data) > max_email_message_length) return for_modify ? 10 : 8; if (method == ALERT_METHOD_EMAIL && strcmp (name, "recipient_credential") == 0 && *data && strcmp (*data, "")) { credential_t credential; char *type; if (find_credential_with_permission (*data, &credential, NULL)) return -1; else if (credential == 0) return 60; type = credential_type (credential); if (strcmp (type, "pgp") && strcmp (type, "smime")) { free (type); return 61; } free (type); } return 0; } /** * @brief Validate method data for the SCP method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 15 error in SCP host, 17 failed to find report format for * SCP method, 18 error in SCP credential, 19 error in SCP path, * -1 error. */ static int validate_scp_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_SCP && strcmp (name, "scp_credential") == 0) { credential_t credential; if (find_credential_with_permission (*data, &credential, "get_credentials")) return -1; else if (credential == 0) return 18; else { gchar *username; username = credential_value (credential, "username"); if (username == NULL || strlen (username) == 0) { g_free (username); return 18; } if (strchr (username, ':')) { g_free (username); return 18; } g_free (username); } } if (method == ALERT_METHOD_SCP && strcmp (name, "scp_path") == 0) { if (strlen (*data) == 0) return 19; } if (method == ALERT_METHOD_SCP && strcmp (name, "scp_host") == 0) { int type; gchar *stripped; stripped = g_strstrip (g_strdup (*data)); type = gvm_get_host_type (stripped); g_free (stripped); if ((type != HOST_TYPE_IPV4) && (type != HOST_TYPE_IPV6) && (type != HOST_TYPE_NAME)) return 15; } if (method == ALERT_METHOD_SCP && strcmp (name, "scp_report_format") == 0) { report_format_t report_format; report_format = 0; if (find_report_format_with_permission (*data, &report_format, "get_report_formats")) return -1; if (report_format == 0) return 17; } return 0; } /** * @brief Validate method data for the Send method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 12 error in Send host, 13 error in Send port, 14 failed * to find report format for Send method, -1 error. */ static int validate_send_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_SEND && strcmp (name, "send_host") == 0) { int type; gchar *stripped; stripped = g_strstrip (g_strdup (*data)); type = gvm_get_host_type (stripped); g_free (stripped); if ((type != HOST_TYPE_IPV4) && (type != HOST_TYPE_IPV6) && (type != HOST_TYPE_NAME)) return 12; } if (method == ALERT_METHOD_SEND && strcmp (name, "send_port") == 0) { int port; gchar *stripped, *end; stripped = g_strstrip (g_strdup (*data)); port = strtol (stripped, &end, 10); if (*end != '\0') { g_free (stripped); return 13; } g_free (stripped); g_free (*data); *data = g_strdup_printf ("%i", port); } if (method == ALERT_METHOD_SEND && strcmp (name, "send_report_format") == 0) { report_format_t report_format; report_format = 0; if (find_report_format_with_permission (*data, &report_format, "get_report_formats")) return -1; if (report_format == 0) return 14; } return 0; } /** * @brief Validate method data for the Send method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 40 invalid credential, 41 invalid SMB share path, * 42 invalid SMB file path, 43 SMB file path contains dot, -1 error. */ static int validate_smb_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_SMB) { if (strcmp (name, "smb_credential") == 0) { credential_t credential; if (find_credential_with_permission (*data, &credential, "get_credentials")) return -1; else if (credential == 0) return 40; else { gchar *username; username = credential_value (credential, "username"); if (username == NULL || strlen (username) == 0) { g_free (username); return 40; } if (strchr (username, '@') || strchr (username, ':')) { g_free (username); return 40; } g_free (username); } } if (strcmp (name, "smb_share_path") == 0) { /* Check if share path has the correct format * "\\\" */ if (g_regex_match_simple ("^(?>\\\\\\\\|\\/\\/)[^:?<>|]+" "(?>\\\\|\\/)[^:?<>|]+$", *data, 0, 0) == FALSE) { return 41; } } if (strcmp (name, "smb_file_path") == 0) { /* Check if file path contains invalid characters: * ":", "?", "<", ">", "|" */ if (g_regex_match_simple ("^[^:?<>|]+$", *data, 0, 0) == FALSE) { return 42; } /* Check if a file or directory name ends with a dot, * e.g. "../a", "abc/../xyz" or "abc/..". */ else if (g_regex_match_simple ("^(?:.*\\.)(?:[\\/\\\\].*)*$", *data, 0, 0)) { return 43; } } } return 0; } /** * @brief Validate method data for the TippingPoint method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 50 invalid credential, 51 invalid hostname, * 52 invalid certificate, 53 invalid TLS workaround setting. */ static int validate_tippingpoint_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_TIPPINGPOINT) { if (strcmp (name, "tp_sms_credential") == 0) { credential_t credential; if (find_credential_with_permission (*data, &credential, "get_credentials")) return -1; else if (credential == 0) return 50; else { if (strcmp (credential_type (credential), "up")) return 50; } } if (strcmp (name, "tp_sms_hostname") == 0) { if (g_regex_match_simple ("^[0-9A-Za-z][0-9A-Za-z.-]*$", *data, 0, 0) == FALSE) { return 51; } } if (strcmp (name, "tp_sms_tls_certificate") == 0) { // TODO: Check certificate, return 52 on failure } if (strcmp (name, "tp_sms_tls_workaround") == 0) { if (g_regex_match_simple ("^0|1$", *data, 0, 0) == FALSE) { return 53; } } } return 0; } /** * @brief Validate method data for the vFire alert method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 70 credential not found, 71 invalid credential type */ static int validate_vfire_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_VFIRE) { if (strcmp (name, "vfire_credential") == 0) { credential_t credential; if (find_credential_with_permission (*data, &credential, "get_credentials")) return -1; else if (credential == 0) return 70; else { char *cred_type = credential_type (credential); if (strcmp (cred_type, "up")) { free (cred_type); return 71; } free (cred_type); } } } return 0; } /** * @brief Validate method data for the Sourcefire method. * * @param[in] method Method that data corresponds to. * @param[in] name Name of data. * @param[in] data The data. * * @return 0 valid, 80 credential not found, 81 invalid credential type */ static int validate_sourcefire_data (alert_method_t method, const gchar *name, gchar **data) { if (method == ALERT_METHOD_SOURCEFIRE) { if (strcmp (name, "pkcs12_credential") == 0) { credential_t credential; if (find_credential_with_permission (*data, &credential, "get_credentials")) return -1; else if (credential == 0) return 80; else { char *sourcefire_credential_type; sourcefire_credential_type = credential_type (credential); if (strcmp (sourcefire_credential_type, "up") && strcmp (sourcefire_credential_type, "pw")) { free (sourcefire_credential_type); return 81; } free (sourcefire_credential_type); } } } return 0; } /** * @brief Check alert params. * * @param[in] event Type of event. * @param[in] condition Event condition. * @param[in] method Escalation method. * * @return 0 success, 20 method does not match event, 21 condition does not * match event. */ static int check_alert_params (event_t event, alert_condition_t condition, alert_method_t method) { if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { if (method == ALERT_METHOD_HTTP_GET || method == ALERT_METHOD_SOURCEFIRE || method == ALERT_METHOD_VERINICE) return 20; if (condition == ALERT_CONDITION_SEVERITY_AT_LEAST || condition == ALERT_CONDITION_SEVERITY_CHANGED || condition == ALERT_CONDITION_FILTER_COUNT_CHANGED) return 21; } return 0; } /** * @brief Create an alert. * * @param[in] name Name of alert. * @param[in] comment Comment on alert. * @param[in] filter_id Filter. * @param[in] active Whether the alert is active. * @param[in] event Type of event. * @param[in] event_data Type-specific event data. * @param[in] condition Event condition. * @param[in] condition_data Condition-specific data. * @param[in] method Escalation method. * @param[in] method_data Data for escalation method. * @param[out] alert Created alert on success. * * @return 0 success, 1 escalation exists already, 2 validation of email failed, * 3 failed to find filter, 4 type must be "result" if specified, * 5 unexpected condition data name, 6 syntax error in condition data, * 7 email subject too long, 8 email message too long, 9 failed to find * filter for condition, 12 error in Send host, 13 error in Send port, * 14 failed to find report format for Send method, 15 error in * SCP host, 17 failed to find report format for SCP method, 18 error * in SCP credential, 19 error in SCP path, 20 method does not match * event, 21 condition does not match event, 31 unexpected event data * name, 32 syntax error in event data, 40 invalid SMB credential * , 41 invalid SMB share path, 42 invalid SMB file path, * 43 SMB file path contains dot, * 50 invalid TippingPoint credential, 51 invalid TippingPoint hostname, * 52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS * workaround setting, 60 recipient credential not found, 61 invalid * recipient credential type, 70 vFire credential not found, * 71 invalid vFire credential type, * 99 permission denied, -1 error. */ int create_alert (const char* name, const char* comment, const char* filter_id, const char* active, event_t event, GPtrArray* event_data, alert_condition_t condition, GPtrArray* condition_data, alert_method_t method, GPtrArray* method_data, alert_t *alert) { int index, ret; gchar *item, *quoted_comment; gchar *quoted_name; filter_t filter; assert (current_credentials.uuid); sql_begin_immediate (); if (acl_user_may ("create_alert") == 0) { sql_rollback (); return 99; } ret = check_alert_params (event, condition, method); if (ret) { sql_rollback (); return ret; } filter = 0; if (event != EVENT_NEW_SECINFO && event != EVENT_UPDATED_SECINFO && filter_id && strcmp (filter_id, "0")) { char *type; if (find_filter_with_permission (filter_id, &filter, "get_filters")) { sql_rollback (); return -1; } if (filter == 0) { sql_rollback (); return 3; } /* Filter type must be result if specified. */ type = sql_string ("SELECT type FROM filters WHERE id = %llu;", filter); if (type && strcasecmp (type, "result")) { free (type); sql_rollback (); return 4; } free (type); } if (resource_with_name_exists (name, "alert", 0)) { sql_rollback (); return 1; } quoted_name = sql_quote (name); quoted_comment = sql_quote (comment ?: ""); sql ("INSERT INTO alerts (uuid, owner, name, comment, event, condition," " method, filter, active, creation_time, modification_time)" " VALUES (make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '%s', %i, %i, %i, %llu, %i, m_now (), m_now ());", current_credentials.uuid, quoted_name, quoted_comment, event, condition, method, filter, active ? strcmp (active, "0") : 1); g_free (quoted_comment); g_free (quoted_name); *alert = sql_last_insert_id (); index = 0; while ((item = (gchar*) g_ptr_array_index (condition_data, index++))) { int validation_result; gchar *data_name = sql_quote (item); gchar *data = sql_quote (item + strlen (item) + 1); validation_result = validate_alert_condition_data (data_name, data, condition); if (validation_result) { g_free (data_name); g_free (data); sql_rollback (); switch (validation_result) { case 1: return 5; case 2: return 6; case 3: return 9; default: return -1; } } sql ("INSERT INTO alert_condition_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", *alert, data_name, data); g_free (data_name); g_free (data); } index = 0; while ((item = (gchar*) g_ptr_array_index (event_data, index++))) { int validation_result; gchar *data_name = sql_quote (item); gchar *data = sql_quote (item + strlen (item) + 1); validation_result = validate_alert_event_data (data_name, data, event); if (validation_result) { g_free (data_name); g_free (data); sql_rollback (); switch (validation_result) { case 1: return 31; case 2: return 32; default: return -1; } } sql ("INSERT INTO alert_event_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", *alert, data_name, data); g_free (data_name); g_free (data); } index = 0; while ((item = (gchar*) g_ptr_array_index (method_data, index++))) { gchar *data_name, *data; data_name = sql_quote (item); data = sql_quote (item + strlen (item) + 1); ret = validate_email_data (method, data_name, &data, 0); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_scp_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_send_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_smb_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_sourcefire_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_tippingpoint_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_vfire_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } sql ("INSERT INTO alert_method_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", *alert, data_name, data); g_free (data_name); g_free (data); } sql_commit (); return 0; } /** * @brief Create an alert from an existing alert. * * @param[in] name Name of new alert. NULL to copy from existing. * @param[in] comment Comment on new alert. NULL to copy from * existing. * @param[in] alert_id UUID of existing alert. * @param[out] new_alert New alert. * * @return 0 success, 1 alert exists already, 2 failed to find existing * alert, 99 permission denied, -1 error. */ int copy_alert (const char* name, const char* comment, const char* alert_id, alert_t* new_alert) { int ret; alert_t new, old; assert (current_credentials.uuid); if (alert_id == NULL) return -1; sql_begin_immediate (); ret = copy_resource_lock ("alert", name, comment, alert_id, "event, condition, method, filter, active", 1, &new, &old); if (ret) { sql_rollback (); return ret; } /* Copy the alert condition data */ sql ("INSERT INTO alert_condition_data (alert, name, data)" " SELECT %llu, name, data FROM alert_condition_data" " WHERE alert = %llu;", new, old); /* Copy the alert event data */ sql ("INSERT INTO alert_event_data (alert, name, data)" " SELECT %llu, name, data FROM alert_event_data" " WHERE alert = %llu;", new, old); /* Copy the alert method data */ sql ("INSERT INTO alert_method_data (alert, name, data)" " SELECT %llu, name, data FROM alert_method_data" " WHERE alert = %llu;", new, old); sql_commit (); if (new_alert) *new_alert = new; return 0; } /** * @brief Modify an alert. * * @param[in] alert_id UUID of alert. * @param[in] name Name of alert. * @param[in] comment Comment on alert. * @param[in] filter_id Filter. * @param[in] active Whether the alert is active. NULL to leave it * at the current value. * @param[in] event Type of event. * @param[in] event_data Type-specific event data. * @param[in] condition Event condition. * @param[in] condition_data Condition-specific data. * @param[in] method Escalation method. * @param[in] method_data Data for escalation method. * * @return 0 success, 1 failed to find alert, 2 alert with new name exists, * 3 alert_id required, 4 failed to find filter, 5 filter type must be * result if specified, 6 Provided email address not valid, * 7 unexpected condition data name, 8 syntax error in condition data, * 9 email subject too long, 10 email message too long, 11 failed to * find filter for condition, 12 error in Send host, 13 error in Send * port, 14 failed to find report format for Send method, 15 error in * SCP host, 17 failed to find report format for SCP method, 18 error * in SCP credential, 19 error in SCP path, 20 method does not match * event, 21 condition does not match event, 31 unexpected event data * name, 32 syntax error in event data, 40 invalid SMB credential * , 41 invalid SMB share path, 42 invalid SMB file path, * 43 SMB file path contains dot, * 50 invalid TippingPoint credential, 51 invalid TippingPoint hostname, * 52 invalid TippingPoint certificate, 53 invalid TippingPoint TLS * workaround setting, 60 recipient credential not found, 61 invalid * recipient credential type, 70 vFire credential not found, * 71 invalid vFire credential type, * 99 permission denied, -1 internal error. */ int modify_alert (const char *alert_id, const char *name, const char *comment, const char *filter_id, const char *active, event_t event, GPtrArray *event_data, alert_condition_t condition, GPtrArray *condition_data, alert_method_t method, GPtrArray *method_data) { int index, ret; gchar *quoted_name, *quoted_comment, *item; alert_t alert; filter_t filter; if (alert_id == NULL) return 3; sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_alert") == 0) { sql_rollback (); return 99; } ret = check_alert_params (event, condition, method); if (ret) { sql_rollback (); return ret; } alert = 0; if (find_alert_with_permission (alert_id, &alert, "modify_alert")) { sql_rollback (); return -1; } if (alert == 0) { sql_rollback (); return 1; } /* Check whether an alert with the same name exists already. */ if (resource_with_name_exists (name, "alert", alert)) { sql_rollback (); return 2; } /* Check filter. */ filter = 0; if (event != EVENT_NEW_SECINFO && event != EVENT_UPDATED_SECINFO && filter_id && strcmp (filter_id, "0")) { char *type; if (find_filter_with_permission (filter_id, &filter, "get_filters")) { sql_rollback (); return -1; } if (filter == 0) { sql_rollback (); return 4; } /* Filter type must be report if specified. */ type = sql_string ("SELECT type FROM filters WHERE id = %llu;", filter); if (type && strcasecmp (type, "result")) { free (type); sql_rollback (); return 5; } free (type); } quoted_name = sql_quote (name ?: ""); quoted_comment = sql_quote (comment ? comment : ""); sql ("UPDATE alerts SET" " name = '%s'," " comment = '%s'," " filter = %llu," " active = %s," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, quoted_comment, filter, active ? (strcmp (active, "0") ? "1" : "0") : "active", alert); g_free (quoted_comment); g_free (quoted_name); /* Modify alert event */ if (event != EVENT_ERROR) { sql ("UPDATE alerts set event = %i WHERE id = %llu", event, alert); sql ("DELETE FROM alert_event_data WHERE alert = %llu", alert); index = 0; while ((item = (gchar*) g_ptr_array_index (event_data, index++))) { int validation_result; gchar *data_name = sql_quote (item); gchar *data = sql_quote (item + strlen (item) + 1); validation_result = validate_alert_event_data (data_name, data, event); if (validation_result) { g_free (data_name); g_free (data); sql_rollback (); switch (validation_result) { case 1: return 31; case 2: return 32; default: return -1; } } sql ("INSERT INTO alert_event_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", alert, data_name, data); g_free (data_name); g_free (data); } } /* Modify alert condition */ if (condition != ALERT_CONDITION_ERROR) { sql ("UPDATE alerts set condition = %i WHERE id = %llu", condition, alert); sql ("DELETE FROM alert_condition_data WHERE alert = %llu", alert); index = 0; while ((item = (gchar*) g_ptr_array_index (condition_data, index++))) { int validation_result; gchar *data_name = sql_quote (item); gchar *data = sql_quote (item + strlen (item) + 1); validation_result = validate_alert_condition_data (data_name, data, condition); if (validation_result) { g_free (data_name); g_free (data); sql_rollback (); switch (validation_result) { case 1: return 7; case 2: return 8; case 3: return 11; default: return -1; } } sql ("INSERT INTO alert_condition_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", alert, data_name, data); g_free (data_name); g_free (data); } } /* Modify alert method */ if (method != ALERT_METHOD_ERROR) { sql ("UPDATE alerts set method = %i WHERE id = %llu", method, alert); sql ("DELETE FROM alert_method_data WHERE alert = %llu", alert); index = 0; while ((item = (gchar*) g_ptr_array_index (method_data, index++))) { gchar *data_name, *data; data_name = sql_quote (item); data = sql_quote (item + strlen (item) + 1); ret = validate_email_data (method, data_name, &data, 1); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_scp_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_send_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_smb_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_sourcefire_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_tippingpoint_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } ret = validate_vfire_data (method, data_name, &data); if (ret) { g_free (data_name); g_free (data); sql_rollback (); return ret; } sql ("INSERT INTO alert_method_data (alert, name, data)" " VALUES (%llu, '%s', '%s');", alert, data_name, data); g_free (data_name); g_free (data); } } sql_commit (); return 0; } /** * @brief Delete an alert. * * @param[in] alert_id UUID of alert. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a task refers to the alert, 2 failed * to find target, 99 permission denied, -1 error. */ int delete_alert (const char *alert_id, int ultimate) { alert_t alert = 0; sql_begin_immediate (); if (acl_user_may ("delete_alert") == 0) { sql_rollback (); return 99; } if (find_alert_with_permission (alert_id, &alert, "delete_alert")) { sql_rollback (); return -1; } if (alert == 0) { if (find_trash ("alert", alert_id, &alert)) { sql_rollback (); return -1; } if (alert == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by a task in the trashcan. */ if (sql_int ("SELECT count(*) FROM task_alerts" " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TRASH) ";", alert)) { sql_rollback (); return 1; } permissions_set_orphans ("alert", alert, LOCATION_TRASH); tags_remove_resource ("alert", alert, LOCATION_TRASH); sql ("DELETE FROM alert_condition_data_trash WHERE alert = %llu;", alert); sql ("DELETE FROM alert_event_data_trash WHERE alert = %llu;", alert); sql ("DELETE FROM alert_method_data_trash WHERE alert = %llu;", alert); sql ("DELETE FROM alerts_trash WHERE id = %llu;", alert); sql_commit (); return 0; } if (ultimate == 0) { alert_t trash_alert; if (sql_int ("SELECT count(*) FROM task_alerts" " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TABLE) " AND (SELECT hidden < 2 FROM tasks" " WHERE id = task_alerts.task);", alert)) { sql_rollback (); return 1; } sql ("INSERT INTO alerts_trash" " (uuid, owner, name, comment, event, condition, method, filter," " filter_location, active, creation_time, modification_time)" " SELECT uuid, owner, name, comment, event, condition, method," " filter, " G_STRINGIFY (LOCATION_TABLE) ", active," " creation_time, m_now ()" " FROM alerts WHERE id = %llu;", alert); trash_alert = sql_last_insert_id (); sql ("INSERT INTO alert_condition_data_trash" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_condition_data WHERE alert = %llu;", trash_alert, alert); sql ("INSERT INTO alert_event_data_trash" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_event_data WHERE alert = %llu;", trash_alert, alert); sql ("INSERT INTO alert_method_data_trash" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_method_data WHERE alert = %llu;", trash_alert, alert); /* Update the location of the alert in any trashcan tasks. */ sql ("UPDATE task_alerts" " SET alert = %llu," " alert_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TABLE) ";", trash_alert, alert); permissions_set_locations ("alert", alert, trash_alert, LOCATION_TRASH); tags_set_locations ("alert", alert, trash_alert, LOCATION_TRASH); } else if (sql_int ("SELECT count(*) FROM task_alerts" " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TABLE) ";", alert)) { sql_rollback (); return 1; } else { permissions_set_orphans ("alert", alert, LOCATION_TABLE); tags_remove_resource ("alert", alert, LOCATION_TABLE); } sql ("DELETE FROM alert_condition_data WHERE alert = %llu;", alert); sql ("DELETE FROM alert_event_data WHERE alert = %llu;", alert); sql ("DELETE FROM alert_method_data WHERE alert = %llu;", alert); sql ("DELETE FROM alerts WHERE id = %llu;", alert); sql_commit (); return 0; } /** * @brief Return the UUID of an alert. * * @param[in] alert Alert. * * @return UUID of alert. */ char * alert_uuid (alert_t alert) { return sql_string ("SELECT uuid FROM alerts WHERE id = %llu;", alert); } /** * @brief Return the name of an alert. * * @param[in] alert Alert. * * @return Name of alert. */ static char * alert_name (alert_t alert) { return sql_string ("SELECT name FROM alerts WHERE id = %llu;", alert); } /** * @brief Return the owner of an alert. * * @param[in] alert Alert. * * @return Owner. */ static user_t alert_owner (alert_t alert) { return sql_int64_0 ("SELECT owner FROM alerts WHERE id = %llu;", alert); } /** * @brief Return the UUID of the owner of an alert. * * @param[in] alert Alert. * * @return UUID of owner. */ static char * alert_owner_uuid (alert_t alert) { return sql_string ("SELECT uuid FROM users" " WHERE id = (SELECT owner FROM alerts WHERE id = %llu);", alert); } /** * @brief Return the UUID of the filter of an alert. * * @param[in] alert Alert. * * @return UUID if there's a filter, else NULL. */ static char * alert_filter_id (alert_t alert) { return sql_string ("SELECT" " (CASE WHEN (SELECT filter IS NULL OR filter = 0" " FROM alerts WHERE id = %llu)" " THEN NULL" " ELSE (SELECT uuid FROM filters" " WHERE id = (SELECT filter FROM alerts" " WHERE id = %llu))" " END);", alert, alert); } /** * @brief Return the condition associated with an alert. * * @param[in] alert Alert. * * @return Condition. */ static alert_condition_t alert_condition (alert_t alert) { return sql_int ("SELECT condition FROM alerts WHERE id = %llu;", alert); } /** * @brief Return the method associated with an alert. * * @param[in] alert Alert. * * @return Method. */ static alert_method_t alert_method (alert_t alert) { return sql_int ("SELECT method FROM alerts WHERE id = %llu;", alert); } /** * @brief Return the event associated with an alert. * * @param[in] alert Alert. * * @return Event. */ static event_t alert_event (alert_t alert) { return sql_int ("SELECT event FROM alerts WHERE id = %llu;", alert); } /** * @brief Filter columns for alert iterator. */ #define ALERT_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "event", "condition", "method", \ "filter", NULL } /** * @brief Alert iterator columns. */ #define ALERT_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (alerts), \ { "event", NULL, KEYWORD_TYPE_INTEGER }, \ { "condition", NULL, KEYWORD_TYPE_INTEGER }, \ { "method", NULL, KEYWORD_TYPE_INTEGER }, \ { "filter", NULL, KEYWORD_TYPE_INTEGER }, \ { G_STRINGIFY (LOCATION_TABLE), NULL, KEYWORD_TYPE_INTEGER }, \ { "active", NULL, KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Alert iterator columns for trash case. */ #define ALERT_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (alerts_trash), \ { "event", NULL, KEYWORD_TYPE_INTEGER }, \ { "condition", NULL, KEYWORD_TYPE_INTEGER }, \ { "method", NULL, KEYWORD_TYPE_INTEGER }, \ { "filter", NULL, KEYWORD_TYPE_STRING }, \ { "filter_location", NULL, KEYWORD_TYPE_INTEGER}, \ { "active", NULL, KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count the number of alerts. * * @param[in] get GET params. * * @return Total number of alerts filtered set. */ int alert_count (const get_data_t *get) { static const char *filter_columns[] = ALERT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = ALERT_ITERATOR_COLUMNS; static column_t trash_columns[] = ALERT_ITERATOR_TRASH_COLUMNS; return count ("alert", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Return whether a alert is in use by a task. * * @param[in] alert Alert. * * @return 1 if in use, else 0. */ int alert_in_use (alert_t alert) { return !!sql_int ("SELECT count (*) FROM task_alerts WHERE alert = %llu;", alert); } /** * @brief Return whether a trashcan alert is in use by a task. * * @param[in] alert Alert. * * @return 1 if in use, else 0. */ int trash_alert_in_use (alert_t alert) { return !!sql_int ("SELECT count(*) FROM task_alerts" " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TRASH), alert); } /** * @brief Return whether a alert is writable. * * @param[in] alert Alert. * * @return 1 if writable, else 0. */ int alert_writable (alert_t alert) { return 1; } /** * @brief Return whether a trashcan alert is writable. * * @param[in] alert Alert. * * @return 1 if writable, else 0. */ int trash_alert_writable (alert_t alert) { return 1; } /** * @brief Initialise an alert iterator, including observed alerts. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find alert, 2 failed to find filter (filt_id), * -1 error. */ int init_alert_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = ALERT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = ALERT_ITERATOR_COLUMNS; static column_t trash_columns[] = ALERT_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "alert", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Return the event from an alert iterator. * * @param[in] iterator Iterator. * * @return Event of the alert or NULL if iteration is complete. */ int alert_iterator_event (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT); return ret; } /** * @brief Return the condition from an alert iterator. * * @param[in] iterator Iterator. * * @return Condition of the alert or NULL if iteration is complete. */ int alert_iterator_condition (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); return ret; } /** * @brief Return the method from an alert iterator. * * @param[in] iterator Iterator. * * @return Method of the alert or NULL if iteration is complete. */ int alert_iterator_method (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2); return ret; } /** * @brief Return the filter from an alert iterator. * * @param[in] iterator Iterator. * * @return Filter of the alert or NULL if iteration is complete. */ static filter_t alert_iterator_filter (iterator_t* iterator) { if (iterator->done) return -1; return (filter_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 3); } /** * @brief Return the filter UUID from an alert iterator. * * @param[in] iterator Iterator. * * @return UUID of filter of the alert or NULL if iteration is complete. */ char * alert_iterator_filter_uuid (iterator_t* iterator) { filter_t filter; if (iterator->done) return NULL; filter = alert_iterator_filter (iterator); if (filter) { if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4) == LOCATION_TABLE) return filter_uuid (filter); return trash_filter_uuid (filter); } return NULL; } /** * @brief Return the filter name from an alert iterator. * * @param[in] iterator Iterator. * * @return Name of filter of the alert or NULL if iteration is complete. */ char * alert_iterator_filter_name (iterator_t* iterator) { filter_t filter; if (iterator->done) return NULL; filter = alert_iterator_filter (iterator); if (filter) { if (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4) == LOCATION_TABLE) return filter_name (filter); return trash_filter_name (filter); } return NULL; } /** * @brief Return the location of an alert iterator filter. * * @param[in] iterator Iterator. * * @return 0 in table, 1 in trash. */ int alert_iterator_filter_trash (iterator_t* iterator) { if (iterator->done) return 0; if (alert_iterator_filter (iterator) && (iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4) == LOCATION_TRASH)) return 1; return 0; } /** * @brief Return the filter readable state from an alert iterator. * * @param[in] iterator Iterator. * * @return Whether filter is readable. */ int alert_iterator_filter_readable (iterator_t* iterator) { filter_t filter; if (iterator->done) return 0; filter = alert_iterator_filter (iterator); if (filter) { char *uuid; uuid = alert_iterator_filter_uuid (iterator); if (uuid) { int readable; readable = acl_user_has_access_uuid ("filter", uuid, "get_filters", iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4) == LOCATION_TRASH); free (uuid); return readable; } } return 0; } /** * @brief Return the active state from an alert. * * @param[in] iterator Iterator. * * @return Method of the alert or NULL if iteration is complete. */ int alert_iterator_active (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5); return ret; } /** * @brief Initialise an alert data iterator. * * @param[in] iterator Iterator. * @param[in] alert Alert. * @param[in] trash Whether to iterate over trashcan alert data. * @param[in] table Type of data: "condition", "event" or "method", * corresponds to substring of the table to select * from. */ void init_alert_data_iterator (iterator_t *iterator, alert_t alert, int trash, const char *table) { init_iterator (iterator, "SELECT name, data FROM alert_%s_data%s" " WHERE alert = %llu;", table, trash ? "_trash" : "", alert); } /** * @brief Return the name from an alert data iterator. * * @param[in] iterator Iterator. * * @return Name of the alert data or NULL if iteration is complete. */ const char* alert_data_iterator_name (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, 0); return ret; } /** * @brief Return the data from an alert data iterator. * * @param[in] iterator Iterator. * * * @return Data of the alert data or NULL if iteration is complete. */ const char* alert_data_iterator_data (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, 1); return ret; } /** * @brief Return data associated with an alert. * * @param[in] alert Alert. * @param[in] type Type of data: "condition", "event" or "method". * @param[in] name Name of the data. * * @return Freshly allocated data if it exists, else NULL. */ char * alert_data (alert_t alert, const char *type, const char *name) { gchar *quoted_name; char *data; assert (strcmp (type, "condition") == 0 || strcmp (type, "event") == 0 || strcmp (type, "method") == 0); quoted_name = sql_quote (name); data = sql_string ("SELECT data FROM alert_%s_data" " WHERE alert = %llu AND name = '%s';", type, alert, quoted_name); g_free (quoted_name); return data; } /** * @brief Check whether an alert applies to a task. * * @param[in] alert Alert. * @param[in] task Task. * * @return 1 if applies, else 0. */ static int alert_applies_to_task (alert_t alert, task_t task) { return sql_int ("SELECT EXISTS (SELECT * FROM task_alerts" " WHERE task = %llu" " AND alert = %llu);", task, alert); } /** * @brief Initialise a task alert iterator. * * @param[in] iterator Iterator. * @param[in] task Task. */ void init_task_alert_iterator (iterator_t* iterator, task_t task) { gchar *owned_clause, *with_clause; get_data_t get; array_t *permissions; assert (task); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_alerts")); owned_clause = acl_where_owned ("alert", &get, 0, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT alerts.id, alerts.uuid, alerts.name" " FROM alerts, task_alerts" " WHERE task_alerts.task = %llu" " AND task_alerts.alert = alerts.id" " AND %s;", with_clause ? with_clause : "", task, owned_clause); g_free (with_clause); g_free (owned_clause); } /** * @brief Get the UUID from a task alert iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_alert_iterator_uuid, 1); /** * @brief Get the name from a task alert iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_alert_iterator_name, 2); /** * @brief Initialise an event alert iterator. * * @param[in] iterator Iterator. * @param[in] event Event. */ static void init_event_alert_iterator (iterator_t* iterator, event_t event) { gchar *owned_clause, *with_clause; get_data_t get; array_t *permissions; assert (event); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_alerts")); owned_clause = acl_where_owned ("alert", &get, 0, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT alerts.id, alerts.active" " FROM alerts" " WHERE event = %i" " AND %s;", with_clause ? with_clause : "", event, owned_clause); g_free (with_clause); g_free (owned_clause); } /** * @brief Get the alert from a event alert iterator. * * @param[in] iterator Iterator. * * @return alert. */ static alert_t event_alert_iterator_alert (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, 0); } /** * @brief Get the active state from an event alert iterator. * * @param[in] iterator Iterator. * * @return Active state. */ static int event_alert_iterator_active (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, 1); return ret; } /** * @brief Write the content of a plain text email to a stream. * * @param[in] content_file Stream to write the email content to. * @param[in] to_address Address to send to. * @param[in] from_address Address to send to. * @param[in] subject Subject of email. * @param[in] body Body of email. * @param[in] attachment Attachment in line broken base64, or NULL. * @param[in] attachment_type Attachment MIME type, or NULL. * @param[in] attachment_name Base file name of the attachment, or NULL. * @param[in] attachment_extension Attachment file extension, or NULL. * * @return 0 success, -1 error. */ static int email_write_content (FILE *content_file, const char *to_address, const char *from_address, const char *subject, const char *body, const gchar *attachment, const char *attachment_type, const char *attachment_name, const char *attachment_extension) { if (fprintf (content_file, "To: %s\n" "From: %s\n" "Subject: %s\n" "%s%s%s" "\n" "%s" "%s\n", to_address, from_address ? from_address : "automated@openvas.org", subject, (attachment ? "MIME-Version: 1.0\n" "Content-Type: multipart/mixed;" " boundary=\"" : "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n"), /* @todo Future callers may give email containing this string. */ (attachment ? "=-=-=-=-=" : ""), (attachment ? "\"\n" : ""), (attachment ? "--=-=-=-=-=\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Content-Disposition: inline\n" "\n" : ""), body) < 0) { g_warning ("%s: output error", __func__); return -1; } if (attachment) { int len; if (fprintf (content_file, "--=-=-=-=-=\n" "Content-Type: %s\n" "Content-Disposition: attachment;" " filename=\"%s.%s\"\n" "Content-Transfer-Encoding: base64\n" "Content-Description: Report\n\n", attachment_type, attachment_name, attachment_extension) < 0) { g_warning ("%s: output error", __func__); return -1; } len = strlen (attachment); while (len) if (len > 72) { if (fprintf (content_file, "%.*s\n", 72, attachment) < 0) { g_warning ("%s: output error", __func__); return -1; } attachment += 72; len -= 72; } else { if (fprintf (content_file, "%s\n", attachment) < 0) { g_warning ("%s: output error", __func__); return -1; } break; } if (fprintf (content_file, "--=-=-=-=-=--\n") < 0) { g_warning ("%s: output error", __func__); return -1; } } while (fflush (content_file)) if (errno == EINTR) continue; else { g_warning ("%s", strerror (errno)); return -1; } return 0; } /** * @brief Create a PGP encrypted email from a plain text one. * * @param[in] plain_file Stream to read the plain text email from. * @param[in] encrypted_file Stream to write the encrypted email to. * @param[in] public_key Recipient public key to use for encryption. * @param[in] to_address Email address to send to. * @param[in] from_address Email address to use as sender. * @param[in] subject Subject of email. * * @return 0 success, -1 error. */ static int email_encrypt_gpg (FILE *plain_file, FILE *encrypted_file, const char *public_key, const char *to_address, const char *from_address, const char *subject) { // Headers and metadata parts if (fprintf (encrypted_file, "To: %s\n" "From: %s\n" "Subject: %s\n" "MIME-Version: 1.0\n" "Content-Type: multipart/encrypted;\n" " protocol=\"application/pgp-encrypted\";\n" " boundary=\"=-=-=-=-=\"\n" "\n" "--=-=-=-=-=\n" "Content-Type: application/pgp-encrypted\n" "Content-Description: PGP/MIME version identification\n" "\n" "Version: 1\n" "\n" "--=-=-=-=-=\n" "Content-Type: application/octet-stream\n" "Content-Description: OpenPGP encrypted message\n" "Content-Disposition: inline; filename=\"encrypted.asc\"\n" "\n", to_address, from_address ? from_address : "automated@openvas.org", subject) < 0) { g_warning ("%s: output error at headers", __func__); return -1; } // Encrypted message if (gvm_pgp_pubkey_encrypt_stream (plain_file, encrypted_file, to_address, public_key, -1)) { return -1; } // End of message if (fprintf (encrypted_file, "\n" "--=-=-=-=-=--\n") < 0) { g_warning ("%s: output error at end of message", __func__); return -1; } while (fflush (encrypted_file)) if (errno == EINTR) continue; else { g_warning ("%s", strerror (errno)); return -1; } return 0; } /** * @brief Create an S/MIME encrypted email from a plain text one. * * @param[in] plain_file Stream to read the plain text email from. * @param[in] encrypted_file Stream to write the encrypted email to. * @param[in] certificate Recipient certificate chain for encryption. * @param[in] to_address Email address to send to. * @param[in] from_address Email address to use as sender. * @param[in] subject Subject of email. * * @return 0 success, -1 error. */ static int email_encrypt_smime (FILE *plain_file, FILE *encrypted_file, const char *certificate, const char *to_address, const char *from_address, const char *subject) { // Headers and metadata parts if (fprintf (encrypted_file, "To: %s\n" "From: %s\n" "Subject: %s\n" "Content-Type: application/x-pkcs7-mime;" " smime-type=enveloped-data; name=\"smime.p7m\"\n" "Content-Disposition: attachment; filename=\"smime.p7m\"\n" "Content-Transfer-Encoding: base64\n" "\n", to_address, from_address ? from_address : "automated@openvas.org", subject) < 0) { g_warning ("%s: output error at headers", __func__); return -1; } // Encrypted message if (gvm_smime_encrypt_stream (plain_file, encrypted_file, to_address, certificate, -1)) { g_warning ("%s: encryption failed", __func__); return -1; } // End of message if (fprintf (encrypted_file, "\n") < 0) { g_warning ("%s: output error at end of message", __func__); return -1; } while (fflush (encrypted_file)) if (errno == EINTR) continue; else { g_warning ("%s", strerror (errno)); return -1; } return 0; } /** * @brief Send an email. * * @param[in] to_address Address to send to. * @param[in] from_address Address to send to. * @param[in] subject Subject of email. * @param[in] body Body of email. * @param[in] attachment Attachment in line broken base64, or NULL. * @param[in] attachment_type Attachment MIME type, or NULL. * @param[in] attachment_name Base file name of the attachment, or NULL. * @param[in] attachment_extension Attachment file extension, or NULL. * @param[in] recipient_credential Optional credential to use for encryption. * * @return 0 success, -1 error. */ static int email (const char *to_address, const char *from_address, const char *subject, const char *body, const gchar *attachment, const char *attachment_type, const char *attachment_name, const char *attachment_extension, credential_t recipient_credential) { int ret, content_fd, args_fd; gchar *command; GError *error = NULL; char content_file_name[] = "/tmp/gvmd-content-XXXXXX"; char args_file_name[] = "/tmp/gvmd-args-XXXXXX"; gchar *sendmail_args; FILE *content_file; content_fd = mkstemp (content_file_name); if (content_fd == -1) { g_warning ("%s: mkstemp: %s", __func__, strerror (errno)); return -1; } g_debug (" EMAIL to %s from %s subject: %s, body: %s", to_address, from_address, subject, body); content_file = fdopen (content_fd, "w"); if (content_file == NULL) { g_warning ("%s: Could not open content file: %s", __func__, strerror (errno)); close (content_fd); return -1; } if (recipient_credential) { iterator_t iterator; init_credential_iterator_one (&iterator, recipient_credential); if (next (&iterator)) { const char *type = credential_iterator_type (&iterator); const char *public_key = credential_iterator_public_key (&iterator); const char *certificate = credential_iterator_certificate (&iterator); char plain_file_name[] = "/tmp/gvmd-plain-XXXXXX"; int plain_fd; FILE *plain_file; // Create plain text message plain_fd = mkstemp (plain_file_name); if (plain_fd == -1) { g_warning ("%s: mkstemp for plain text file: %s", __func__, strerror (errno)); fclose (content_file); unlink (content_file_name); cleanup_iterator (&iterator); return -1; } plain_file = fdopen (plain_fd, "w+"); if (plain_file == NULL) { g_warning ("%s: Could not open plain text file: %s", __func__, strerror (errno)); fclose (content_file); unlink (content_file_name); close (plain_fd); unlink (plain_file_name); cleanup_iterator (&iterator); return -1; } if (email_write_content (plain_file, to_address, from_address, subject, body, attachment, attachment_type, attachment_name, attachment_extension)) { fclose (content_file); unlink (content_file_name); fclose (plain_file); unlink (plain_file_name); cleanup_iterator (&iterator); return -1; } rewind (plain_file); // Create encrypted email if (strcmp (type, "pgp") == 0) { ret = email_encrypt_gpg (plain_file, content_file, public_key, to_address, from_address, subject); fclose (plain_file); unlink (plain_file_name); if (ret) { g_warning ("%s: PGP encryption failed", __func__); fclose (content_file); unlink (content_file_name); cleanup_iterator (&iterator); return -1; } } else if (strcmp (type, "smime") == 0) { ret = email_encrypt_smime (plain_file, content_file, certificate, to_address, from_address, subject); fclose (plain_file); unlink (plain_file_name); if (ret) { g_warning ("%s: S/MIME encryption failed", __func__); fclose (content_file); unlink (content_file_name); cleanup_iterator (&iterator); return -1; } } else { g_warning ("%s: Invalid recipient credential type", __func__); fclose (content_file); unlink (content_file_name); fclose (plain_file); unlink (plain_file_name); cleanup_iterator (&iterator); return -1; } } cleanup_iterator (&iterator); } else { if (email_write_content (content_file, to_address, from_address, subject, body, attachment, attachment_type, attachment_name, attachment_extension)) { fclose (content_file); return -1; } } args_fd = mkstemp (args_file_name); if (args_fd == -1) { g_warning ("%s: mkstemp: %s", __func__, strerror (errno)); fclose (content_file); return -1; } sendmail_args = g_strdup_printf ("%s %s", from_address, to_address); g_file_set_contents (args_file_name, sendmail_args, strlen (sendmail_args), &error); g_free (sendmail_args); if (error) { g_warning ("%s", error->message); g_error_free (error); fclose (content_file); close (args_fd); return -1; } command = g_strdup_printf ("read FROM TO < %s;" " /usr/sbin/sendmail -f \"$FROM\" \"$TO\" < %s" " > /dev/null 2>&1", args_file_name, content_file_name); g_debug (" command: %s", command); ret = system (command); if ((ret == -1) || WEXITSTATUS (ret)) { g_warning ("%s: system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); g_free (command); fclose (content_file); close (args_fd); unlink (content_file_name); unlink (args_file_name); return -1; } g_free (command); fclose (content_file); close (args_fd); unlink (content_file_name); unlink (args_file_name); return 0; } /** * @brief GET an HTTP resource. * * @param[in] url URL. * * @return 0 success, -1 error. */ static int http_get (const char *url) { int ret; gchar *standard_out = NULL; gchar *standard_err = NULL; gint exit_status; gchar **cmd; g_debug (" HTTP_GET %s", url); cmd = (gchar **) g_malloc (5 * sizeof (gchar *)); cmd[0] = g_strdup ("/usr/bin/wget"); cmd[1] = g_strdup ("-O"); cmd[2] = g_strdup ("-"); cmd[3] = g_strdup (url); cmd[4] = NULL; g_debug ("%s: Spawning in /tmp/: %s %s %s %s", __func__, cmd[0], cmd[1], cmd[2], cmd[3]); if ((g_spawn_sync ("/tmp/", cmd, NULL, /* Environment. */ G_SPAWN_SEARCH_PATH, NULL, /* Setup function. */ NULL, &standard_out, &standard_err, &exit_status, NULL) == FALSE) || (WIFEXITED (exit_status) == 0) || WEXITSTATUS (exit_status)) { g_debug ("%s: wget failed: %d (WIF %i, WEX %i)", __func__, exit_status, WIFEXITED (exit_status), WEXITSTATUS (exit_status)); g_debug ("%s: stdout: %s", __func__, standard_out); g_debug ("%s: stderr: %s", __func__, standard_err); ret = -1; } else { if (strlen (standard_out) > 80) standard_out[80] = '\0'; g_debug (" HTTP_GET %s: %s", url, standard_out); ret = 0; } g_free (cmd[0]); g_free (cmd[1]); g_free (cmd[2]); g_free (cmd[3]); g_free (cmd[4]); g_free (cmd); g_free (standard_out); g_free (standard_err); return ret; } /** * @brief Initialize common files and variables for an alert script. * * The temporary file / dir parameters will be modified by mkdtemp / mkstemp * to contain the actual path. * The extra data is meant for data that should not be logged like passwords. * * @param[in] report_filename Filename for the report or NULL for default. * @param[in] report Report that should be sent. * @param[in] report_size Size of the report. * @param[in] extra_content Optional extra data, e.g. credentials * @param[in] extra_size Optional extra data length * @param[in,out] report_dir Template for temporary report directory * @param[out] report_path Pointer to store path to report file at * @param[out] error_path Pointer to temporary file path for error messages * @param[out] extra_path Pointer to temporary extra data file path * * @return 0 success, -1 error. */ static int alert_script_init (const char *report_filename, const char* report, size_t report_size, const char *extra_content, size_t extra_size, char *report_dir, gchar **report_path, gchar **error_path, gchar **extra_path) { GError *error; /* Create temp directory */ if (mkdtemp (report_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return -1; } /* Create report file */ *report_path = g_strdup_printf ("%s/%s", report_dir, report_filename ? report_filename : "report"); error = NULL; g_file_set_contents (*report_path, report, report_size, &error); if (error) { g_warning ("%s: could not write report: %s", __func__, error->message); g_error_free (error); g_free (*report_path); gvm_file_remove_recurse (report_dir); return -1; } /* Create error file */ *error_path = g_strdup_printf ("%s/error_XXXXXX", report_dir); if (mkstemp (*error_path) == -1) { g_warning ("%s: mkstemp for error output failed", __func__); gvm_file_remove_recurse (report_dir); g_free (*report_path); g_free (*error_path); return -1; } /* Create extra data file */ if (extra_content) { *extra_path = g_strdup_printf ("%s/extra_XXXXXX", report_dir); if (mkstemp (*extra_path) == -1) { g_warning ("%s: mkstemp for extra data failed", __func__); gvm_file_remove_recurse (report_dir); g_free (*report_path); g_free (*error_path); g_free (*extra_path); return -1; } error = NULL; g_file_set_contents (*extra_path, extra_content, extra_size, &error); if (error) { g_warning ("%s: could not write extra data: %s", __func__, error->message); g_error_free (error); gvm_file_remove_recurse (report_dir); g_free (*report_path); g_free (*error_path); g_free (*extra_path); return -1; } } else *extra_path = NULL; return 0; } /** * @brief Execute the alert script. * * @param[in] alert_id UUID of the alert. * @param[in] command_args Args for the "alert" script. * @param[in] report_path Path to temporary file containing the report * @param[in] report_dir Temporary directory for the report * @param[in] error_path Path to the script error message file * @param[in] extra_path Path to the extra data file * @param[out] message Custom error message generated by the script * * @return 0 success, -1 error, -5 alert script failed. */ static int alert_script_exec (const char *alert_id, const char *command_args, const char *report_path, const char *report_dir, const char *error_path, const char *extra_path, gchar **message) { gchar *script, *script_dir; /* Setup script file name. */ script_dir = g_build_filename (GVMD_DATA_DIR, "global_alert_methods", alert_id, NULL); script = g_build_filename (script_dir, "alert", NULL); if (!gvm_file_is_readable (script)) { g_warning ("%s: Failed to find alert script: %s", __func__, script); g_free (script); g_free (script_dir); return -1; } /* Run the script */ { gchar *command; char *previous_dir; int ret; /* Change into the script directory. */ previous_dir = getcwd (NULL, 0); if (previous_dir == NULL) { g_warning ("%s: Failed to getcwd: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } if (chdir (script_dir)) { g_warning ("%s: Failed to chdir: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } g_free (script_dir); /* Call the script. */ if (extra_path) command = g_strdup_printf ("%s %s %s %s" " > /dev/null 2> %s", script, command_args, extra_path, report_path, error_path); else command = g_strdup_printf ("%s %s %s" " > /dev/null 2> %s", script, command_args, report_path, error_path); g_free (script); g_debug (" command: %s", command); if (geteuid () == 0) { pid_t pid; struct passwd *nobody; /* Run the command with lower privileges in a fork. */ nobody = getpwnam ("nobody"); if ((nobody == NULL) || chown (report_dir, nobody->pw_uid, nobody->pw_gid) || chown (report_path, nobody->pw_uid, nobody->pw_gid) || chown (error_path, nobody->pw_uid, nobody->pw_gid) || (extra_path && chown (extra_path, nobody->pw_uid, nobody->pw_gid))) { g_warning ("%s: Failed to set permissions for user nobody: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (command); return -1; } pid = fork (); switch (pid) { case 0: { /* Child. Drop privileges, run command, exit. */ cleanup_manage_process (FALSE); proctitle_set ("gvmd: Running alert script"); if (setgroups (0,NULL)) { g_warning ("%s (child): setgroups: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setgid (nobody->pw_gid)) { g_warning ("%s (child): setgid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setuid (nobody->pw_uid)) { g_warning ("%s (child): setuid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } ret = system (command); /* * Check shell command exit status, assuming 0 means success. */ if (ret == -1) { g_warning ("%s (child):" " system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); exit (EXIT_FAILURE); } else if (ret != 0) { GError *error; if (g_file_get_contents (error_path, message, NULL, &error) == FALSE) { g_warning ("%s: failed to test error message: %s", __func__, error->message); g_error_free (error); if (message) g_free (*message); exit (EXIT_FAILURE); } if (message == NULL) exit (EXIT_FAILURE); else if (*message == NULL || strcmp (*message, "") == 0) { g_free (*message); *message = g_strdup_printf ("Exited with code %d.", WEXITSTATUS (ret)); if (g_file_set_contents (error_path, *message, strlen (*message), &error) == FALSE) { g_warning ("%s: failed to write error message:" " %s", __func__, error->message); g_error_free (error); g_free (*message); exit (EXIT_FAILURE); } } g_free (*message); exit (2); } exit (EXIT_SUCCESS); } case -1: /* Parent when error. */ g_warning ("%s: Failed to fork: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; break; default: { int status; /* Parent on success. Wait for child, and check result. */ while (waitpid (pid, &status, 0) < 0) { if (errno == ECHILD) { g_warning ("%s: Failed to get child exit status", __func__); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } if (errno == EINTR) continue; g_warning ("%s: wait: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } if (WIFEXITED (status)) switch (WEXITSTATUS (status)) { case EXIT_SUCCESS: break; case 2: // script failed if (message) { GError *error = NULL; if (g_file_get_contents (error_path, message, NULL, &error) == FALSE) { g_warning ("%s: failed to get error message: %s", __func__, error->message); g_error_free (error); } if (strcmp (*message, "") == 0) { g_free (*message); *message = NULL; } } if (chdir (previous_dir)) g_warning ("%s: chdir failed", __func__); return -5; case EXIT_FAILURE: default: g_warning ("%s: child failed, %s", __func__, command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } else { g_warning ("%s: child failed, %s", __func__, command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } /* Child succeeded, continue to process result. */ break; } } } else { /* Just run the command as the current user. */ ret = system (command); /* Ignore the shell command exit status, because we've not * specified what it must be in the past. */ if (ret == -1) { g_warning ("%s: system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } else if (ret) { if (message) { GError *error = NULL; if (g_file_get_contents (error_path, message, NULL, &error) == FALSE) { g_warning ("%s: failed to get error message: %s", __func__, error->message); g_error_free (error); } if (strcmp (*message, "") == 0) { g_free (*message); *message = NULL; } if (*message == NULL) { *message = g_strdup_printf ("Exited with code %d.", WEXITSTATUS (ret)); } } g_free (previous_dir); g_free (command); return -5; } } g_free (command); /* Change back to the previous directory. */ if (chdir (previous_dir)) { g_warning ("%s: Failed to chdir back: %s", __func__, strerror (errno)); g_free (previous_dir); return -1; } g_free (previous_dir); } return 0; } /** * @brief Write data to a file for use by an alert script. * * @param[in] directory Base directory to create the file in * @param[in] filename Filename without directory * @param[in] content The file content * @param[in] content_size Size of the file content * @param[in] description Short file description for error messages * @param[out] file_path Return location of combined file path * * @return 0 success, -1 error */ static int alert_write_data_file (const char *directory, const char *filename, const char *content, gsize content_size, const char *description, gchar **file_path) { gchar *path; GError *error; if (file_path) *file_path = NULL; /* Setup extra data file */ path = g_build_filename (directory, filename, NULL); error = NULL; if (g_file_set_contents (path, content, content_size, &error) == FALSE) { g_warning ("%s: Failed to write %s to file: %s", __func__, description ? description : "extra data", error->message); g_free (path); return -1; } if (geteuid () == 0) { struct passwd *nobody; /* Set the owner for the extra data file like the other * files handled by alert_script_exec, to be able to * run the command with lower privileges in a fork. */ nobody = getpwnam ("nobody"); if ((nobody == NULL) || chown (path, nobody->pw_uid, nobody->pw_gid)) { g_warning ("%s: Failed to set permissions for user nobody: %s", __func__, strerror (errno)); g_free (path); return -1; } } if (file_path) *file_path = path; return 0; } /** * @brief Clean up common files and variables for running alert script. * * @param[in] report_dir The temporary directory. * @param[in] report_path The temporary report file path to free. * @param[in] error_path The temporary error file path to free. * @param[in] extra_path The temporary extra data file path to free. * * @return 0 success, -1 error. */ static int alert_script_cleanup (const char *report_dir, gchar *report_path, gchar *error_path, gchar *extra_path) { gvm_file_remove_recurse (report_dir); g_free (report_path); g_free (error_path); g_free (extra_path); return 0; } /** * @brief Run an alert's "alert" script with one file of extra data. * * @param[in] alert_id ID of alert. * @param[in] command_args Args for the "alert" script. * @param[in] report_filename Optional report file name, default: "report" * @param[in] report Report that should be sent. * @param[in] report_size Size of the report. * @param[in] extra_content Optional extra data like passwords * @param[in] extra_size Size of the report. * @param[out] message Custom error message of the script. * * @return 0 success, -1 error, -5 alert script failed. */ static int run_alert_script (const char *alert_id, const char *command_args, const char *report_filename, const char *report, size_t report_size, const char *extra_content, size_t extra_size, gchar **message) { char report_dir[] = "/tmp/gvmd_alert_XXXXXX"; gchar *report_path, *error_path, *extra_path; int ret; if (message) *message = NULL; if (report == NULL) return -1; g_debug ("report: %s", report); /* Setup files. */ ret = alert_script_init (report_filename, report, report_size, extra_content, extra_size, report_dir, &report_path, &error_path, &extra_path); if (ret) return ret; /* Run the script */ ret = alert_script_exec (alert_id, command_args, report_path, report_dir, error_path, extra_path, message); if (ret) { alert_script_cleanup (report_dir, report_path, error_path, extra_path); return ret; } /* Remove the directory. */ ret = alert_script_cleanup (report_dir, report_path, error_path, extra_path); return ret; } /** * @brief Send an SNMP TRAP to a host. * * @param[in] community Community. * @param[in] agent Agent. * @param[in] message Message. * @param[out] script_message Custom error message of the script. * * @return 0 success, -1 error, -5 alert script failed. */ static int snmp_to_host (const char *community, const char *agent, const char *message, gchar **script_message) { gchar *clean_community, *clean_agent, *clean_message, *command_args; int ret; g_debug ("SNMP to host: %s", agent); if (community == NULL || agent == NULL || message == NULL) { g_warning ("%s: parameter was NULL", __func__); return -1; } clean_community = g_shell_quote (community); clean_agent = g_shell_quote (agent); clean_message = g_shell_quote (message); command_args = g_strdup_printf ("%s %s %s", clean_community, clean_agent, clean_message); g_free (clean_community); g_free (clean_agent); g_free (clean_message); ret = run_alert_script ("9d435134-15d3-11e6-bf5c-28d24461215b", command_args, "report", "", 0, NULL, 0, script_message); g_free (command_args); return ret; } /** * @brief Send a report to a host via TCP. * * @param[in] host Address of host. * @param[in] port Port of host. * @param[in] report Report that should be sent. * @param[in] report_size Size of the report. * @param[out] script_message Custom error message of the script. * * @return 0 success, -1 error, -5 alert script failed. */ static int send_to_host (const char *host, const char *port, const char *report, int report_size, gchar **script_message) { gchar *clean_host, *clean_port, *command_args; int ret; g_debug ("send to host: %s:%s", host, port); if (host == NULL) return -1; clean_host = g_shell_quote (host); clean_port = g_shell_quote (port); command_args = g_strdup_printf ("%s %s", clean_host, clean_port); g_free (clean_host); g_free (clean_port); ret = run_alert_script ("4a398d42-87c0-11e5-a1c0-28d24461215b", command_args, "report", report, report_size, NULL, 0, script_message); g_free (command_args); return ret; } /** * @brief Send a report to a host via TCP. * * @param[in] username Username. * @param[in] password Password or passphrase of private key. * @param[in] private_key Private key or NULL for password-only auth. * @param[in] host Address of host. * @param[in] path Destination filename with path. * @param[in] known_hosts Content for known_hosts file. * @param[in] report Report that should be sent. * @param[in] report_size Size of the report. * @param[out] script_message Custom error message of the alert script. * * @return 0 success, -1 error, -5 alert script failed. */ static int scp_to_host (const char *username, const char *password, const char *private_key, const char *host, const char *path, const char *known_hosts, const char *report, int report_size, gchar **script_message) { const char *alert_id = "2db07698-ec49-11e5-bcff-28d24461215b"; char report_dir[] = "/tmp/gvmd_alert_XXXXXX"; gchar *report_path, *error_path, *password_path, *private_key_path; gchar *clean_username, *clean_host, *clean_path, *clean_private_key_path; gchar *clean_known_hosts, *command_args; int ret; g_debug ("scp to host: %s@%s:%s", username, host, path); if (password == NULL || username == NULL || host == NULL || path == NULL) return -1; if (known_hosts == NULL) known_hosts = ""; /* Setup files, including password but not private key */ ret = alert_script_init ("report", report, report_size, password, strlen (password), report_dir, &report_path, &error_path, &password_path); if (ret) return -1; if (private_key) { /* Setup private key here because alert_script_init and alert_script_exec * only handle one extra file. */ if (alert_write_data_file (report_dir, "private_key", private_key, strlen (private_key), "private key", &private_key_path)) { alert_script_cleanup (report_dir, report_path, error_path, password_path); g_free (private_key_path); return -1; } } else private_key_path = g_strdup (""); /* Create arguments */ clean_username = g_shell_quote (username); clean_host = g_shell_quote (host); clean_path = g_shell_quote (path); clean_known_hosts = g_shell_quote (known_hosts); clean_private_key_path = g_shell_quote (private_key_path); command_args = g_strdup_printf ("%s %s %s %s %s", clean_username, clean_host, clean_path, clean_known_hosts, clean_private_key_path); g_free (clean_username); g_free (clean_host); g_free (clean_path); g_free (clean_known_hosts); g_free (clean_private_key_path); /* Run script */ ret = alert_script_exec (alert_id, command_args, report_path, report_dir, error_path, password_path, script_message); g_free (command_args); if (ret) { alert_script_cleanup (report_dir, report_path, error_path, password_path); g_free (private_key_path); return ret; } /* Remove the directory and free path strings. */ ret = alert_script_cleanup (report_dir, report_path, error_path, password_path); g_free (private_key_path); return ret; } /** * @brief Send a report to a host via SMB. * * @param[in] password Password. * @param[in] username Username. * @param[in] share_path Name/address of host and name of the share. * @param[in] file_path Destination filename with path inside the share. * @param[in] report Report that should be sent. * @param[in] report_size Size of the report. * @param[out] script_message Custom error message of the alert script. * * @return 0 success, -1 error, -5 alert script failed. */ static int smb_send_to_host (const char *password, const char *username, const char *share_path, const char *file_path, const char *report, gsize report_size, gchar **script_message) { gchar *clean_share_path, *clean_file_path; gchar *authfile_content; gchar *command_args; int ret; g_debug ("smb as %s to share: %s, path: %s", username, share_path, file_path); if (password == NULL || username == NULL || share_path == NULL || file_path == NULL) return -1; clean_share_path = g_shell_quote (share_path); clean_file_path = g_shell_quote (file_path); authfile_content = g_strdup_printf ("username = %s\n" "password = %s\n", username, password); command_args = g_strdup_printf ("%s %s", clean_share_path, clean_file_path); g_free (clean_share_path); g_free (clean_file_path); ret = run_alert_script ("c427a688-b653-40ab-a9d0-d6ba842a9d63", command_args, "report", report, report_size, authfile_content, strlen (authfile_content), script_message); g_free (authfile_content); g_free (command_args); return ret; } /** * @brief Send a report to a Sourcefire Defense Center. * * @param[in] ip IP of center. * @param[in] port Port of center. * @param[in] pkcs12_64 PKCS12 content in base64. * @param[in] pkcs12_password Password for encrypted PKCS12. * @param[in] report Report in "Sourcefire" format. * * @return 0 success, -1 error. */ static int send_to_sourcefire (const char *ip, const char *port, const char *pkcs12_64, const char *pkcs12_password, const char *report) { gchar *script, *script_dir; gchar *report_file, *pkcs12_file, *pkcs12, *clean_password; gchar *clean_ip, *clean_port; char report_dir[] = "/tmp/gvmd_escalate_XXXXXX"; GError *error; gsize pkcs12_len; if ((report == NULL) || (ip == NULL) || (port == NULL)) return -1; g_debug ("send to sourcefire: %s:%s", ip, port); g_debug ("report: %s", report); /* Setup files. */ if (mkdtemp (report_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return -1; } report_file = g_strdup_printf ("%s/report.csv", report_dir); error = NULL; g_file_set_contents (report_file, report, strlen (report), &error); if (error) { g_warning ("%s", error->message); g_error_free (error); g_free (report_file); return -1; } pkcs12_file = g_strdup_printf ("%s/pkcs12", report_dir); if (strlen (pkcs12_64)) pkcs12 = (gchar*) g_base64_decode (pkcs12_64, &pkcs12_len); else { pkcs12 = g_strdup (""); pkcs12_len = 0; } error = NULL; g_file_set_contents (pkcs12_file, pkcs12, pkcs12_len, &error); if (error) { g_warning ("%s", error->message); g_error_free (error); g_free (report_file); g_free (pkcs12_file); return -1; } clean_password = g_shell_quote (pkcs12_password ? pkcs12_password : ""); /* Setup file names. */ script_dir = g_build_filename (GVMD_DATA_DIR, "global_alert_methods", "cd1f5a34-6bdc-11e0-9827-002264764cea", NULL); script = g_build_filename (script_dir, "alert", NULL); if (!gvm_file_is_readable (script)) { g_free (report_file); g_free (pkcs12_file); g_free (clean_password); g_free (script); g_free (script_dir); return -1; } { gchar *command; char *previous_dir; int ret; /* Change into the script directory. */ previous_dir = getcwd (NULL, 0); if (previous_dir == NULL) { g_warning ("%s: Failed to getcwd: %s", __func__, strerror (errno)); g_free (report_file); g_free (pkcs12_file); g_free (clean_password); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } if (chdir (script_dir)) { g_warning ("%s: Failed to chdir: %s", __func__, strerror (errno)); g_free (report_file); g_free (pkcs12_file); g_free (clean_password); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } g_free (script_dir); /* Call the script. */ clean_ip = g_shell_quote (ip); clean_port = g_shell_quote (port); command = g_strdup_printf ("%s %s %s %s %s %s > /dev/null" " 2> /dev/null", script, clean_ip, clean_port, pkcs12_file, report_file, clean_password); g_free (script); g_free (clean_ip); g_free (clean_port); g_free (clean_password); g_debug (" command: %s", command); if (geteuid () == 0) { pid_t pid; struct passwd *nobody; /* Run the command with lower privileges in a fork. */ nobody = getpwnam ("nobody"); if ((nobody == NULL) || chown (report_dir, nobody->pw_uid, nobody->pw_gid) || chown (report_file, nobody->pw_uid, nobody->pw_gid) || chown (pkcs12_file, nobody->pw_uid, nobody->pw_gid)) { g_warning ("%s: Failed to set permissions for user nobody: %s", __func__, strerror (errno)); g_free (report_file); g_free (pkcs12_file); g_free (previous_dir); return -1; } g_free (report_file); g_free (pkcs12_file); pid = fork (); switch (pid) { case 0: { /* Child. Drop privileges, run command, exit. */ cleanup_manage_process (FALSE); proctitle_set ("gvmd: Sending to Sourcefire"); if (setgroups (0,NULL)) { g_warning ("%s (child): setgroups: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setgid (nobody->pw_gid)) { g_warning ("%s (child): setgid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setuid (nobody->pw_uid)) { g_warning ("%s (child): setuid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } ret = system (command); /* Ignore the shell command exit status, because we've not * specified what it must be in the past. */ if (ret == -1) { g_warning ("%s (child):" " system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); exit (EXIT_FAILURE); } exit (EXIT_SUCCESS); } case -1: /* Parent when error. */ g_warning ("%s: Failed to fork: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; break; default: { int status; /* Parent on success. Wait for child, and check result. */ while (waitpid (pid, &status, 0) < 0) { if (errno == ECHILD) { g_warning ("%s: Failed to get child exit status", __func__); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } if (errno == EINTR) continue; g_warning ("%s: wait: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } if (WIFEXITED (status)) switch (WEXITSTATUS (status)) { case EXIT_SUCCESS: break; case EXIT_FAILURE: default: g_warning ("%s: child failed, %s", __func__, command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } else { g_warning ("%s: child failed, %s", __func__, command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } /* Child succeeded, continue to process result. */ g_free (command); break; } } } else { /* Just run the command as the current user. */ g_free (report_file); g_free (pkcs12_file); ret = system (command); /* Ignore the shell command exit status, because we've not * specified what it must be in the past. */ if (ret == -1) { g_warning ("%s: system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } g_free (command); } /* Change back to the previous directory. */ if (chdir (previous_dir)) { g_warning ("%s: Failed to chdir back: %s", __func__, strerror (errno)); g_free (previous_dir); return -1; } g_free (previous_dir); /* Remove the directory. */ gvm_file_remove_recurse (report_dir); return 0; } } /** * @brief Send a report to a verinice.PRO server. * * @param[in] url URL of the server. * @param[in] username Username for server access. * @param[in] password Password for server access. * @param[in] archive Verinice archive that should be sent. * @param[in] archive_size Size of the verinice archive * * @return 0 success, -1 error. */ static int send_to_verinice (const char *url, const char *username, const char *password, const char *archive, int archive_size) { gchar *script, *script_dir; gchar *archive_file; gchar *clean_url, *clean_username, *clean_password; char archive_dir[] = "/tmp/gvmd_alert_XXXXXX"; GError *error; if ((archive == NULL) || (url == NULL)) return -1; g_debug ("send to verinice: %s", url); g_debug ("archive: %s", archive); /* Setup files. */ if (mkdtemp (archive_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return -1; } archive_file = g_strdup_printf ("%s/archive.vna", archive_dir); error = NULL; g_file_set_contents (archive_file, archive, archive_size, &error); if (error) { g_warning ("%s", error->message); g_error_free (error); g_free (archive_file); return -1; } /* Setup file names. */ script_dir = g_build_filename (GVMD_DATA_DIR, "global_alert_methods", "f9d97653-f89b-41af-9ba1-0f6ee00e9c1a", NULL); script = g_build_filename (script_dir, "alert", NULL); if (!gvm_file_is_readable (script)) { g_warning ("%s: Failed to find alert script: %s", __func__, script); g_free (archive_file); g_free (script); g_free (script_dir); return -1; } { gchar *command; gchar *log_command; /* Command with password removed. */ char *previous_dir; int ret; /* Change into the script directory. */ previous_dir = getcwd (NULL, 0); if (previous_dir == NULL) { g_warning ("%s: Failed to getcwd: %s", __func__, strerror (errno)); g_free (archive_file); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } if (chdir (script_dir)) { g_warning ("%s: Failed to chdir: %s", __func__, strerror (errno)); g_free (archive_file); g_free (previous_dir); g_free (script); g_free (script_dir); return -1; } g_free (script_dir); /* Call the script. */ clean_url = g_shell_quote (url); clean_username = g_shell_quote (username); clean_password = g_shell_quote (password); command = g_strdup_printf ("%s %s %s %s %s > /dev/null" " 2> /dev/null", script, clean_url, clean_username, clean_password, archive_file); log_command = g_strdup_printf ("%s %s %s ****** %s > /dev/null" " 2> /dev/null", script, clean_url, clean_username, archive_file); g_free (script); g_free (clean_url); g_free (clean_username); g_free (clean_password); g_debug (" command: %s", log_command); if (geteuid () == 0) { pid_t pid; struct passwd *nobody; /* Run the command with lower privileges in a fork. */ nobody = getpwnam ("nobody"); if ((nobody == NULL) || chown (archive_dir, nobody->pw_uid, nobody->pw_gid) || chown (archive_file, nobody->pw_uid, nobody->pw_gid)) { g_warning ("%s: Failed to set permissions for user nobody: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (archive_file); g_free (command); g_free (log_command); return -1; } g_free (archive_file); pid = fork (); switch (pid) { case 0: { /* Child. Drop privileges, run command, exit. */ proctitle_set ("gvmd: Sending to Verinice"); cleanup_manage_process (FALSE); if (setgroups (0,NULL)) { g_warning ("%s (child): setgroups: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setgid (nobody->pw_gid)) { g_warning ("%s (child): setgid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } if (setuid (nobody->pw_uid)) { g_warning ("%s (child): setuid: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); } ret = system (command); /* Ignore the shell command exit status, because we've not * specified what it must be in the past. */ if (ret == -1) { g_warning ("%s (child):" " system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), log_command); exit (EXIT_FAILURE); } exit (EXIT_SUCCESS); } case -1: /* Parent when error. */ g_warning ("%s: Failed to fork: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); g_free (log_command); return -1; break; default: { int status; /* Parent on success. Wait for child, and check result. */ while (waitpid (pid, &status, 0) < 0) { if (errno == ECHILD) { g_warning ("%s: Failed to get child exit status", __func__); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } if (errno == EINTR) continue; g_warning ("%s: wait: %s", __func__, strerror (errno)); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } if (WIFEXITED (status)) switch (WEXITSTATUS (status)) { case EXIT_SUCCESS: break; case EXIT_FAILURE: default: g_warning ("%s: child failed, %s", __func__, log_command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } else { g_warning ("%s: child failed, %s", __func__, log_command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); return -1; } /* Child succeeded, continue to process result. */ break; } } } else { /* Just run the command as the current user. */ g_free (archive_file); ret = system (command); /* Ignore the shell command exit status, because we've not * specified what it must be in the past. */ if (ret == -1) { g_warning ("%s: system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), log_command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); return -1; } } g_free (command); g_free (log_command); /* Change back to the previous directory. */ if (chdir (previous_dir)) { g_warning ("%s: Failed to chdir back: %s", __func__, strerror (errno)); g_free (previous_dir); return -1; } g_free (previous_dir); /* Remove the directory. */ gvm_file_remove_recurse (archive_dir); return 0; } } /** * @brief Appends an XML fragment for vFire call input to a string buffer. * * @param[in] key The name of the key. * @param[in] value The value to add. * @param[in] buffer The string buffer to append to. * * @return Always FALSE. */ gboolean buffer_vfire_call_input (gchar *key, gchar *value, GString *buffer) { xml_string_append (buffer, "<%s>%s", key, value, key); return FALSE; } /** * @brief Checks a mandatory vFire parameter and adds it to the config XML. * * @param[in] param The parameter to check. */ #define APPEND_VFIRE_PARAM(param) \ if (param) \ xml_string_append (config_xml, \ "<" G_STRINGIFY(param) ">%s", \ param); \ else \ { \ if (message) \ *message = g_strdup ("Mandatory " G_STRINGIFY(param) " missing."); \ g_warning ("%s: Missing " G_STRINGIFY(param) ".", __func__); \ g_string_free (config_xml, TRUE); \ return -1; \ } /** * @brief Create a new call on an Alemba vFire server. * * @param[in] base_url Base url of the vFire server. * @param[in] client_id The Alemba API Client ID to authenticate with. * @param[in] session_type Alemba session type to use, e.g. "Analyst". * @param[in] username Username. * @param[in] password Password. * @param[in] report_data Data for vFire call report attachments. * @param[in] call_data Data for creating the vFire call. * @param[in] description_template Template for the description text. * @param[out] message Error message. * * @return 0 success, -1 error, -5 alert script failed. */ static int send_to_vfire (const char *base_url, const char *client_id, const char *session_type, const char *username, const char *password, GPtrArray *report_data, GTree *call_data, const char *description_template, gchar **message) { const char *alert_id = "159f79a5-fce8-4ec5-aa49-7d17a77739a3"; GString *config_xml; int index; char config_xml_filename[] = "/tmp/gvmd_vfire_data_XXXXXX.xml"; int config_xml_fd; FILE *config_xml_file; gchar **cmd; gchar *alert_script; int ret, exit_status; GError *err; config_xml = g_string_new (""); // Mandatory parameters APPEND_VFIRE_PARAM (base_url) APPEND_VFIRE_PARAM (client_id) APPEND_VFIRE_PARAM (username) APPEND_VFIRE_PARAM (password) // Optional parameters xml_string_append (config_xml, "%s", session_type ? session_type : "Analyst"); // Call input g_string_append (config_xml, ""); g_tree_foreach (call_data, ((GTraverseFunc) buffer_vfire_call_input), config_xml); g_string_append (config_xml, ""); // Report data g_string_append (config_xml, ""); for (index = 0; index < report_data->len; index++) { alert_report_data_t *report_item; report_item = g_ptr_array_index (report_data, index); xml_string_append (config_xml, "" "%s" "%s" "%s" "%s" "", report_item->local_filename, report_item->remote_filename, report_item->content_type, report_item->report_format_name); } g_string_append (config_xml, ""); // End data XML and output to file g_string_append (config_xml, ""); config_xml_fd = g_mkstemp (config_xml_filename); if (config_xml_fd == -1) { g_warning ("%s: Could not create alert script config file: %s", __func__, strerror (errno)); g_string_free (config_xml, TRUE); return -1; } config_xml_file = fdopen (config_xml_fd, "w"); if (config_xml_file == NULL) { g_warning ("%s: Could not open alert script config file: %s", __func__, strerror (errno)); g_string_free (config_xml, TRUE); close (config_xml_fd); return -1; } if (fprintf (config_xml_file, "%s", config_xml->str) <= 0) { g_warning ("%s: Could not write alert script config file: %s", __func__, strerror (errno)); g_string_free (config_xml, TRUE); fclose (config_xml_file); return -1; } fflush (config_xml_file); fclose (config_xml_file); g_string_free (config_xml, TRUE); // Run the script alert_script = g_build_filename (GVMD_DATA_DIR, "global_alert_methods", alert_id, "alert", NULL); // TODO: Drop privileges when running as root cmd = (gchar **) g_malloc (3 * sizeof (gchar *)); cmd[0] = alert_script; cmd[1] = config_xml_filename; cmd[2] = NULL; ret = 0; if (g_spawn_sync (NULL, cmd, NULL, G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, message, &exit_status, &err) == FALSE) { g_warning ("%s: Failed to run alert script: %s", __func__, err->message); ret = -1; } if (exit_status) { g_warning ("%s: Alert script exited with status %d", __func__, exit_status); g_message ("%s: stderr: %s", __func__, message ? *message: ""); ret = -5; } // Cleanup g_free (cmd); g_free (alert_script); g_unlink (config_xml_filename); return ret; } /** * @brief Convert an XML report and send it to a TippingPoint SMS. * * @param[in] report Report to send. * @param[in] report_size Size of report. * @param[in] username Username. * @param[in] password Password. * @param[in] hostname Hostname. * @param[in] certificate Certificate. * @param[in] cert_workaround Whether to use cert workaround. * @param[out] message Custom error message of the script. * * @return 0 success, -1 error. */ static int send_to_tippingpoint (const char *report, size_t report_size, const char *username, const char *password, const char *hostname, const char *certificate, int cert_workaround, gchar **message) { const char *alert_id = "5b39c481-9137-4876-b734-263849dd96ce"; char report_dir[] = "/tmp/gvmd_alert_XXXXXX"; gchar *auth_config, *report_path, *error_path, *extra_path, *cert_path; gchar *command_args, *hostname_clean, *convert_script; GError *error = NULL; int ret; /* Setup auth file contents */ auth_config = g_strdup_printf ("machine %s\n" "login %s\n" "password %s\n", cert_workaround ? "Tippingpoint" : hostname, username, password); /* Setup common files. */ ret = alert_script_init ("report", report, report_size, auth_config, strlen (auth_config), report_dir, &report_path, &error_path, &extra_path); g_free (auth_config); if (ret) return ret; /* Setup certificate file */ cert_path = g_build_filename (report_dir, "cacert.pem", NULL); if (g_file_set_contents (cert_path, certificate, strlen (certificate), &error) == FALSE) { g_warning ("%s: Failed to write TLS certificate to file: %s", __func__, error->message); alert_script_cleanup (report_dir, report_path, error_path, extra_path); g_free (cert_path); return -1; } if (geteuid () == 0) { struct passwd *nobody; /* Run the command with lower privileges in a fork. */ nobody = getpwnam ("nobody"); if ((nobody == NULL) || chown (cert_path, nobody->pw_uid, nobody->pw_gid)) { g_warning ("%s: Failed to set permissions for user nobody: %s", __func__, strerror (errno)); g_free (cert_path); alert_script_cleanup (report_dir, report_path, error_path, extra_path); return -1; } } /* Build args and run the script */ hostname_clean = g_shell_quote (hostname); convert_script = g_build_filename (GVMD_DATA_DIR, "global_alert_methods", alert_id, "report-convert.py", NULL); command_args = g_strdup_printf ("%s %s %d %s", hostname_clean, cert_path, cert_workaround, convert_script); g_free (hostname_clean); g_free (cert_path); g_free (convert_script); ret = alert_script_exec (alert_id, command_args, report_path, report_dir, error_path, extra_path, message); if (ret) { alert_script_cleanup (report_dir, report_path, error_path, extra_path); return ret; } /* Remove the directory. */ ret = alert_script_cleanup (report_dir, report_path, error_path, extra_path); return ret; } /** * @brief Format string for simple notice alert email. */ #define SIMPLE_NOTICE_FORMAT \ "%s.\n" \ "\n" \ "After the event %s,\n" \ "the following condition was met: %s\n" \ "\n" \ "This email escalation is not configured to provide more details.\n" \ "Full details are stored on the scan engine.\n" \ "\n" \ "\n" \ "Note:\n" \ "This email was sent to you as a configured security scan escalation.\n" \ "Please contact your local system administrator if you think you\n" \ "should not have received it.\n" /** * @brief Format string for simple notice alert email. */ #define SECINFO_SIMPLE_NOTICE_FORMAT \ "%s.\n" \ "\n" \ "After the event %s,\n" \ "the following condition was met: %s\n" \ "\n" \ "This email escalation is not configured to provide more details.\n" \ "Full details are stored on the scan engine.\n" \ "\n" \ "\n" \ "Note:\n" \ "This email was sent to you as a configured security scan escalation.\n" \ "Please contact your local system administrator if you think you\n" \ "should not have received it.\n" /** * @brief Print an alert subject. * * @param[in] subject Format string for subject. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] alert Alert. * @param[in] task Task. * @param[in] total Total number of resources (for SecInfo alerts). * * @return Freshly allocated subject. */ static gchar * alert_subject_print (const gchar *subject, event_t event, const void *event_data, alert_t alert, task_t task, int total) { int formatting; const gchar *point, *end; GString *new_subject; assert (subject); new_subject = g_string_new (""); for (formatting = 0, point = subject, end = (subject + strlen (subject)); point < end; point++) if (formatting) { switch (*point) { case '$': g_string_append_c (new_subject, '$'); break; case 'd': /* Date that the check was last performed. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { char time_string[100]; time_t date; struct tm tm; if (event_data && (strcasecmp (event_data, "nvt") == 0)) date = nvts_check_time (); else if (type_is_scap (event_data)) date = scap_check_time (); else date = cert_check_time (); if (localtime_r (&date, &tm) == NULL) { g_warning ("%s: localtime failed, aborting", __func__); abort (); } if (strftime (time_string, 98, "%F", &tm) == 0) break; g_string_append (new_subject, time_string); } break; case 'e': { gchar *event_desc; event_desc = event_description (event, event_data, NULL); g_string_append (new_subject, event_desc); g_free (event_desc); break; } case 'n': { if (task) { char *name = task_name (task); g_string_append (new_subject, name); free (name); } break; } case 'N': { /* Alert Name */ char *name = alert_name (alert); g_string_append (new_subject, name); free (name); break; } case 'q': if (event == EVENT_NEW_SECINFO) g_string_append (new_subject, "New"); else if (event == EVENT_UPDATED_SECINFO) g_string_append (new_subject, "Updated"); break; case 's': /* Type. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) g_string_append (new_subject, type_name (event_data)); break; case 'S': /* Type, plural. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) g_string_append (new_subject, type_name_plural (event_data)); break; case 'T': g_string_append_printf (new_subject, "%i", total); break; case 'u': { /* Current user or owner of the Alert */ if (current_credentials.username && strcmp (current_credentials.username, "")) { g_string_append (new_subject, current_credentials.username); } else { char *owner = alert_owner_uuid (alert); gchar *name = user_name (owner); g_string_append (new_subject, name); free (owner); g_free (name); } break; } case 'U': { /* Alert UUID */ char *uuid = alert_uuid (alert); g_string_append (new_subject, uuid); free (uuid); break; } default: g_string_append_c (new_subject, '$'); g_string_append_c (new_subject, *point); break; } formatting = 0; } else if (*point == '$') formatting = 1; else g_string_append_c (new_subject, *point); return g_string_free (new_subject, FALSE); } /** * @brief Print an alert message. * * @param[in] message Format string for message. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] task Task. * @param[in] alert Alert. * @param[in] condition Alert condition. * @param[in] format_name Report format name. * @param[in] filter Filter. * @param[in] term Filter term. * @param[in] zone Timezone. * @param[in] host_summary Host summary. * @param[in] content The report, for inlining. * @param[in] content_length Length of content. * @param[in] truncated Whether the report was truncated. * @param[in] total Total number of resources (for SecInfo alerts). * @param[in] max_length Max allowed length of content. * * @return Freshly allocated message. */ static gchar * alert_message_print (const gchar *message, event_t event, const void *event_data, task_t task, alert_t alert, alert_condition_t condition, gchar *format_name, filter_t filter, const gchar *term, const gchar *zone, const gchar *host_summary, const gchar *content, gsize content_length, int truncated, int total, int max_length) { int formatting; const gchar *point, *end; GString *new_message; assert (message); new_message = g_string_new (""); for (formatting = 0, point = message, end = (message + strlen (message)); point < end; point++) if (formatting) { switch (*point) { case '$': g_string_append_c (new_message, '$'); break; case 'c': { gchar *condition_desc; condition_desc = alert_condition_description (condition, alert); g_string_append (new_message, condition_desc); g_free (condition_desc); break; } case 'd': /* Date that the check was last performed. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { char time_string[100]; time_t date; struct tm tm; if (event_data && (strcasecmp (event_data, "nvt") == 0)) date = nvts_check_time (); else if (type_is_scap (event_data)) date = scap_check_time (); else date = cert_check_time (); if (localtime_r (&date, &tm) == NULL) { g_warning ("%s: localtime failed, aborting", __func__); abort (); } if (strftime (time_string, 98, "%F", &tm) == 0) break; g_string_append (new_message, time_string); } break; case 'e': { gchar *event_desc; event_desc = event_description (event, event_data, NULL); g_string_append (new_message, event_desc); g_free (event_desc); break; } case 'H': { /* Host summary. */ g_string_append (new_message, host_summary ? host_summary : "N/A"); break; } case 'i': { if (content) { g_string_append_printf (new_message, "%.*s", /* Cast for 64 bit. */ (int) MIN (content_length, max_content_length), content); if (content_length > max_content_length) g_string_append_printf (new_message, "\n... (report truncated after" " %i characters)\n", max_content_length); } break; } case 'n': if (task) { char *name = task_name (task); g_string_append (new_message, name); free (name); } break; case 'N': { /* Alert Name */ char *name = alert_name (alert); g_string_append (new_message, name); free (name); break; } case 'r': { /* Report format name. */ g_string_append (new_message, format_name ? format_name : "N/A"); break; } case 'F': { /* Name of filter. */ if (filter) { char *name = filter_name (filter); g_string_append (new_message, name); free (name); } else g_string_append (new_message, "N/A"); break; } case 'f': { /* Filter term. */ g_string_append (new_message, term ? term : "N/A"); break; } case 'q': { if (event == EVENT_NEW_SECINFO) g_string_append (new_message, "New"); else if (event == EVENT_UPDATED_SECINFO) g_string_append (new_message, "Updated"); break; } case 's': /* Type. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) g_string_append (new_message, type_name (event_data)); break; case 'S': /* Type, plural. */ if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) g_string_append (new_message, type_name_plural (event_data)); break; case 't': { if (truncated) g_string_append_printf (new_message, "Note: This report exceeds the" " maximum length of %i characters" " and thus\n" "was truncated.\n", max_length); break; } case 'T': { g_string_append_printf (new_message, "%i", total); break; } case 'u': { /* Current user or owner of the Alert */ if (current_credentials.username && strcmp (current_credentials.username, "")) { g_string_append (new_message, current_credentials.username); } else { char *owner = alert_owner_uuid (alert); gchar *name = user_name (owner); g_string_append (new_message, name); free (owner); g_free (name); } break; } case 'U': { /* Alert UUID */ char *uuid = alert_uuid (alert); g_string_append (new_message, uuid); free (uuid); break; } case 'z': { /* Timezone. */ g_string_append (new_message, zone ? zone : "N/A"); break; } case 'R': default: g_string_append_c (new_message, '$'); g_string_append_c (new_message, *point); break; } formatting = 0; } else if (*point == '$') formatting = 1; else g_string_append_c (new_message, *point); return g_string_free (new_message, FALSE); } /** * @brief Print an SCP alert file path. * * @param[in] message Format string for message. * @param[in] task Task. * * @return Freshly allocated message. */ static gchar * scp_alert_path_print (const gchar *message, task_t task) { int formatting; const gchar *point, *end; GString *new_message; assert (message); new_message = g_string_new (""); for (formatting = 0, point = message, end = (message + strlen (message)); point < end; point++) if (formatting) { switch (*point) { case '$': g_string_append_c (new_message, '$'); break; case 'D': case 'T': { char time_string[9]; time_t current_time; struct tm tm; const gchar *format_str; if (*point == 'T') format_str = "%H%M%S"; else format_str = "%Y%m%d"; memset(&time_string, 0, 9); current_time = time (NULL); if (localtime_r (¤t_time, &tm) == NULL) { g_warning ("%s: localtime failed, aborting", __func__); abort (); } if (strftime (time_string, 9, format_str, &tm)) g_string_append (new_message, time_string); break; } case 'n': if (task) { char *name = task_name (task); g_string_append (new_message, name); free (name); } break; } formatting = 0; } else if (*point == '$') formatting = 1; else g_string_append_c (new_message, *point); return g_string_free (new_message, FALSE); } /** * @brief Build and send email for a ticket alert. * * @param[in] alert Alert. * @param[in] ticket Ticket. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] method Method from alert. * @param[in] condition Condition from alert, which was met by event. * @param[in] to_address To address. * @param[in] from_address From address. * @param[in] subject Subject. * * @return 0 success, -1 error. */ static int email_ticket (alert_t alert, ticket_t ticket, event_t event, const void* event_data, alert_method_t method, alert_condition_t condition, const gchar *to_address, const gchar *from_address, const gchar *subject) { gchar *full_subject, *body; char *recipient_credential_id; credential_t recipient_credential; int ret; /* Setup subject. */ full_subject = g_strdup_printf ("%s: %s (UUID: %s)", subject, ticket_nvt_name (ticket) ? ticket_nvt_name (ticket) : "[Orphan]", ticket_uuid (ticket)); /* Setup body. */ { gchar *event_desc, *condition_desc; event_desc = event_description (event, event_data, NULL); condition_desc = alert_condition_description (condition, alert); body = g_strdup_printf (SIMPLE_NOTICE_FORMAT, event_desc, event_desc, condition_desc); free (event_desc); free (condition_desc); } /* Get credential */ recipient_credential_id = alert_data (alert, "method", "recipient_credential"); recipient_credential = 0; if (recipient_credential_id) { find_credential_with_permission (recipient_credential_id, &recipient_credential, NULL); } /* Send email. */ ret = email (to_address, from_address, full_subject, body, NULL, NULL, NULL, NULL, recipient_credential); g_free (body); g_free (full_subject); free (recipient_credential_id); return ret; } /** * @brief Build and send email for SecInfo alert. * * @param[in] alert Alert. * @param[in] task Task. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] method Method from alert. * @param[in] condition Condition from alert, which was met by event. * @param[in] to_address To address. * @param[in] from_address From address. * * @return 0 success, -1 error, -2 failed to find report format, -3 failed to * find filter. */ static int email_secinfo (alert_t alert, task_t task, event_t event, const void* event_data, alert_method_t method, alert_condition_t condition, const gchar *to_address, const gchar *from_address) { gchar *alert_subject, *message, *subject, *example, *list, *type, *base64; gchar *term, *body; char *notice, *recipient_credential_id, *condition_filter_id; filter_t condition_filter; credential_t recipient_credential; int ret, count; list = new_secinfo_list (event, event_data, alert, &count); type = g_strdup (event_data); if (type && (example = strstr (type, "_example"))) example[0] = '\0'; /* Setup subject. */ subject = g_strdup_printf ("[GVM] %s %s arrived", event == EVENT_NEW_SECINFO ? "New" : "Updated", type_name_plural (type ? type : "nvt")); alert_subject = alert_data (alert, "method", "subject"); if (alert_subject && strlen (alert_subject)) { g_free (subject); subject = alert_subject_print (alert_subject, event, type, alert, task, count); } g_free (alert_subject); /* Setup body. */ notice = alert_data (alert, "method", "notice"); message = alert_data (alert, "method", "message"); if (message == NULL || strlen (message) == 0) { g_free (message); if (notice && strcmp (notice, "0") == 0) /* Message with inlined report. */ message = g_strdup (SECINFO_ALERT_MESSAGE_INCLUDE); else if (notice && strcmp (notice, "2") == 0) /* Message with attached report. */ message = g_strdup (SECINFO_ALERT_MESSAGE_ATTACH); else /* Simple notice message. */ message = NULL; } base64 = NULL; if (list && notice && strcmp (notice, "2") == 0) { /* Add list as text attachment. */ if (max_attach_length <= 0 || strlen (list) <= max_attach_length) base64 = g_base64_encode ((guchar*) list, strlen (list)); } condition_filter = 0; term = NULL; condition_filter_id = alert_data (alert, "condition", "filter_id"); if (condition_filter_id) { gchar *quoted_filter_id; quoted_filter_id = sql_quote (condition_filter_id); sql_int64 (&condition_filter, "SELECT id FROM filters WHERE uuid = '%s'", quoted_filter_id); term = filter_term (condition_filter_id); g_free (quoted_filter_id); } free (condition_filter_id); if (message && strlen (message)) body = alert_message_print (message, event, type, task, alert, condition, NULL, condition_filter, term, NULL, NULL, list, list ? strlen (list) : 0, 0, count, 0); else { gchar *event_desc, *condition_desc; event_desc = event_description (event, event_data, NULL); condition_desc = alert_condition_description (condition, alert); body = g_strdup_printf (SECINFO_SIMPLE_NOTICE_FORMAT, event_desc, event_desc, condition_desc); free (event_desc); free (condition_desc); } g_free (term); g_free (message); g_free (list); /* Get credential */ recipient_credential_id = alert_data (alert, "method", "recipient_credential"); recipient_credential = 0; if (recipient_credential_id) { find_credential_with_permission (recipient_credential_id, &recipient_credential, NULL); } /* Send email. */ ret = email (to_address, from_address, subject, body, base64, base64 ? "text/plain" : NULL, base64 ? "secinfo-alert" : NULL, base64 ? "txt" : NULL, recipient_credential); g_free (body); g_free (type); g_free (subject); free (recipient_credential_id); return ret; } /** * @brief Get the delta report to be used for an alert. * * @param[in] alert Alert. * @param[in] task Task. * @param[in] report Report. * * @return Report to compare with if required, else 0. */ static report_t get_delta_report (alert_t alert, task_t task, report_t report) { char *delta_type; report_t delta_report; delta_type = alert_data (alert, "method", "delta_type"); if (delta_type == NULL) return 0; delta_report = 0; if (strcmp (delta_type, "Previous") == 0) { if (task_report_previous (task, report, &delta_report)) g_warning ("%s: failed to get previous report", __func__); } else if (strcmp (delta_type, "Report") == 0) { char *delta_report_id; delta_report_id = alert_data (alert, "method", "delta_report_id"); if (delta_report_id && find_report_with_permission (delta_report_id, &delta_report, "get_reports")) g_warning ("%s: error while finding report", __func__); } free (delta_type); return delta_report; } /** * @brief Generates report results get data for an alert. * * @param[in] alert The alert to try to get the filter data from. * @param[in] base_get_data The get data for fallback and other data. * @param[out] alert_filter_get Pointer to the newly allocated get_data. * @param[out] filter_return Pointer to the filter. * * @return 0 success, -1 error, -3 filter not found. */ static int generate_alert_filter_get (alert_t alert, const get_data_t *base_get_data, get_data_t **alert_filter_get, filter_t *filter_return) { char *filt_id; filter_t filter; if (alert_filter_get == NULL) return -1; filt_id = alert_filter_id (alert); filter = 0; if (filt_id) { if (find_filter_with_permission (filt_id, &filter, "get_filters")) { free (filt_id); return -1; } if (filter == 0) { free (filt_id); return -3; } } if (filter_return) *filter_return = filter; if (filter) { (*alert_filter_get) = g_malloc0 (sizeof (get_data_t)); (*alert_filter_get)->details = base_get_data->details; (*alert_filter_get)->ignore_pagination = base_get_data->ignore_pagination; (*alert_filter_get)->ignore_max_rows_per_page = base_get_data->ignore_max_rows_per_page; (*alert_filter_get)->filt_id = g_strdup (filt_id); (*alert_filter_get)->filter = filter_term (filt_id); } else (*alert_filter_get) = NULL; /* Adjust filter for report composer. * * As a first step towards a full composer we have two fields stored * on the alert for controlling visibility of notes and overrides. * * We simply use these fields to adjust the filter. In the future we'll * remove the filter terms and extend the way we get the report. */ if (filter) { gchar *include_notes, *include_overrides; include_notes = alert_data (alert, "method", "composer_include_notes"); if (include_notes) { gchar *new_filter; new_filter = g_strdup_printf ("notes=%i %s", atoi (include_notes), (*alert_filter_get)->filter); g_free ((*alert_filter_get)->filter); (*alert_filter_get)->filter = new_filter; (*alert_filter_get)->filt_id = NULL; g_free (include_notes); } include_overrides = alert_data (alert, "method", "composer_include_overrides"); if (include_overrides) { gchar *new_filter; new_filter = g_strdup_printf ("overrides=%i %s", atoi (include_overrides), (*alert_filter_get)->filter); g_free ((*alert_filter_get)->filter); (*alert_filter_get)->filter = new_filter; (*alert_filter_get)->filt_id = NULL; g_free (include_overrides); } } return 0; } /** * @brief Generate report content for alert * * @param[in] alert The alert the report is generated for. * @param[in] report Report or NULL to get last report of task. * @param[in] task Task the report belongs to. * @param[in] get GET data for the report. * @param[in] report_format_data_name Name of alert data with report format, * or NULL if not configurable. * @param[in] report_format_lookup Name of report format to lookup if * lookup by name, or NULL if not required. * Used if report_format_data_name is * NULL or fails. * @param[in] fallback_format_id UUID of fallback report format. Used * if both report_format_data_name and * report_format_lookup are NULL or fail. * @param[in] notes_details Whether to include details of notes in report. * @param[in] overrides_details Whether to include override details in report. * @param[out] content Report content location. * @param[out] content_length Length of report content. * @param[out] extension File extension of report format. * @param[out] content_type Content type of report format. * @param[out] term Filter term. * @param[out] report_zone Actual timezone used in report. * @param[out] host_summary Summary of results per host. * @param[out] used_report_format Report format used. * @param[out] filter_return Filter used. * * @return 0 success, -1 error, -2 failed to find report format, -3 failed to * find filter. */ static int report_content_for_alert (alert_t alert, report_t report, task_t task, const get_data_t *get, const char *report_format_data_name, const char *report_format_lookup, const char *fallback_format_id, int notes_details, int overrides_details, gchar **content, gsize *content_length, gchar **extension, gchar **content_type, gchar **term, gchar **report_zone, gchar **host_summary, report_format_t *used_report_format, filter_t *filter_return) { int ret; report_format_t report_format; get_data_t *alert_filter_get; gchar *report_content; filter_t filter; assert (content); // Get filter ret = generate_alert_filter_get (alert, get, &alert_filter_get, &filter); if (ret) return ret; // Get last report from task if no report is given if (report == 0) switch (sql_int64 (&report, "SELECT max (id) FROM reports" " WHERE task = %llu", task)) { case 0: if (report) break; case 1: /* Too few rows in result of query. */ case -1: if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -1; break; default: /* Programming error. */ assert (0); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -1; } // Get report format or use fallback. report_format = 0; if (report_format_data_name) { gchar *format_uuid; format_uuid = alert_data (alert, "method", report_format_data_name); if (format_uuid && strlen (format_uuid)) { if (find_report_format_with_permission (format_uuid, &report_format, "get_report_formats") || (report_format == 0)) { g_warning ("%s: Could not find report format '%s' for %s", __func__, format_uuid, alert_method_name (alert_method (alert))); g_free (format_uuid); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -2; } g_free (format_uuid); } } if (report_format_lookup && (report_format == 0)) { if (lookup_report_format (report_format_lookup, &report_format) || (report_format == 0)) { g_warning ("%s: Could not find report format '%s' for %s", __func__, report_format_lookup, alert_method_name (alert_method (alert))); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -2; } } if (report_format == 0) { if (fallback_format_id == NULL) { g_warning ("%s: No fallback report format for %s", __func__, alert_method_name (alert_method (alert))); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -1; } if (find_report_format_with_permission (fallback_format_id, &report_format, "get_report_formats") || (report_format == 0)) { g_warning ("%s: Could not find fallback RFP '%s' for %s", __func__, fallback_format_id, alert_method_name (alert_method (alert))); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } return -2; } } // Generate report content report_content = manage_report (report, get_delta_report (alert, task, report), alert_filter_get ? alert_filter_get : get, report_format, notes_details, overrides_details, content_length, extension, content_type, term, report_zone, host_summary); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } if (report_content == NULL) return -1; *content = report_content; *used_report_format = report_format; return 0; } /** * @brief Generates a filename or path for a report. * * If no custom_format is given, the setting "Report Export File Name" * is used instead. * * @param[in] report The report to generate the filename for. * @param[in] report_format The report format to use. * @param[in] custom_format A custom format string to use for the filename. * @param[in] add_extension Whether to add the filename extension or not. * * @return Newly allocated filename. */ static gchar * generate_report_filename (report_t report, report_format_t report_format, const char *custom_format, gboolean add_extension) { task_t task; char *fname_format, *report_id, *creation_time, *modification_time; char *report_task_name, *rf_name; gchar *filename_base, *filename; if (custom_format && strcmp (custom_format, "")) fname_format = g_strdup (custom_format); else fname_format = sql_string ("SELECT value FROM settings" " WHERE name" " = 'Report Export File Name'" " AND " ACL_GLOBAL_OR_USER_OWNS () " ORDER BY coalesce (owner, 0) DESC LIMIT 1;", current_credentials.uuid); report_id = report_uuid (report); creation_time = sql_string ("SELECT iso_time (start_time)" " FROM reports" " WHERE id = %llu", report); modification_time = sql_string ("SELECT iso_time (end_time)" " FROM reports" " WHERE id = %llu", report); report_task (report, &task); report_task_name = task_name (task); rf_name = report_format ? report_format_name (report_format) : g_strdup ("unknown"); filename_base = gvm_export_file_name (fname_format, current_credentials.username, "report", report_id, creation_time, modification_time, report_task_name, rf_name); if (add_extension && report_format) { gchar *extension; extension = report_format_extension (report_format); filename = g_strdup_printf ("%s.%s", filename_base, extension); free (extension); } else filename = g_strdup (filename_base); free (fname_format); free (report_id); free (creation_time); free (modification_time); free (report_task_name); free (rf_name); g_free (filename_base); return filename; } /** * @brief Escalate an event. * * @param[in] alert Alert. * @param[in] task Task. * @param[in] report Report. 0 for most recent report. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] method Method from alert. * @param[in] condition Condition from alert, which was met by event. * @param[in] get GET data for report. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides_details If overrides, Whether to include details. * @param[out] script_message Custom error message from the script. * * @return 0 success, -1 error, -2 failed to find report format, -3 failed to * find filter, -4 failed to find credential, -5 alert script failed. */ static int escalate_to_vfire (alert_t alert, task_t task, report_t report, event_t event, const void* event_data, alert_method_t method, alert_condition_t condition, const get_data_t *get, int notes_details, int overrides_details, gchar **script_message) { int ret; char *credential_id; get_data_t *alert_filter_get; filter_t filter; credential_t credential; char *base_url, *session_type, *client_id, *username, *password; char *report_formats_str; gchar **report_formats, **point; char reports_dir[] = "/tmp/gvmd_XXXXXX"; gboolean is_first_report = TRUE; GString *format_names; GPtrArray *reports; char *report_zone; gchar *host_summary; iterator_t data_iterator; GTree *call_input; char *description_template; int name_offset; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"Alemba vFire\" not support", __func__); return -1; } // Get report if (report == 0) switch (sql_int64 (&report, "SELECT max (id) FROM reports" " WHERE task = %llu", task)) { case 0: if (report) break; case 1: /* Too few rows in result of query. */ case -1: return -1; break; default: /* Programming error. */ assert (0); return -1; } // Get report results filter and corresponding get data alert_filter_get = NULL; ret = generate_alert_filter_get (alert, get, &alert_filter_get, &filter); if (ret) return ret; // Generate reports if (mkdtemp (reports_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); get_data_reset (alert_filter_get); g_free (alert_filter_get); return -1; } reports = g_ptr_array_new_full (0, (GDestroyNotify) alert_report_data_free); report_formats_str = alert_data (alert, "method", "report_formats"); report_formats = g_strsplit_set (report_formats_str, ",", 0); point = report_formats; free (report_formats_str); report_zone = NULL; host_summary = NULL; format_names = g_string_new (""); while (*point) { gchar *report_format_id; report_format_t report_format; report_format_id = g_strstrip (*point); find_report_format_with_permission (report_format_id, &report_format, "get_report_formats"); if (report_format) { alert_report_data_t *alert_report_item; size_t content_length; gchar *report_content; GError *error = NULL; alert_report_item = g_malloc0 (sizeof (alert_report_data_t)); report_content = manage_report (report, get_delta_report (alert, task, report), alert_filter_get ? alert_filter_get : get, report_format, notes_details, overrides_details, &content_length, NULL /* extension */, &(alert_report_item->content_type), NULL /* term */, is_first_report ? &report_zone : NULL, is_first_report ? &host_summary : NULL); if (report_content == NULL) { g_warning ("%s: Failed to generate report", __func__); get_data_reset (alert_filter_get); g_free (alert_filter_get); alert_report_data_free (alert_report_item); g_strfreev (report_formats); return -1; } alert_report_item->report_format_name = report_format_name (report_format); if (is_first_report == FALSE) g_string_append (format_names, ", "); g_string_append (format_names, alert_report_item->report_format_name); alert_report_item->remote_filename = generate_report_filename (report, report_format, NULL, TRUE); alert_report_item->local_filename = g_build_filename (reports_dir, alert_report_item->remote_filename, NULL); g_file_set_contents (alert_report_item->local_filename, report_content, content_length, &error); g_free (report_content); if (error) { g_warning ("%s: Failed to write report to %s: %s", __func__, alert_report_item->local_filename, error->message); get_data_reset (alert_filter_get); g_free (alert_filter_get); alert_report_data_free (alert_report_item); g_strfreev (report_formats); return -1; } g_ptr_array_add (reports, alert_report_item); is_first_report = FALSE; } point ++; } g_strfreev (report_formats); // Find vFire credential credential_id = alert_data (alert, "method", "vfire_credential"); if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { get_data_reset (alert_filter_get); g_free (alert_filter_get); free (credential_id); return -1; } else if (credential == 0) { get_data_reset (alert_filter_get); g_free (alert_filter_get); free (credential_id); return -4; } // vFire General data base_url = alert_data (alert, "method", "vfire_base_url"); // vFire Login data session_type = alert_data (alert, "method", "vfire_session_type"); client_id = alert_data (alert, "method", "vfire_client_id"); username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); // Call input data call_input = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, g_free); name_offset = strlen ("vfire_call_"); init_iterator (&data_iterator, "SELECT name, data" " FROM alert_method_data" " WHERE alert = %llu" " AND name %s 'vfire_call_%%';", alert, sql_ilike_op ()); while (next (&data_iterator)) { gchar *name, *value; name = g_strdup (iterator_string (&data_iterator, 0) + name_offset); value = g_strdup (iterator_string (&data_iterator, 1)); g_tree_replace (call_input, name, value); } cleanup_iterator (&data_iterator); // Special case for descriptionterm description_template = alert_data (alert, "method", "vfire_call_description"); if (description_template == NULL) description_template = g_strdup (ALERT_VFIRE_CALL_DESCRIPTION); gchar *description; description = alert_message_print (description_template, event, event_data, task, alert, condition, format_names->str, filter, alert_filter_get ? alert_filter_get->filter : (get->filter ? get->filter : ""), report_zone, host_summary, NULL, 0, 0, 0, max_attach_length); g_tree_replace (call_input, g_strdup ("description"), description); // Create vFire ticket ret = send_to_vfire (base_url, client_id, session_type, username, password, reports, call_input, description_template, script_message); // Cleanup gvm_file_remove_recurse (reports_dir); if (alert_filter_get) { get_data_reset (alert_filter_get); g_free (alert_filter_get); } free (base_url); free (session_type); free (client_id); free (username); free (password); g_ptr_array_free (reports, TRUE); g_tree_destroy (call_input); free (description_template); return ret; } /** * @brief Escalate an event. * * @param[in] alert Alert. * @param[in] task Task. * @param[in] report Report. 0 for most recent report. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] method Method from alert. * @param[in] condition Condition from alert, which was met by event. * @param[in] get GET data for report. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides_details If overrides, Whether to include details. * @param[out] script_message Custom error message from the script. * * @return 0 success, -1 error, -2 failed to find report format, -3 failed to * find filter, -4 failed to find credential, -5 alert script failed. */ static int escalate_2 (alert_t alert, task_t task, report_t report, event_t event, const void* event_data, alert_method_t method, alert_condition_t condition, const get_data_t *get, int notes_details, int overrides_details, gchar **script_message) { if (script_message) *script_message = NULL; { char *name_alert; gchar *event_desc, *alert_desc; name_alert = alert_name (alert); event_desc = event_description (event, event_data, NULL); alert_desc = alert_condition_description (condition, alert); g_log ("event alert", G_LOG_LEVEL_MESSAGE, "The alert %s was triggered " "(Event: %s, Condition: %s)", name_alert, event_desc, alert_desc); free (name_alert); free (event_desc); free (alert_desc); } switch (method) { case ALERT_METHOD_EMAIL: { char *to_address; char *format_name; format_name = NULL; to_address = alert_data (alert, "method", "to_address"); if (to_address) { int ret; gchar *body, *subject; char *name, *notice, *from_address; gchar *base64, *type, *extension; base64 = NULL; type = NULL; extension = NULL; from_address = alert_data (alert, "method", "from_address"); if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { ret = email_secinfo (alert, task, event, event_data, method, condition, to_address, from_address); free (to_address); free (from_address); return ret; } if (event == EVENT_TICKET_RECEIVED) { ret = email_ticket (alert, task, event, event_data, method, condition, to_address, from_address, "Ticket received"); free (to_address); free (from_address); return ret; } if (event == EVENT_ASSIGNED_TICKET_CHANGED) { ret = email_ticket (alert, task, event, event_data, method, condition, to_address, from_address, "Assigned ticket changed"); free (to_address); free (from_address); return ret; } if (event == EVENT_OWNED_TICKET_CHANGED) { ret = email_ticket (alert, task, event, event_data, method, condition, to_address, from_address, "Owned ticket changed"); free (to_address); free (from_address); return ret; } notice = alert_data (alert, "method", "notice"); name = task_name (task); if (notice && strcmp (notice, "0") == 0) { gchar *event_desc, *condition_desc, *report_content; gchar *alert_subject, *message; gchar *term, *report_zone, *host_summary; report_format_t report_format = 0; gsize content_length; filter_t filter; /* Message with inlined report. */ term = NULL; report_zone = NULL; host_summary = NULL; /* report_content_for_alert always sets this, but init it * anyway, to make it easier for the compiler to see. */ filter = 0; ret = report_content_for_alert (alert, 0, task, get, "notice_report_format", NULL, /* TXT fallback */ "a3810a62-1f62-11e1-9219-406186ea4fc5", notes_details, overrides_details, &report_content, &content_length, &extension, NULL, &term, &report_zone, &host_summary, &report_format, &filter); if (ret || report_content == NULL) { free (notice); free (name); free (to_address); free (from_address); g_free (term); g_free (report_zone); g_free (host_summary); return -1; } format_name = report_format_name (report_format); condition_desc = alert_condition_description (condition, alert); event_desc = event_description (event, event_data, NULL); subject = g_strdup_printf ("[GVM] Task '%s': %s", name ? name : "Internal Error", event_desc); g_free (event_desc); alert_subject = alert_data (alert, "method", "subject"); if (alert_subject && strlen (alert_subject)) { g_free (subject); subject = alert_subject_print (alert_subject, event, event_data, alert, task, 0); } g_free (alert_subject); message = alert_data (alert, "method", "message"); if (message == NULL || strlen (message) == 0) { g_free (message); message = g_strdup (ALERT_MESSAGE_INCLUDE); } body = alert_message_print (message, event, event_data, task, alert, condition, format_name, filter, term, report_zone, host_summary, report_content, content_length, content_length > max_content_length, 0, max_content_length); g_free (message); g_free (report_content); g_free (condition_desc); g_free (term); g_free (report_zone); g_free (host_summary); } else if (notice && strcmp (notice, "2") == 0) { gchar *event_desc, *condition_desc, *report_content; report_format_t report_format = 0; gsize content_length; gchar *alert_subject, *message; gchar *term, *report_zone, *host_summary; filter_t filter; /* Message with attached report. */ term = NULL; report_zone = NULL; host_summary = NULL; /* report_content_for_alert always sets this, but init it * anyway, to make it easier for the compiler to see. */ filter = 0; ret = report_content_for_alert (alert, 0, task, get, "notice_attach_format", NULL, /* TXT fallback */ "a3810a62-1f62-11e1-9219-406186ea4fc5", notes_details, overrides_details, &report_content, &content_length, &extension, &type, &term, &report_zone, &host_summary, &report_format, &filter); if (ret || report_content == NULL) { free (notice); free (name); free (to_address); free (from_address); g_free (term); g_free (report_zone); g_free (host_summary); return -1; } format_name = report_format_name (report_format); condition_desc = alert_condition_description (condition, alert); event_desc = event_description (event, event_data, NULL); subject = g_strdup_printf ("[GVM] Task '%s': %s", name ? name : "Internal Error", event_desc); g_free (event_desc); alert_subject = alert_data (alert, "method", "subject"); if (alert_subject && strlen (alert_subject)) { g_free (subject); subject = alert_subject_print (alert_subject, event, event_data, alert, task, 0); } g_free (alert_subject); if (max_attach_length <= 0 || content_length <= max_attach_length) base64 = g_base64_encode ((guchar*) report_content, content_length); g_free (report_content); message = alert_data (alert, "method", "message"); if (message == NULL || strlen (message) == 0) { g_free (message); message = g_strdup (ALERT_MESSAGE_ATTACH); } body = alert_message_print (message, event, event_data, task, alert, condition, format_name, filter, term, report_zone, host_summary, NULL, 0, base64 == NULL, 0, max_attach_length); g_free (message); g_free (condition_desc); g_free (term); g_free (report_zone); g_free (host_summary); } else { gchar *event_desc, *generic_desc, *condition_desc; gchar *alert_subject, *message; /* Simple notice message. */ format_name = NULL; event_desc = event_description (event, event_data, name); generic_desc = event_description (event, event_data, NULL); condition_desc = alert_condition_description (condition, alert); subject = g_strdup_printf ("[GVM] Task '%s':" " An event occurred", name); alert_subject = alert_data (alert, "method", "subject"); if (alert_subject && strlen (alert_subject)) { g_free (subject); subject = alert_subject_print (alert_subject, event, event_data, alert, task, 0); } g_free (alert_subject); message = alert_data (alert, "method", "message"); if (message && strlen (message)) body = alert_message_print (message, event, event_data, task, alert, condition, NULL, 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0); else body = g_strdup_printf (SIMPLE_NOTICE_FORMAT, event_desc, generic_desc, condition_desc); g_free (message); g_free (event_desc); g_free (generic_desc); g_free (condition_desc); } free (notice); gchar *fname_format, *file_name; gchar *report_id, *creation_time, *modification_time; char *recipient_credential_id; credential_t recipient_credential; fname_format = sql_string ("SELECT value FROM settings" " WHERE name" " = 'Report Export File Name'" " AND " ACL_GLOBAL_OR_USER_OWNS () " ORDER BY coalesce (owner, 0) DESC LIMIT 1;", current_credentials.uuid); report_id = report_uuid (report); creation_time = sql_string ("SELECT iso_time (start_time)" " FROM reports" " WHERE id = %llu", report); modification_time = sql_string ("SELECT iso_time (end_time)" " FROM reports" " WHERE id = %llu", report); file_name = gvm_export_file_name (fname_format, current_credentials.username, "report", report_id, creation_time, modification_time, name, format_name); /* Get credential */ recipient_credential_id = alert_data (alert, "method", "recipient_credential"); recipient_credential = 0; if (recipient_credential_id) { find_credential_with_permission (recipient_credential_id, &recipient_credential, NULL); } ret = email (to_address, from_address, subject, body, base64, type, file_name ? file_name : "openvas-report", extension, recipient_credential); free (extension); free (type); free (name); free (format_name); g_free (base64); free (to_address); free (from_address); g_free (subject); g_free (body); g_free (fname_format); g_free (file_name); g_free (report_id); g_free (creation_time); g_free (modification_time); free (recipient_credential_id); return ret; } return -1; } case ALERT_METHOD_HTTP_GET: { char *url; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"HTTP Get\" not support", __func__); return -1; } if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { g_warning ("%s: Event \"%s NVTs arrived\" with method" " \"HTTP Get\" not support", __func__, event == EVENT_NEW_SECINFO ? "New" : "Updated"); return -1; } url = alert_data (alert, "method", "URL"); if (url) { int ret, formatting; gchar *point, *end; GString *new_url; new_url = g_string_new (""); for (formatting = 0, point = url, end = (url + strlen (url)); point < end; point++) if (formatting) { switch (*point) { case '$': g_string_append_c (new_url, '$'); break; case 'c': { gchar *condition_desc; condition_desc = alert_condition_description (condition, alert); g_string_append (new_url, condition_desc); g_free (condition_desc); break; } case 'e': { gchar *event_desc; event_desc = event_description (event, event_data, NULL); g_string_append (new_url, event_desc); g_free (event_desc); break; } case 'n': { char *name = task_name (task); g_string_append (new_url, name); free (name); break; } default: g_string_append_c (new_url, '$'); g_string_append_c (new_url, *point); break; } formatting = 0; } else if (*point == '$') formatting = 1; else g_string_append_c (new_url, *point); ret = http_get (new_url->str); g_string_free (new_url, TRUE); g_free (url); return ret; } return -1; } case ALERT_METHOD_SCP: { credential_t credential; char *credential_id; char *private_key, *password, *username, *host, *path, *known_hosts; gchar *report_content, *alert_path; gsize content_length; report_format_t report_format; int ret; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"SCP\" not support", __func__); return -1; } if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { gchar *message; credential_id = alert_data (alert, "method", "scp_credential"); if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { return -1; } else if (credential == 0) { return -4; } else { message = new_secinfo_message (event, event_data, alert); username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); private_key = credential_encrypted_value (credential, "private_key"); host = alert_data (alert, "method", "scp_host"); path = alert_data (alert, "method", "scp_path"); known_hosts = alert_data (alert, "method", "scp_known_hosts"); alert_path = scp_alert_path_print (path, task); free (path); ret = scp_to_host (username, password, private_key, host, alert_path, known_hosts, message, strlen (message), script_message); g_free (message); free (private_key); free (password); free (username); free (host); g_free (alert_path); free (known_hosts); return ret; } } ret = report_content_for_alert (alert, 0, task, get, "scp_report_format", NULL, /* XML fallback. */ "a994b278-1f62-11e1-96ac-406186ea4fc5", notes_details, overrides_details, &report_content, &content_length, NULL, NULL, NULL, NULL, NULL, &report_format, NULL); if (ret || report_content == NULL) { g_warning ("%s: Empty Report", __func__); return -1; } credential_id = alert_data (alert, "method", "scp_credential"); if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { g_free (report_content); return -1; } else if (credential == 0) { g_free (report_content); return -4; } else { username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); private_key = credential_encrypted_value (credential, "private_key"); host = alert_data (alert, "method", "scp_host"); path = alert_data (alert, "method", "scp_path"); known_hosts = alert_data (alert, "method", "scp_known_hosts"); alert_path = scp_alert_path_print (path, task); free (path); ret = scp_to_host (username, password, private_key, host, alert_path, known_hosts, report_content, content_length, script_message); free (private_key); free (password); free (username); free (host); g_free (alert_path); free (known_hosts); } g_free (report_content); return ret; } case ALERT_METHOD_SEND: { char *host, *port; gchar *report_content; gsize content_length; report_format_t report_format; int ret; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"Send\" not support", __func__); return -1; } if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { gchar *message; message = new_secinfo_message (event, event_data, alert); host = alert_data (alert, "method", "send_host"); port = alert_data (alert, "method", "send_port"); g_debug ("send host: %s", host); g_debug ("send port: %s", port); ret = send_to_host (host, port, message, strlen (message), script_message); g_free (message); free (host); free (port); return ret; } ret = report_content_for_alert (alert, 0, task, get, "send_report_format", NULL, /* XML fallback. */ "a994b278-1f62-11e1-96ac-406186ea4fc5", notes_details, overrides_details, &report_content, &content_length, NULL, NULL, NULL, NULL, NULL, &report_format, NULL); if (ret || report_content == NULL) { g_warning ("%s: Empty Report", __func__); return -1; } host = alert_data (alert, "method", "send_host"); port = alert_data (alert, "method", "send_port"); g_debug ("send host: %s", host); g_debug ("send port: %s", port); ret = send_to_host (host, port, report_content, content_length, script_message); free (host); free (port); g_free (report_content); return ret; } case ALERT_METHOD_SMB: { char *credential_id, *username, *password; char *share_path, *file_path_format; gboolean file_path_is_dir; report_format_t report_format; gchar *file_path, *report_content, *extension; gsize content_length; credential_t credential; int ret; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"SMP\" not support", __func__); return -1; } if (report == 0) switch (sql_int64 (&report, "SELECT max (id) FROM reports" " WHERE task = %llu", task)) { case 0: if (report) break; case 1: /* Too few rows in result of query. */ case -1: return -1; break; default: /* Programming error. */ assert (0); return -1; } if (task == 0 && report) { ret = report_task (report, &task); if (ret) return ret; } credential_id = alert_data (alert, "method", "smb_credential"); share_path = alert_data (alert, "method", "smb_share_path"); file_path_format = sql_string ("SELECT value FROM tags" " WHERE name = 'smb-alert:file_path'" " AND EXISTS" " (SELECT * FROM tag_resources" " WHERE resource_type = 'task'" " AND resource = %llu" " AND tag = tags.id)" " ORDER BY modification_time LIMIT 1;", task); if (file_path_format == NULL) file_path_format = alert_data (alert, "method", "smb_file_path"); file_path_is_dir = (g_str_has_suffix (file_path_format, "\\") || g_str_has_suffix (file_path_format, "/")); report_content = NULL; extension = NULL; report_format = 0; g_debug ("smb_credential: %s", credential_id); g_debug ("smb_share_path: %s", share_path); g_debug ("smb_file_path: %s (%s)", file_path_format, file_path_is_dir ? "dir" : "file"); ret = report_content_for_alert (alert, report, task, get, "smb_report_format", NULL, "a994b278-1f62-11e1-96ac-406186ea4fc5", /* XML fallback */ notes_details, overrides_details, &report_content, &content_length, &extension, NULL, NULL, NULL, NULL, &report_format, NULL); if (ret || report_content == NULL) { free (credential_id); free (share_path); free (file_path_format); g_free (report_content); g_free (extension); return ret ? ret : -1; } if (file_path_is_dir) { char *dirname, *filename; dirname = generate_report_filename (report, report_format, file_path_format, FALSE); filename = generate_report_filename (report, report_format, NULL, TRUE); file_path = g_strdup_printf ("%s\\%s", dirname, filename); free (dirname); free (filename); } else { file_path = generate_report_filename (report, report_format, file_path_format, TRUE); } credential = 0; ret = find_credential_with_permission (credential_id, &credential, "get_credentials"); if (ret || credential == 0) { if (ret == 0) { g_warning ("%s: Could not find credential %s", __func__, credential_id); } free (credential_id); free (share_path); free (file_path); g_free (report_content); g_free (extension); return ret ? -1 : -4; } username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); ret = smb_send_to_host (password, username, share_path, file_path, report_content, content_length, script_message); g_free (username); g_free (password); free (credential_id); free (share_path); free (file_path); g_free (report_content); g_free (extension); return ret; } case ALERT_METHOD_SNMP: { char *community, *agent, *snmp_message; int ret; gchar *message; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"SNMP\" not support", __func__); return -1; } community = alert_data (alert, "method", "snmp_community"); agent = alert_data (alert, "method", "snmp_agent"); snmp_message = alert_data (alert, "method", "snmp_message"); g_debug ("snmp_message: %s", snmp_message); g_debug ("snmp_community: %s", community); g_debug ("snmp_agent: %s", agent); if (snmp_message) { if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { int count; gchar *list, *example, *type; type = g_strdup (event_data); if (type && (example = strstr (type, "_example"))) example[0] = '\0'; list = new_secinfo_list (event, event_data, alert, &count); g_free (list); message = alert_subject_print (snmp_message, event, type, alert, task, count); g_free (type); } else message = alert_subject_print (snmp_message, event, event_data, alert, task, 0); } else { gchar *event_desc; event_desc = event_description (event, event_data, NULL); message = g_strdup_printf ("%s", event_desc); g_free (event_desc); } ret = snmp_to_host (community, agent, message, script_message); free (agent); free (community); free (snmp_message); g_free (message); return ret; } case ALERT_METHOD_SOURCEFIRE: { char *ip, *port, *pkcs12, *pkcs12_credential_id; credential_t pkcs12_credential; gchar *pkcs12_password, *report_content; gsize content_length; report_format_t report_format; int ret; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"Sourcefire\" not support", __func__); return -1; } if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { g_warning ("%s: Event \"%s NVTs arrived\" with method" " \"Sourcefire\" not support", __func__, event == EVENT_NEW_SECINFO ? "New" : "Updated"); return -1; } ret = report_content_for_alert (alert, report, task, get, NULL, "Sourcefire", NULL, notes_details, overrides_details, &report_content, &content_length, NULL, NULL, NULL, NULL, NULL, &report_format, NULL); if (ret || report_content == NULL) { g_warning ("%s: Empty Report", __func__); return -1; } ip = alert_data (alert, "method", "defense_center_ip"); port = alert_data (alert, "method", "defense_center_port"); if (port == NULL) port = g_strdup ("8307"); pkcs12 = alert_data (alert, "method", "pkcs12"); pkcs12_credential_id = alert_data (alert, "method", "pkcs12_credential"); if (pkcs12_credential_id == NULL || strcmp (pkcs12_credential_id, "") == 0) { pkcs12_password = g_strdup (""); } else if (find_credential_with_permission (pkcs12_credential_id, &pkcs12_credential, "get_credentials")) { g_free (ip); g_free (port); g_free (pkcs12); g_free (pkcs12_credential_id); return -1; } else if (pkcs12_credential == 0) { g_free (ip); g_free (port); g_free (pkcs12); g_free (pkcs12_credential_id); return -4; } else { g_free (pkcs12_credential_id); pkcs12_password = credential_encrypted_value (pkcs12_credential, "password"); } g_debug (" sourcefire ip: %s", ip); g_debug (" sourcefire port: %s", port); g_debug ("sourcefire pkcs12: %s", pkcs12); ret = send_to_sourcefire (ip, port, pkcs12, pkcs12_password, report_content); free (ip); g_free (port); free (pkcs12); g_free (report_content); g_free (pkcs12_password); return ret; } case ALERT_METHOD_SYSLOG: { char *submethod; gchar *message, *event_desc, *level; event_desc = event_description (event, event_data, NULL); message = g_strdup_printf ("%s: %s", event_name (event), event_desc); g_free (event_desc); submethod = alert_data (alert, "method", "submethod"); level = g_strdup_printf ("event %s", submethod); g_free (submethod); g_debug (" syslog level: %s", level); g_debug ("syslog message: %s", message); g_log (level, G_LOG_LEVEL_MESSAGE, "%s", message); g_free (level); g_free (message); return 0; } case ALERT_METHOD_TIPPINGPOINT: { int ret; report_format_t report_format; gchar *report_content, *extension; size_t content_length; char *credential_id, *username, *password, *hostname, *certificate; credential_t credential; char *tls_cert_workaround_str; int tls_cert_workaround; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"TippingPoint SMS\" not support", __func__); return -1; } /* TLS certificate subject workaround setting */ tls_cert_workaround_str = alert_data (alert, "method", "tp_sms_tls_workaround"); if (tls_cert_workaround_str) tls_cert_workaround = !!(atoi (tls_cert_workaround_str)); else tls_cert_workaround = 0; g_free (tls_cert_workaround_str); /* SSL / TLS Certificate */ certificate = alert_data (alert, "method", "tp_sms_tls_certificate"); /* Hostname / IP address */ hostname = alert_data (alert, "method", "tp_sms_hostname"); /* Credential */ credential_id = alert_data (alert, "method", "tp_sms_credential"); if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { g_free (certificate); g_free (hostname); g_free (credential_id); return -1; } else if (credential == 0) { g_free (certificate); g_free (hostname); g_free (credential_id); return -4; } else { g_free (credential_id); username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); } /* Report content */ extension = NULL; ret = report_content_for_alert (alert, report, task, get, NULL, /* Report format not configurable */ NULL, "a994b278-1f62-11e1-96ac-406186ea4fc5", /* XML fallback */ notes_details, overrides_details, &report_content, &content_length, &extension, NULL, NULL, NULL, NULL, &report_format, NULL); g_free (extension); if (ret) { g_free (username); g_free (password); g_free (hostname); g_free (certificate); return ret; } /* Send report */ ret = send_to_tippingpoint (report_content, content_length, username, password, hostname, certificate, tls_cert_workaround, script_message); g_free (username); g_free (password); g_free (hostname); g_free (certificate); g_free (report_content); return ret; } case ALERT_METHOD_VERINICE: { credential_t credential; char *url, *username, *password; gchar *credential_id, *report_content; gsize content_length; report_format_t report_format; int ret; if ((event == EVENT_TICKET_RECEIVED) || (event == EVENT_ASSIGNED_TICKET_CHANGED) || (event == EVENT_OWNED_TICKET_CHANGED)) { g_warning ("%s: Ticket events with method" " \"Verinice\" not support", __func__); return -1; } if (event == EVENT_NEW_SECINFO || event == EVENT_UPDATED_SECINFO) { g_warning ("%s: Event \"%s NVTs arrived\" with method" " \"Verinice\" not support", __func__, event == EVENT_NEW_SECINFO ? "New" : "Updated"); return -1; } ret = report_content_for_alert (alert, report, task, get, "verinice_server_report_format", "Verinice ISM", NULL, notes_details, overrides_details, &report_content, &content_length, NULL, NULL, NULL, NULL, NULL, &report_format, NULL); if (ret || report_content == NULL) { g_warning ("%s: Empty Report", __func__); return -1; } url = alert_data (alert, "method", "verinice_server_url"); credential_id = alert_data (alert, "method", "verinice_server_credential"); if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { free (url); g_free (report_content); return -1; } else if (credential == 0) { free (url); g_free (report_content); return -4; } else { username = credential_value (credential, "username"); password = credential_encrypted_value (credential, "password"); g_debug (" verinice url: %s", url); g_debug ("verinice username: %s", username); ret = send_to_verinice (url, username, password, report_content, content_length); free (url); g_free (username); g_free (password); g_free (report_content); return ret; } } case ALERT_METHOD_VFIRE: { int ret; ret = escalate_to_vfire (alert, task, report, event, event_data, method, condition, get, notes_details, overrides_details, script_message); return ret; } case ALERT_METHOD_START_TASK: { gvm_connection_t connection; char *task_id, *report_id, *owner_id, *owner_name; gmp_authenticate_info_opts_t auth_opts; /* Run the callback to fork a child connected to the Manager. */ if (manage_fork_connection == NULL) { g_warning ("%s: no connection fork available", __func__); return -1; } task_id = alert_data (alert, "method", "start_task_task"); if (task_id == NULL || strcmp (task_id, "") == 0) { g_warning ("%s: start_task_task missing or empty", __func__); return -1; } owner_id = sql_string ("SELECT uuid FROM users" " WHERE id = (SELECT owner FROM alerts" " WHERE id = %llu)", alert); owner_name = sql_string ("SELECT name FROM users" " WHERE id = (SELECT owner FROM alerts" " WHERE id = %llu)", alert); if (owner_id == NULL) { g_warning ("%s: could not find alert owner", __func__); free (owner_id); free (owner_name); return -1; } switch (manage_fork_connection (&connection, owner_id)) { case 0: /* Child. Break, stop task, exit. */ break; case -1: /* Parent on error. */ g_free (task_id); g_warning ("%s: fork failed", __func__); return -1; break; default: /* Parent. Continue with whatever lead to this escalation. */ g_free (task_id); free (owner_id); free (owner_name); return 0; break; } /* Start the task. */ auth_opts = gmp_authenticate_info_opts_defaults; auth_opts.username = owner_name; auth_opts.password = "dummy"; if (gmp_authenticate_info_ext_c (&connection, auth_opts)) { g_free (task_id); free (owner_id); free (owner_name); gvm_connection_free (&connection); exit (EXIT_FAILURE); } if (gmp_start_task_report_c (&connection, task_id, &report_id)) { g_free (task_id); free (owner_id); free (owner_name); gvm_connection_free (&connection); exit (EXIT_FAILURE); } g_free (task_id); g_free (report_id); free (owner_id); free (owner_name); gvm_connection_free (&connection); exit (EXIT_SUCCESS); } case ALERT_METHOD_ERROR: default: break; } return -1; } /** * @brief Escalate an event with preset report filtering. * * @param[in] alert Alert. * @param[in] task Task. * @param[in] report Report. * @param[in] event Event. * @param[in] event_data Event data. * @param[in] method Method from alert. * @param[in] condition Condition from alert, which was met by event. * @param[out] script_message Custom error message from alert script. * * @return 0 success, -1 error, -2 failed to find report format for alert, * -3 failed to find filter for alert, -4 failed to find credential, * -5 alert script failed. */ static int escalate_1 (alert_t alert, task_t task, report_t report, event_t event, const void* event_data, alert_method_t method, alert_condition_t condition, gchar **script_message) { int ret; get_data_t get; char *results_filter; memset (&get, 0, sizeof (get_data_t)); get.details = 1; results_filter = setting_filter ("Results"); if (results_filter && strlen (results_filter)) { get.filt_id = results_filter; get.filter = filter_term (results_filter); } else { get.filt_id = g_strdup ("0"); get.filter = g_strdup_printf ("notes=1 overrides=1 sort-reverse=severity" " rows=%d", method == ALERT_METHOD_EMAIL ? 1000 : -1); } ret = escalate_2 (alert, task, report, event, event_data, method, condition, &get, 1, 1, script_message); free (results_filter); g_free (get.filter); return ret; } /** * @brief Escalate an alert with task and event data. * * @param[in] alert_id Alert UUID. * @param[in] task_id Task UUID. * @param[in] event Event. * @param[in] event_data Event data. * @param[out] script_message Custom error message from alert script. * * @return 0 success, 1 failed to find alert, 2 failed to find task, * 99 permission denied, -1 error, -2 failed to find report format * for alert, -3 failed to find filter for alert, -4 failed to find * credential for alert, -5 alert script failed. */ int manage_alert (const char *alert_id, const char *task_id, event_t event, const void* event_data, gchar **script_message) { alert_t alert; task_t task; alert_condition_t condition; alert_method_t method; if (acl_user_may ("test_alert") == 0) return 99; if (find_alert_with_permission (alert_id, &alert, "test_alert")) return -1; if (alert == 0) return 1; if (task_id == NULL || strcmp (task_id, "0") == 0) task = 0; else { if (find_task_with_permission (task_id, &task, NULL)) return -1; if (task == 0) return 2; } condition = alert_condition (alert); method = alert_method (alert); return escalate_1 (alert, task, 0, event, event_data, method, condition, script_message); } /** * @brief Header for "New NVTs" alert message. */ #define NEW_NVTS_HEADER \ /* Open-Xchange (OX) AppSuite XHTML File HTML Injection Vuln... NoneAvailable 0.0 100% */ \ "Name Solution Type Severity QOD\n" \ "------------------------------------------------------------------------------------------\n" /** * @brief Header for "New NVTs" alert message, when there's an OID. */ #define NEW_NVTS_HEADER_OID \ /* Open-Xchange (OX) AppSuite XHTML File HTML Injection Vuln... NoneAvailable 0.0 100% 1.3... */ \ "Name Solution Type Severity QOD OID\n" \ "------------------------------------------------------------------------------------------------\n" /** * @brief Header for "New CVEs" alert message. */ #define NEW_CVES_HEADER \ /* CVE-2014-100001 6.8 Cross-site request forgery (CSRF) vulnerability in... */ \ "Name Severity Description\n" \ "--------------------------------------------------------------------------------\n" /** * @brief Header for "New CPEs" alert message. */ #define NEW_CPES_HEADER \ /* cpe:/a:.joomclan:com_joomclip 1024cms... */ \ "Name Title\n" \ "------------------------------------------------------------------------------------------\n" /** * @brief Header for "New CERT-Bund Advisories" alert message. */ #define NEW_CERT_BUNDS_HEADER \ /* CB-K13/0849 Novell SUSE Linux Enterprise Server: Mehrere Schwachstellen... */ \ "Name Title\n" \ "------------------------------------------------------------------------------------------\n" /** * @brief Header for "New DFN-CERT Advisories" alert message. */ #define NEW_DFN_CERTS_HEADER \ /* DFN-CERT-2008-1100 Denial of Service Schwachstelle in der... */ \ "Name Title\n" \ "------------------------------------------------------------------------------------------\n" /** * @brief Header for "New CERT-Bund Advisories" alert message. */ #define NEW_OVAL_DEFS_HEADER \ /* oval:org.mitre.oval:def:100116 libtiff Malloc Error Denial of Service */ \ "OVAL ID Title\n" \ "------------------------------------------------------------------------------------------\n" /** * @brief Test an alert. * * @param[in] alert_id Alert UUID. * @param[out] script_message Custom message from the alert script. * * @return 0 success, 1 failed to find alert, 2 failed to find task, * 99 permission denied, -1 error, -2 failed to find report format * for alert, -3 failed to find filter for alert, -4 failed to find * credential for alert, -5 alert script failed. */ int manage_test_alert (const char *alert_id, gchar **script_message) { int ret; alert_t alert; task_t task; report_t report; result_t result; char *task_id, *report_id; time_t now; char now_string[26]; gchar *clean; if (acl_user_may ("test_alert") == 0) return 99; if (find_alert_with_permission (alert_id, &alert, "test_alert")) return -1; if (alert == 0) return 1; if (alert_event (alert) == EVENT_NEW_SECINFO || alert_event (alert) == EVENT_UPDATED_SECINFO) { char *alert_event_data; gchar *type; alert_event_data = alert_data (alert, "event", "secinfo_type"); type = g_strdup_printf ("%s_example", alert_event_data ?: "NVT"); free (alert_event_data); if (alert_event (alert) == EVENT_NEW_SECINFO) ret = manage_alert (alert_id, "0", EVENT_NEW_SECINFO, (void*) type, script_message); else ret = manage_alert (alert_id, "0", EVENT_UPDATED_SECINFO, (void*) type, script_message); g_free (type); return ret; } task = make_task (g_strdup ("Temporary Task for Alert"), g_strdup (""), 0, /* Exclude from assets. */ 0); /* Skip event and log. */ report_id = gvm_uuid_make (); if (report_id == NULL) return -1; task_uuid (task, &task_id); report = make_report (task, report_id, TASK_STATUS_DONE); result = make_result (task, "127.0.0.1", "localhost", "telnet (23/tcp)", "1.3.6.1.4.1.25623.1.0.10330", "Alarm", "A telnet server seems to be running on this port.", NULL); now = time (NULL); if (strlen (ctime_r (&now, now_string)) == 0) { ret = -1; goto exit; } clean = g_strdup (now_string); if (clean[strlen (clean) - 1] == '\n') clean[strlen (clean) - 1] = '\0'; set_task_start_time_ctime (task, g_strdup (clean)); set_scan_start_time_ctime (report, g_strdup (clean)); set_scan_host_start_time_ctime (report, "127.0.0.1", clean); if (result) report_add_result (report, result); set_scan_host_end_time_ctime (report, "127.0.0.1", clean); set_scan_end_time_ctime (report, clean); g_free (clean); ret = manage_alert (alert_id, task_id, EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_DONE, script_message); exit: /* No one should be running this task, so we don't worry about the lock. We * could guarantee that no one runs the task, but this is a very rare case. */ delete_task (task, 1); free (task_id); free (report_id); return ret; } /** * @brief Return whether an event applies to a task and an alert. * * @param[in] event Event. * @param[in] event_data Event data. * @param[in] event_resource Event resource. * @param[in] alert Alert. * * @return 1 if event applies, else 0. */ static int event_applies (event_t event, const void *event_data, resource_t event_resource, alert_t alert) { switch (event) { case EVENT_TASK_RUN_STATUS_CHANGED: { int ret; char *alert_event_data; if (alert_applies_to_task (alert, event_resource) == 0) return 0; alert_event_data = alert_data (alert, "event", "status"); if (alert_event_data == NULL) return 0; ret = (task_run_status (event_resource) == (task_status_t) event_data) && (strcmp (alert_event_data, run_status_name_internal ((task_status_t) event_data)) == 0); free (alert_event_data); return ret; } case EVENT_NEW_SECINFO: case EVENT_UPDATED_SECINFO: { char *alert_event_data; alert_event_data = alert_data (alert, "event", "secinfo_type"); if (alert_event_data == NULL) return 0; if (strcasecmp (alert_event_data, event_data) == 0) return 1; return 0; } case EVENT_TICKET_RECEIVED: case EVENT_ASSIGNED_TICKET_CHANGED: return ticket_assigned_to (event_resource) == alert_owner (alert); case EVENT_OWNED_TICKET_CHANGED: return ticket_owner (event_resource) == alert_owner (alert); default: return 0; } } /** * @brief Return the SecInfo count. * * @param[in] alert Alert. * @param[in] filter_id Condition filter id. * * @return 1 if met, else 0. */ static time_t alert_secinfo_count (alert_t alert, char *filter_id) { get_data_t get; int db_count, uuid_was_null; event_t event; gboolean get_modified; time_t feed_version_epoch; char *secinfo_type; event = alert_event (alert); get_modified = (event == EVENT_UPDATED_SECINFO); if (current_credentials.uuid == NULL) { current_credentials.uuid = alert_owner_uuid (alert); uuid_was_null = 1; } else uuid_was_null = 0; memset (&get, '\0', sizeof (get)); if (filter_id && strlen (filter_id) && strcmp (filter_id, "0")) get.filt_id = filter_id; secinfo_type = alert_data (alert, "event", "secinfo_type"); if (strcmp (secinfo_type, "nvt") == 0) { feed_version_epoch = nvts_feed_version_epoch (); db_count = nvt_info_count_after (&get, feed_version_epoch, get_modified); } else if (strcmp (secinfo_type, "cert_bund_adv") == 0 || strcmp (secinfo_type, "dfn_cert_adv") == 0) { feed_version_epoch = cert_check_time (); db_count = secinfo_count_after (&get, secinfo_type, feed_version_epoch, get_modified); } else // assume SCAP data { feed_version_epoch = scap_check_time (); db_count = secinfo_count_after (&get, secinfo_type, feed_version_epoch, get_modified); } if (uuid_was_null) { free (current_credentials.uuid); current_credentials.uuid = NULL; } return db_count; } /** * @brief Return whether the condition of an alert is met by a task. * * @param[in] task Task. * @param[in] report Report. * @param[in] alert Alert. * @param[in] condition Condition. * * @return 1 if met, else 0. */ static int condition_met (task_t task, report_t report, alert_t alert, alert_condition_t condition) { switch (condition) { case ALERT_CONDITION_ALWAYS: return 1; break; case ALERT_CONDITION_FILTER_COUNT_AT_LEAST: { char *filter_id, *count_string; report_t last_report; int holes, infos, logs, warnings, false_positives; int count; double severity; /* True if there are at least the given number of results matched by * the given filter in the last finished report. */ filter_id = alert_data (alert, "condition", "filter_id"); count_string = alert_data (alert, "condition", "count"); if (count_string) { count = atoi (count_string); free (count_string); } else count = 0; if (task == 0) { int db_count; /* SecInfo event. */ db_count = alert_secinfo_count (alert, filter_id); if (db_count >= count) return 1; break; } if (report) last_report = report; else { last_report = 0; if (task_last_report (task, &last_report)) g_warning ("%s: failed to get last report", __func__); } g_debug ("%s: last_report: %llu", __func__, last_report); if (last_report) { int db_count; get_data_t get; memset (&get, 0, sizeof (get_data_t)); get.type = "result"; get.filt_id = filter_id; report_counts_id (last_report, &holes, &infos, &logs, &warnings, &false_positives, &severity, &get, NULL); db_count = holes + infos + logs + warnings + false_positives; g_debug ("%s: count: %i vs %i", __func__, db_count, count); if (db_count >= count) { g_free (filter_id); return 1; } } g_free (filter_id); break; } case ALERT_CONDITION_FILTER_COUNT_CHANGED: { char *direction, *filter_id, *count_string; report_t last_report; int holes, infos, logs, warnings, false_positives; int count; double severity; /* True if the number of results matched by the given filter in the * last finished report changed in the given direction with respect * to the second last finished report. */ direction = alert_data (alert, "condition", "direction"); filter_id = alert_data (alert, "condition", "filter_id"); count_string = alert_data (alert, "condition", "count"); if (count_string) { count = atoi (count_string); free (count_string); } else count = 0; if (report) last_report = report; else { last_report = 0; if (task_last_report (task, &last_report)) g_warning ("%s: failed to get last report", __func__); } if (last_report) { report_t second_last_report; int last_count; get_data_t get; get.type = "result"; get.filt_id = filter_id; report_counts_id (last_report, &holes, &infos, &logs, &warnings, &false_positives, &severity, &get, NULL); last_count = holes + infos + logs + warnings + false_positives; second_last_report = 0; if (task_second_last_report (task, &second_last_report)) g_warning ("%s: failed to get second last report", __func__); if (second_last_report) { int cmp, second_last_count; report_counts_id (second_last_report, &holes, &infos, &logs, &warnings, &false_positives, &severity, &get, NULL); second_last_count = holes + infos + logs + warnings + false_positives; cmp = last_count - second_last_count; g_debug ("cmp: %i (vs %i)", cmp, count); g_debug ("direction: %s", direction); g_debug ("last_count: %i", last_count); g_debug ("second_last_count: %i", second_last_count); if (count < 0) { count = -count; if (direction == NULL || strcasecmp (direction, "increased") == 0) { free (direction); direction = g_strdup ("decreased"); } else if (strcasecmp (direction, "decreased") == 0) { free (direction); direction = g_strdup ("increased"); } } if (direction == NULL) { /* Same as "increased". */ if (cmp >= count) return 1; } else if (((strcasecmp (direction, "changed") == 0) && (abs (cmp) >= count)) || ((strcasecmp (direction, "increased") == 0) && (cmp >= count)) || ((strcasecmp (direction, "decreased") == 0) && (cmp <= count))) { free (direction); free (filter_id); return 1; } } else { g_debug ("direction: %s", direction); g_debug ("last_count: %i", last_count); g_debug ("second_last_count NULL"); if (((strcasecmp (direction, "changed") == 0) || (strcasecmp (direction, "increased") == 0)) && (last_count > 0)) { free (direction); free (filter_id); return 1; } } } free (direction); free (filter_id); break; } case ALERT_CONDITION_SEVERITY_AT_LEAST: { char *condition_severity_str; /* True if the threat level of the last finished report is at * least the given level. */ condition_severity_str = alert_data (alert, "condition", "severity"); if (condition_severity_str) { double condition_severity_dbl, task_severity_dbl; condition_severity_dbl = g_ascii_strtod (condition_severity_str, 0); task_severity_dbl = task_severity_double (task, 1, MIN_QOD_DEFAULT, 0); if (task_severity_dbl >= condition_severity_dbl) { free (condition_severity_str); return 1; } } free (condition_severity_str); break; } case ALERT_CONDITION_SEVERITY_CHANGED: { char *direction; double last_severity, second_last_severity; /* True if the threat level of the last finished report changed * in the given direction with respect to the second last finished * report. */ direction = alert_data (alert, "condition", "direction"); last_severity = task_severity_double (task, 1, MIN_QOD_DEFAULT, 0); second_last_severity = task_severity_double (task, 1, MIN_QOD_DEFAULT, 1); if (direction && last_severity > SEVERITY_MISSING && second_last_severity > SEVERITY_MISSING) { double cmp = last_severity - second_last_severity; g_debug ("cmp: %f", cmp); g_debug ("direction: %s", direction); g_debug ("last_level: %1.1f", last_severity); g_debug ("second_last_level: %1.1f", second_last_severity); if (((strcasecmp (direction, "changed") == 0) && cmp) || ((strcasecmp (direction, "increased") == 0) && (cmp > 0)) || ((strcasecmp (direction, "decreased") == 0) && (cmp < 0))) { free (direction); return 1; } } else if (direction && last_severity > SEVERITY_MISSING) { g_debug ("direction: %s", direction); g_debug ("last_level: %1.1f", last_severity); g_debug ("second_last_level NULL"); if ((strcasecmp (direction, "changed") == 0) || (strcasecmp (direction, "increased") == 0)) { free (direction); return 1; } } free (direction); break; } default: break; } return 0; } /** * @brief Produce an event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] resource_1 Event type specific resource 1. For example, * a task for EVENT_TASK_RUN_STATUS_CHANGED. * @param[in] resource_2 Event type specific resource 2. */ void event (event_t event, void* event_data, resource_t resource_1, resource_t resource_2) { iterator_t alerts; GArray *alerts_triggered; guint index; g_debug (" EVENT %i on resource %llu", event, resource_1); alerts_triggered = g_array_new (TRUE, TRUE, sizeof (alert_t)); if ((event == EVENT_TASK_RUN_STATUS_CHANGED) && (((task_status_t) event_data) == TASK_STATUS_DONE)) check_tickets (resource_1); init_event_alert_iterator (&alerts, event); while (next (&alerts)) { alert_t alert = event_alert_iterator_alert (&alerts); if (event_alert_iterator_active (&alerts) && event_applies (event, event_data, resource_1, alert)) { alert_condition_t condition; condition = alert_condition (alert); if (condition_met (resource_1, resource_2, alert, condition)) g_array_append_val (alerts_triggered, alert); } } cleanup_iterator (&alerts); /* Run the alerts outside the iterator, because they may take some * time and the iterator would prevent update processes (GMP MODIFY_XXX, * CREATE_XXX, ...) from locking the database. */ index = alerts_triggered->len; while (index--) { alert_t alert; alert_condition_t condition; alert = g_array_index (alerts_triggered, alert_t, index); condition = alert_condition (alert); escalate_1 (alert, resource_1, resource_2, event, event_data, alert_method (alert), condition, NULL); } g_array_free (alerts_triggered, TRUE); } /** * @brief Initialise an alert task iterator. * * Iterate over all tasks that use the alert. * * @param[in] iterator Iterator. * @param[in] alert Alert. * @param[in] ascending Whether to sort ascending or descending. */ void init_alert_task_iterator (iterator_t* iterator, alert_t alert, int ascending) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (alert); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_tasks")); available = acl_where_owned ("task", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT tasks.name, tasks.uuid, %s FROM tasks, task_alerts" " WHERE tasks.id = task_alerts.task" " AND task_alerts.alert = %llu" " AND hidden = 0" " ORDER BY tasks.name %s;", with_clause ? with_clause : "", available, alert, ascending ? "ASC" : "DESC"); g_free (with_clause); g_free (available); } /** * @brief Return the name from an alert task iterator. * * @param[in] iterator Iterator. * * @return Name of the task or NULL if iteration is complete. */ const char* alert_task_iterator_name (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, 0); return ret; } /** * @brief Return the uuid from an alert task iterator. * * @param[in] iterator Iterator. * * @return UUID of the task or NULL if iteration is complete. */ const char* alert_task_iterator_uuid (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, 1); return ret; } /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int alert_task_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 2); } /* Task functions. */ /** * @brief Generate an extra WHERE clause for selecting tasks * * @param[in] trash Whether to get tasks from the trashcan. * @param[in] usage_type The usage type to limit the selection to. * * @return Newly allocated where clause string. */ static gchar * tasks_extra_where (int trash, const char *usage_type) { gchar *extra_where = NULL; if (usage_type && strcmp (usage_type, "")) { gchar *quoted_usage_type; quoted_usage_type = sql_quote (usage_type); extra_where = g_strdup_printf (" AND hidden = %d" " AND usage_type = '%s'", trash ? 2 : 0, quoted_usage_type); g_free (quoted_usage_type); } else extra_where = g_strdup_printf (" AND hidden = %d", trash ? 2 : 0); return extra_where; } /** * @brief Append value to field of task. * * @param[in] task Task. * @param[in] field Field. * @param[in] value Value. */ static void append_to_task_string (task_t task, const char* field, const char* value) { char* current; gchar* quote; current = sql_string ("SELECT %s FROM tasks WHERE id = %llu;", field, task); if (current) { gchar* new = g_strconcat ((const gchar*) current, value, NULL); free (current); quote = sql_nquote (new, strlen (new)); g_free (new); } else quote = sql_nquote (value, strlen (value)); sql ("UPDATE tasks SET %s = '%s', modification_time = m_now ()" " WHERE id = %llu;", field, quote, task); g_free (quote); } /** * @brief Filter columns for task iterator. */ #define TASK_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "status", "total", "first_report", \ "last_report", "threat", "trend", "severity", "schedule", "next_due", \ "first", "last", "false_positive", "log", "low", "medium", "high", \ "hosts", "result_hosts", "fp_per_host", "log_per_host", "low_per_host", \ "medium_per_host", "high_per_host", "target", "usage_type", NULL } /** * @brief Task iterator columns. */ #define TASK_ITERATOR_COLUMNS_INNER \ { "run_status", NULL, KEYWORD_TYPE_INTEGER }, \ { \ "(SELECT count(*) FROM reports" \ " WHERE task = tasks.id)", \ "total", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT uuid FROM reports WHERE task = tasks.id" \ /* TODO 1 == TASK_STATUS_DONE */ \ " AND scan_run_status = 1" \ " ORDER BY date ASC LIMIT 1)", \ "first_report", \ KEYWORD_TYPE_STRING \ }, \ { "run_status_name (run_status)", "status", KEYWORD_TYPE_STRING }, \ { \ "(SELECT uuid FROM reports WHERE task = tasks.id" \ /* TODO 1 == TASK_STATUS_DONE */ \ " AND scan_run_status = 1" \ " ORDER BY date DESC LIMIT 1)", \ "last_report", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT count(*) FROM reports" \ /* TODO 1 == TASK_STATUS_DONE */ \ " WHERE task = tasks.id AND scan_run_status = 1)", \ NULL, \ KEYWORD_TYPE_INTEGER \ }, \ { "hosts_ordering", NULL, KEYWORD_TYPE_STRING }, \ { "scanner", NULL, KEYWORD_TYPE_INTEGER }, \ { "usage_type", NULL, KEYWORD_TYPE_STRING } /** * @brief Task iterator WHERE columns. */ #define TASK_ITERATOR_WHERE_COLUMNS_INNER \ { \ "task_threat_level (id, opts.override, opts.min_qod)", \ "threat", \ KEYWORD_TYPE_STRING \ }, \ { \ "task_trend (id, opts.override, opts.min_qod)", \ "trend", \ KEYWORD_TYPE_STRING \ }, \ { \ "task_severity (id, opts.override, opts.min_qod)", \ "severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "(SELECT schedules.name FROM schedules" \ " WHERE schedules.id = tasks.schedule)", \ "schedule", \ KEYWORD_TYPE_STRING \ }, \ { \ "(CASE WHEN schedule_next_time IS NULL" \ " THEN -1" \ " WHEN schedule_next_time = 0 AND tasks.schedule > 0" \ " THEN (SELECT first_time" \ " FROM schedules" \ " WHERE schedules.id = tasks.schedule)" \ " ELSE schedule_next_time" \ " END)", \ "next_due", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT date FROM reports WHERE task = tasks.id" \ /* TODO 1 == TASK_STATUS_DONE */ \ " AND scan_run_status = 1" \ " ORDER BY date ASC LIMIT 1)", \ "first", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT date FROM reports WHERE task = tasks.id" \ /* TODO 1 == TASK_STATUS_DONE */ \ " AND scan_run_status = 1" \ " ORDER BY date DESC LIMIT 1)", \ "last", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'False Positive')" \ " END", \ "false_positive", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod, 'Log')" \ " END", \ "log", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod, 'Low')" \ " END", \ "low", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod, 'Medium')" \ " END", \ "medium", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod, 'High')" \ " END", \ "high", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_host_count (task_last_report (id))" \ " END", \ "hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " report_result_host_count (task_last_report (id), opts.min_qod)" \ " END", \ "result_hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " coalesce (report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'False Positive') * 1.0" \ " / nullif (report_result_host_count (task_last_report (id),"\ " opts.min_qod), 0)," \ " 0)" \ " END", \ "fp_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " coalesce (report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'Log') * 1.0" \ " / nullif (report_result_host_count (task_last_report (id),"\ " opts.min_qod), 0)," \ " 0)" \ " END", \ "log_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " coalesce (report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'Low') * 1.0" \ " / nullif (report_result_host_count (task_last_report (id),"\ " opts.min_qod), 0)," \ " 0)" \ " END", \ "low_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " coalesce (report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'Medium') * 1.0" \ " / nullif (report_result_host_count (task_last_report (id),"\ " opts.min_qod), 0)," \ " 0)" \ " END", \ "medium_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "CASE WHEN target IS null OR opts.ignore_severity != 0 THEN 0 ELSE" \ " coalesce (report_severity_count (task_last_report (id)," \ " opts.override, opts.min_qod," \ " 'High') * 1.0" \ " / nullif (report_result_host_count (task_last_report (id),"\ " opts.min_qod), 0)," \ " 0)" \ " END", \ "high_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT name FROM targets WHERE id = target)", \ "target", \ KEYWORD_TYPE_STRING \ } /** * @brief Task iterator WHERE columns. */ #define TASK_ITERATOR_WHERE_COLUMNS \ { \ TASK_ITERATOR_WHERE_COLUMNS_INNER, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Task iterator columns. */ #define TASK_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (tasks), \ TASK_ITERATOR_COLUMNS_INNER, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Task iterator minimal columns. */ #define TASK_ITERATOR_COLUMNS_MIN \ { \ GET_ITERATOR_COLUMNS (tasks), \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Task iterator minimal WHERE columns. */ #define TASK_ITERATOR_WHERE_COLUMNS_MIN \ { \ TASK_ITERATOR_COLUMNS_INNER, \ TASK_ITERATOR_WHERE_COLUMNS_INNER, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Generate the extra_tables string for a task iterator. * * @param[in] override Whether to apply overrides. * @param[in] min_qod Minimum QoD of results to count. * @param[in] ignore_severity Whether to ignore severity data. * @return Newly allocated string with the extra_tables clause. */ static gchar* task_iterator_opts_table (int override, int min_qod, int ignore_severity) { return g_strdup_printf (", (SELECT" " %d AS override," " %d AS min_qod," " %d AS ignore_severity)" " AS opts", override, min_qod, ignore_severity); } /** * @brief Initialise a task iterator, limited to current user's tasks. * * @param[in] iterator Task iterator. * @param[in] trash Whether to iterate over trashcan tasks. * @param[in] ignore_severity Whether to ignore severity data. */ static void init_user_task_iterator (iterator_t* iterator, int trash, int ignore_severity) { static column_t select_columns[] = TASK_ITERATOR_COLUMNS; gchar *extra_tables; gchar *columns; extra_tables = task_iterator_opts_table (0, MIN_QOD_DEFAULT, ignore_severity); columns = columns_build_select (select_columns); init_iterator (iterator, "SELECT %s" " FROM tasks%s" " WHERE " ACL_USER_OWNS () "%s;", columns, extra_tables, current_credentials.uuid, trash ? " AND hidden = 2" : " AND hidden < 2"); g_free (extra_tables); g_free (columns); } /** * @brief Initialise a task iterator. * * @param[in] iterator Task iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find target, 2 failed to find filter, * -1 error. */ int init_task_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = TASK_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TASK_ITERATOR_COLUMNS; static column_t where_columns[] = TASK_ITERATOR_WHERE_COLUMNS; static column_t columns_min[] = TASK_ITERATOR_COLUMNS_MIN; static column_t where_columns_min[] = TASK_ITERATOR_WHERE_COLUMNS_MIN; char *filter; int overrides, min_qod; const char *usage_type; gchar *extra_tables, *extra_where; int ret; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; overrides = filter_term_apply_overrides (filter ? filter : get->filter); min_qod = filter_term_min_qod (filter ? filter : get->filter); free (filter); extra_tables = task_iterator_opts_table (overrides, min_qod, 0); usage_type = get_data_get_extra (get, "usage_type"); extra_where = tasks_extra_where (get->trash, usage_type); ret = init_get_iterator2 (iterator, "task", get, /* SELECT columns. */ get->minimal ? columns_min : columns, get->minimal ? columns_min : columns, /* Filterable columns not in SELECT columns. */ get->minimal ? where_columns_min : where_columns, get->minimal ? where_columns_min : where_columns, filter_columns, 0, extra_tables, extra_where, NULL, current_credentials.uuid ? TRUE : FALSE, FALSE, NULL); g_free (extra_tables); g_free (extra_where); return ret; } /** * @brief Get the run status from a task iterator. * * @param[in] iterator Iterator. * * @return Task run status. */ task_status_t task_iterator_run_status (iterator_t* iterator) { task_status_t ret; if (iterator->done) return TASK_STATUS_INTERRUPTED; ret = (unsigned int) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT); return ret; } /** * @brief Get the number of reports of a task iterator. * * @param[in] iterator Iterator. * * @return Count of all task reports. */ int task_iterator_total_reports (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); } /** * @brief Get the first report UUID from a task iterator. * * @param[in] iterator Iterator. * * @return First report UUID. */ const char * task_iterator_first_report (iterator_t* iterator) { if (iterator->done) return 0; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 2); } /** * @brief Get the run status name from a task iterator. * * @param[in] iterator Iterator. * * @return Task run status name. */ const char * task_iterator_run_status_name (iterator_t* iterator) { if (iterator->done) return 0; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 3); } /** * @brief Get the last report UUID from a task iterator. * * @param[in] iterator Iterator. * * @return Last report UUID. */ const char * task_iterator_last_report (iterator_t* iterator) { if (iterator->done) return 0; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the number of reports of a task iterator. * * @param[in] iterator Iterator. * * @return Count of all task reports. */ int task_iterator_finished_reports (iterator_t *iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5); } /** * @brief Get the hosts ordering value from a task iterator. * * @param[in] iterator Iterator. * * @return Task hosts ordering. */ const char * task_iterator_hosts_ordering (iterator_t* iterator) { if (iterator->done) return 0; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Get the UUID of task scanner from a task iterator. * * @param[in] iterator Iterator. * * @return Task scanner if found, NULL otherwise. */ scanner_t task_iterator_scanner (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7); } /** * @brief Get the UUID of task scanner from a task iterator. * * @param[in] iterator Iterator. * * @return Task scanner if found, NULL otherwise. */ const char * task_iterator_usage_type (iterator_t* iterator) { if (iterator->done) return 0; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8); } /** * @brief Return whether a task is in use by a task. * * @param[in] task Task. * * @return 0. */ int task_in_use (task_t task) { task_status_t status; status = task_run_status (task); return status == TASK_STATUS_DELETE_REQUESTED || status == TASK_STATUS_DELETE_WAITING || status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED || status == TASK_STATUS_DELETE_ULTIMATE_WAITING || status == TASK_STATUS_REQUESTED || status == TASK_STATUS_RUNNING || status == TASK_STATUS_QUEUED || status == TASK_STATUS_STOP_REQUESTED || status == TASK_STATUS_STOP_WAITING; } /** * @brief Return whether a trashcan task is referenced by a task. * * @param[in] task Task. * * @return 0. */ int trash_task_in_use (task_t task) { return task_in_use (task); } /** * @brief Return whether a task is an Alterable Task. * * @param[in] task Task. * * @return 1 if Alterable, else 0. */ int task_alterable (task_t task) { return sql_int ("SELECT alterable FROM tasks" " WHERE id = %llu", task); } /** * @brief Return whether a task is writable. * * @param[in] task Task. * * @return 1 if writable, else 0. */ int task_writable (task_t task) { return sql_int ("SELECT hidden = 0 FROM tasks" " WHERE id = %llu", task); } /** * @brief Return whether a trashcan task is writable. * * @param[in] task Task. * * @return 1 if writable, else 0. */ int trash_task_writable (task_t task) { return sql_int ("SELECT hidden = 2 FROM tasks" " WHERE id = %llu", task); } /** * @brief Get the average duration of all finished reports of a task. * * @param[in] task Task. * * @return Average scan duration in seconds. */ int task_average_scan_duration (task_t task) { return sql_int ("SELECT avg (end_time - start_time) FROM reports" " WHERE task = %llu" " AND scan_run_status = %d" " AND coalesce (end_time, 0) != 0" " AND coalesce (start_time, 0) != 0;", task, TASK_STATUS_DONE); } /** * @brief Initialize the manage library: open db. * * @param[in] database Location of manage database. * * @return 1 if open already, else 0. */ static int init_manage_open_db (const db_conn_info_t *database) { if (sql_is_open ()) return 1; /* Open the database. */ if (sql_open (database)) { g_warning ("%s: sql_open failed", __func__); abort (); } /* Ensure the user session variables always exists. */ sql ("SET SESSION \"gvmd.user.id\" = 0;"); sql ("SET SESSION \"gvmd.tz_override\" = '';"); /* Attach the SCAP and CERT databases. */ manage_attach_databases (); return 0; } /** * @brief Initialize the manage library: define SQL functions. */ static void init_manage_create_functions () { lockfile_t lockfile; /* Lock to avoid an error return from Postgres when multiple processes * create a function at the same time. */ if (lockfile_lock (&lockfile, "gvm-create-functions")) abort (); if (manage_create_sql_functions ()) { lockfile_unlock (&lockfile); g_warning ("%s: failed to create functions", __func__); abort (); } lockfile_unlock (&lockfile); } /** * @brief Initialize the manage library for a process. * * Open the SQL database, attach secondary databases, and define functions. * * @param[in] database Location of manage database. */ void init_manage_process (const db_conn_info_t *database) { if (init_manage_open_db (database)) return; init_manage_create_functions (); } /** * @brief Reinitialize the manage library for a process. * * This is mandatory after a fork, to not carry open databases around (refer * to database documentation). */ void reinit_manage_process () { cleanup_manage_process (FALSE); init_manage_process (&gvmd_db_conn_info); } /** * @brief Update the memory cache of NVTs. * * @param[in] nvt NVT. * * @return NVTi if found, else NULL. */ nvti_t * lookup_nvti (const gchar *nvt) { return nvtis_lookup (nvti_cache, nvt); } /** * @brief Update the memory cache of NVTs. */ static void update_nvti_cache () { iterator_t nvts; nvtis_free (nvti_cache); nvti_cache = nvtis_new (); /* Because there are many NVTs and many refs it's slow to query the refs * for each NVT. So this query gets the NVTs and their refs at the same * time. * * The NVT data is duplicated in the result of the query when there are * multiple refs for an NVT, but the loop below uses nvtis_lookup to * check if we've already seen the NVT. This also means we don't have * to sort the data by NVT, which would make the query too slow. */ init_iterator (&nvts, "SELECT nvts.oid, vt_refs.type, vt_refs.ref_id," " vt_refs.ref_text" " FROM nvts" " LEFT OUTER JOIN vt_refs ON nvts.oid = vt_refs.vt_oid;"); while (next (&nvts)) { nvti_t *nvti; nvti = nvtis_lookup (nvti_cache, iterator_string (&nvts, 0)); if (nvti == NULL) { nvti = nvti_new (); nvti_set_oid (nvti, iterator_string (&nvts, 0)); nvtis_add (nvti_cache, nvti); } if (iterator_null (&nvts, 2)) /* No refs. */; else nvti_add_vtref (nvti, vtref_new (iterator_string (&nvts, 1), iterator_string (&nvts, 2), iterator_string (&nvts, 3))); } cleanup_iterator (&nvts); malloc_trim (0); } /** * @brief Update the memory cache of NVTs, if this has been requested. * * @return 0 success, 1 failed to get lock, -1 error. */ int manage_update_nvti_cache () { int ret; ret = sql_begin_immediate_giveup (); if (ret) return ret; if (sql_int ("SELECT value FROM %s.meta" " WHERE name = 'update_nvti_cache';", sql_schema ())) { update_nvti_cache (); sql ("UPDATE %s.meta SET value = 0 WHERE name = 'update_nvti_cache';", sql_schema ()); } sql_commit (); return 0; } /** * @brief Ensure the predefined scanner exists. * * @return 0 if success, -1 if error. */ static int check_db_scanners () { if (sql_int ("SELECT count(*) FROM scanners WHERE uuid = '%s';", SCANNER_UUID_DEFAULT) == 0) { sql ("INSERT INTO scanners" " (uuid, owner, name, host, port, type, ca_pub, credential," " creation_time, modification_time)" " VALUES ('" SCANNER_UUID_DEFAULT "', NULL, 'OpenVAS Default'," " '%s', 0, %d, NULL, NULL, m_now ()," " m_now ());", OPENVAS_DEFAULT_SOCKET, SCANNER_TYPE_OPENVAS); } if (sql_int ("SELECT count(*) FROM scanners WHERE uuid = '%s';", SCANNER_UUID_CVE) == 0) sql ("INSERT INTO scanners" " (uuid, owner, name, host, port, type, ca_pub, credential," " creation_time, modification_time)" " VALUES ('" SCANNER_UUID_CVE "', NULL, 'CVE'," " '', 0, %d, NULL, NULL, m_now (), m_now ());", SCANNER_TYPE_CVE); return 0; } /** * @brief Initialize the default settings. * * Ensure all the default manager settings exist. */ static void check_db_settings () { if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '6765549a-934e-11e3-b358-406186ea4fc5'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('6765549a-934e-11e3-b358-406186ea4fc5', NULL," " 'User Interface Language'," " 'Preferred language to be used in client user interfaces.'," " 'Browser Language');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '" SETTING_UUID_ROWS_PER_PAGE "'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('" SETTING_UUID_ROWS_PER_PAGE "', NULL, 'Rows Per Page'," " 'The default number of rows displayed in any listing.'," " 10);"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '" SETTING_UUID_MAX_ROWS_PER_PAGE "'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('" SETTING_UUID_MAX_ROWS_PER_PAGE "', NULL, 'Max Rows Per Page'," " 'The default maximum number of rows displayed in any listing.'," " 1000);"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '77ec2444-e7f2-4a80-a59b-f4237782d93f'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('77ec2444-e7f2-4a80-a59b-f4237782d93f', NULL, 'Dynamic Severity'," " 'Whether to use dynamic severity scores by default.'," " '0');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '578a1c14-e2dc-45ef-a591-89d31391d007'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('578a1c14-e2dc-45ef-a591-89d31391d007', NULL, 'Auto-Refresh'," " 'The delay between automatic page refreshs in seconds.'," " '0');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = 'a6ac88c5-729c-41ba-ac0a-deea4a3441f2'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('a6ac88c5-729c-41ba-ac0a-deea4a3441f2', NULL," " 'Details Export File Name'," " 'File name format string for the export of resource details.'," " '%%T-%%U');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '0872a6ed-4f85-48c5-ac3f-a5ef5e006745'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('0872a6ed-4f85-48c5-ac3f-a5ef5e006745', NULL," " 'List Export File Name'," " 'File name format string for the export of resource lists.'," " '%%T-%%D');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = 'e1a2ae0b-736e-4484-b029-330c9e15b900'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('e1a2ae0b-736e-4484-b029-330c9e15b900', NULL," " 'Report Export File Name'," " 'File name format string for the export of reports.'," " '%%T-%%U');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '7eda49c5-096c-4bef-b1ab-d080d87300df'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('7eda49c5-096c-4bef-b1ab-d080d87300df', NULL," " 'Default Severity'," " 'Severity to use if none is specified or available from SecInfo.'," " '10.0');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = 'a09285b0-2d47-49b6-a4ef-946ee71f1d5c'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('a09285b0-2d47-49b6-a4ef-946ee71f1d5c', NULL," " 'Auto Cache Rebuild'," " 'Whether to rebuild report caches on changes affecting severity.'," " '1');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '" SETTING_UUID_LSC_DEB_MAINTAINER "'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('" SETTING_UUID_LSC_DEB_MAINTAINER "', NULL," " 'Debian LSC Package Maintainer'," " 'Maintainer email address used in generated Debian LSC packages.'," " '');"); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '" SETTING_UUID_FEED_IMPORT_ROLES "'" " AND " ACL_IS_GLOBAL () ";") == 0) sql ("INSERT into settings (uuid, owner, name, comment, value)" " VALUES" " ('" SETTING_UUID_FEED_IMPORT_ROLES "', NULL," " 'Feed Import Roles'," " 'Roles given access to new resources from feed.'," " '" ROLE_UUID_ADMIN "," ROLE_UUID_USER "');"); } /** * @brief Add command permission to role. * * Caller must ensure args are SQL escaped. * * @param[in] role_id Role. * @param[in] permission Permission. */ static void add_role_permission (const gchar *role_id, const gchar *permission) { if (sql_int ("SELECT EXISTS (SELECT * FROM permissions" " WHERE owner IS NULL" " AND name = lower ('%s')" " AND resource_type = ''" " AND resource = 0" " AND subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '%s'));", permission, role_id) == 0) sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " (make_uuid (), NULL, lower ('%s'), '', ''," " 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role'," " (SELECT id FROM roles WHERE uuid = '%s')," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());", permission, role_id); } /** * @brief Add resource permission to role. * * Caller must ensure args are SQL escaped. * * @param[in] role_id Role ID. * @param[in] permission Permission. * @param[in] type Resource type. * @param[in] resource_id Resource ID. */ void add_role_permission_resource (const gchar *role_id, const gchar *permission, const gchar *type, const gchar *resource_id) { if (sql_int ("SELECT EXISTS (SELECT * FROM permissions" " WHERE owner IS NULL" " AND name = lower ('%s')" " AND resource_type = '%s'" " AND resource = (SELECT id FROM %ss" " WHERE uuid = '%s')" " AND subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '%s'));", permission, type, type, resource_id, role_id) == 0) sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " (make_uuid (), NULL, lower ('%s'), '', '%s'," " (SELECT id FROM %ss WHERE uuid = '%s'), '%s'," " " G_STRINGIFY (LOCATION_TABLE) ", 'role'," " (SELECT id FROM roles WHERE uuid = '%s')," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());", permission, type, type, resource_id, resource_id, role_id); } /** * @brief Ensure that the databases are the right versions. * * @return 0 success, -1 error, -2 database is wrong version. */ static int check_db_versions () { char *database_version; int scap_db_version, cert_db_version; database_version = sql_string ("SELECT value FROM %s.meta" " WHERE name = 'database_version';", sql_schema ()); if (database_version) { if (strcmp (database_version, G_STRINGIFY (GVMD_DATABASE_VERSION))) { g_message ("%s: database version of database: %s", __func__, database_version); g_message ("%s: database version supported by manager: %s", __func__, G_STRINGIFY (GVMD_DATABASE_VERSION)); g_free (database_version); return -2; } g_free (database_version); } /* Check SCAP database version. */ scap_db_version = manage_scap_db_version (); if (scap_db_version == -1) g_message ("No SCAP database found"); else if (scap_db_version != manage_scap_db_supported_version ()) { g_message ("%s: database version of SCAP database: %i", __func__, scap_db_version); g_message ("%s: SCAP database version supported by manager: %s", __func__, G_STRINGIFY (GVMD_SCAP_DATABASE_VERSION)); return -2; } /* Check CERT database version. */ cert_db_version = manage_cert_db_version (); if (cert_db_version == -1) g_message ("No CERT database found"); else if (cert_db_version != manage_cert_db_supported_version ()) { g_message ("%s: database version of CERT database: %i", __func__, cert_db_version); g_message ("%s: CERT database version supported by manager: %s", __func__, G_STRINGIFY (GVMD_CERT_DATABASE_VERSION)); return -2; } return 0; } /** * @brief Ensures the sanity of nvts cache in DB. */ static void check_db_nvt_selectors () { /* Ensure every part of the predefined selector exists. * This restores entries lost due to the error solved 2010-08-13 by r8805. */ if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL) ";") == 0) { sql ("INSERT into nvt_selectors (name, exclude, type, family_or_nvt)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, " G_STRINGIFY (NVT_SELECTOR_TYPE_ALL) ", NULL);"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.810002';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) "," /* OID of the "CPE Inventory" NVT. */ " '1.3.6.1.4.1.25623.1.0.810002', 'Service detection');"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.810003';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) "," /* OID of the "Host Summary" NVT. */ " '1.3.6.1.4.1.25623.1.0.810003', 'General');"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY) " AND family_or_nvt = 'Port scanners';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, " G_STRINGIFY (NVT_SELECTOR_TYPE_FAMILY) "," " 'Port scanners', 'Port scanners');"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.14259';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) "," /* OID of the "Nmap (NASL wrapper)" NVT. */ " '1.3.6.1.4.1.25623.1.0.14259', 'Port scanners');"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) " AND family_or_nvt = '" OID_PING_HOST "';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 0, " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) "," " '" OID_PING_HOST "', 'Port scanners');"); } if (sql_int ("SELECT count(*) FROM nvt_selectors WHERE name =" " '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND type = " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) " AND family_or_nvt = '1.3.6.1.4.1.25623.1.0.80109';") == 0) { sql ("INSERT into nvt_selectors" " (name, exclude, type, family_or_nvt, family)" " VALUES ('" MANAGE_NVT_SELECTOR_UUID_ALL "', 1, " G_STRINGIFY (NVT_SELECTOR_TYPE_NVT) "," /* OID of the "w3af (NASL wrapper)" NVT. */ " '1.3.6.1.4.1.25623.1.0.80109', 'Web application abuses');"); } } /** * @brief Add permissions for all global resources. * * @param[in] role_uuid UUID of role. */ static void add_permissions_on_globals (const gchar *role_uuid) { iterator_t scanners; /* Scanners are global when created from the command line. */ init_iterator (&scanners, "SELECT id, uuid FROM scanners WHERE owner is NULL;"); while (next (&scanners)) add_role_permission_resource (role_uuid, "GET_SCANNERS", "scanner", iterator_string (&scanners, 1)); cleanup_iterator (&scanners); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_ADMIN); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_GUEST); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_INFO); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_MONITOR); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_USER); add_role_permission_resource (role_uuid, "GET_ROLES", "role", ROLE_UUID_OBSERVER); } /** * @brief Ensure the predefined permissions exists. */ static void check_db_permissions () { command_t *command; if (sql_int ("SELECT count(*) FROM permissions" " WHERE uuid = '" PERMISSION_UUID_ADMIN_EVERYTHING "';") == 0) sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " ('" PERMISSION_UUID_ADMIN_EVERYTHING "', NULL, 'Everything', '', ''," " 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role'," " (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_ADMIN "')," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM permissions" " WHERE uuid = '" PERMISSION_UUID_SUPER_ADMIN_EVERYTHING "';") == 0) { sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " ('" PERMISSION_UUID_SUPER_ADMIN_EVERYTHING "', NULL, 'Everything'," " '', '', 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role'," " (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "')," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());"); sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " (make_uuid (), NULL, 'Super'," " '', '', 0, '', " G_STRINGIFY (LOCATION_TABLE) ", 'role'," " (SELECT id FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "')," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());"); } if (sql_int ("SELECT count(*) FROM permissions" " WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_GUEST "')" " AND resource = 0;") <= 1) { /* Clean-up any remaining permissions. */ sql ("DELETE FROM permissions WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_GUEST "');"); } add_role_permission (ROLE_UUID_GUEST, "AUTHENTICATE"); add_role_permission (ROLE_UUID_GUEST, "HELP"); add_role_permission (ROLE_UUID_GUEST, "GET_AGGREGATES"); add_role_permission (ROLE_UUID_GUEST, "GET_FILTERS"); add_role_permission (ROLE_UUID_GUEST, "GET_INFO"); add_role_permission (ROLE_UUID_GUEST, "GET_NVTS"); add_role_permission (ROLE_UUID_GUEST, "GET_SETTINGS"); if (sql_int ("SELECT count(*) FROM permissions" " WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_INFO "')" " AND resource = 0;") <= 1) { /* Clean-up any remaining permissions. */ sql ("DELETE FROM permissions WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_INFO "');"); } add_role_permission (ROLE_UUID_INFO, "AUTHENTICATE"); add_role_permission (ROLE_UUID_INFO, "HELP"); add_role_permission (ROLE_UUID_INFO, "GET_AGGREGATES"); add_role_permission (ROLE_UUID_INFO, "GET_INFO"); add_role_permission (ROLE_UUID_INFO, "GET_NVTS"); add_role_permission (ROLE_UUID_INFO, "GET_SETTINGS"); add_role_permission (ROLE_UUID_INFO, "MODIFY_SETTING"); if (sql_int ("SELECT count(*) FROM permissions" " WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_MONITOR "')" " AND resource = 0;") <= 1) { /* Clean-up any remaining permissions. */ sql ("DELETE FROM permissions WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_MONITOR "');"); } add_role_permission (ROLE_UUID_MONITOR, "AUTHENTICATE"); add_role_permission (ROLE_UUID_MONITOR, "GET_SETTINGS"); add_role_permission (ROLE_UUID_MONITOR, "GET_SYSTEM_REPORTS"); add_role_permission (ROLE_UUID_MONITOR, "HELP"); if (sql_int ("SELECT count(*) FROM permissions" " WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_USER "')" " AND resource = 0;") <= 1) { /* Clean-up any remaining permissions. */ sql ("DELETE FROM permissions WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_USER "');"); } command = gmp_commands; while (command[0].name) { if (strstr (command[0].name, "DESCRIBE_AUTH") == NULL && strcmp (command[0].name, "GET_VERSION") && strstr (command[0].name, "GROUP") == NULL && strstr (command[0].name, "ROLE") == NULL && strstr (command[0].name, "SYNC") == NULL && strstr (command[0].name, "USER") == NULL) add_role_permission (ROLE_UUID_USER, command[0].name); command++; } if (sql_int ("SELECT count(*) FROM permissions" " WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_OBSERVER "')" " AND resource = 0;") <= 1) { /* Clean-up any remaining permissions. */ sql ("DELETE FROM permissions WHERE subject_type = 'role'" " AND subject = (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_OBSERVER "');"); } command = gmp_commands; while (command[0].name) { if ((strstr (command[0].name, "GET") == command[0].name) && strcmp (command[0].name, "GET_GROUPS") && strcmp (command[0].name, "GET_ROLES") && strcmp (command[0].name, "GET_USERS") && strcmp (command[0].name, "GET_VERSION")) add_role_permission (ROLE_UUID_OBSERVER, command[0].name); command++; } add_role_permission (ROLE_UUID_OBSERVER, "AUTHENTICATE"); add_role_permission (ROLE_UUID_OBSERVER, "HELP"); add_role_permission (ROLE_UUID_OBSERVER, "MODIFY_SETTING"); add_permissions_on_globals (ROLE_UUID_ADMIN); add_permissions_on_globals (ROLE_UUID_GUEST); add_permissions_on_globals (ROLE_UUID_OBSERVER); add_permissions_on_globals (ROLE_UUID_USER); } /** * @brief Ensure the predefined roles exists. */ static void check_db_roles () { if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_ADMIN "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_ADMIN "', NULL, 'Admin'," " 'Administrator. Full privileges.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_GUEST "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_GUEST "', NULL, 'Guest'," " 'Guest.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_INFO "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_INFO "', NULL, 'Info'," " 'Information browser.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_MONITOR "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_MONITOR "', NULL, 'Monitor'," " 'Performance monitor.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_USER "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_USER "', NULL, 'User'," " 'Standard user.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_SUPER_ADMIN "', NULL, 'Super Admin'," " 'Super administrator. Full privileges with access to all users.'," " m_now (), m_now ());"); if (sql_int ("SELECT count(*) FROM roles" " WHERE uuid = '" ROLE_UUID_OBSERVER "';") == 0) sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " ('" ROLE_UUID_OBSERVER "', NULL, 'Observer'," " 'Observer.'," " m_now (), m_now ());"); } /** * @brief Cleanup the auth_cache table. */ static void clean_auth_cache () { sql ("DELETE FROM auth_cache;"); } /** * @brief Tries to migrate sensor type scanners to match the relays. * * @return A string describing the results or NULL on error. */ static gchar * manage_migrate_relay_sensors () { iterator_t scanners; int gmp_successes, gmp_failures, osp_failures; gmp_successes = gmp_failures = osp_failures = 0; if (get_relay_mapper_path () == NULL) { g_warning ("%s: No relay mapper set", __func__); return NULL; } init_iterator (&scanners, "SELECT id, uuid, type, host, port FROM scanners" " WHERE type = %d", SCANNER_TYPE_OSP_SENSOR); while (next (&scanners)) { scanner_type_t type; const char *scanner_id, *host; int port; scanner_id = iterator_string (&scanners, 1); type = iterator_int (&scanners, 2); host = iterator_string (&scanners, 3); port = iterator_int (&scanners, 4); if (relay_supports_scanner_type (host, port, type) == FALSE) { if (type == SCANNER_TYPE_OSP_SENSOR) { g_message ("%s: No relay found for OSP Sensor %s (%s:%d).", __func__, scanner_id, host, port); osp_failures++; } else g_warning ("%s: Unexpected type for scanner %s: %d", __func__, scanner_id, type); } } cleanup_iterator (&scanners); if (gmp_successes == 0 && gmp_failures == 0 && osp_failures == 0) return g_strdup ("All GMP or OSP sensors up to date."); else { GString *message = g_string_new (""); g_string_append_printf (message, "%d sensors(s) not matching:", gmp_successes + gmp_failures + osp_failures); if (gmp_successes) g_string_append_printf (message, " %d GMP scanner(s) migrated to OSP.", gmp_successes); if (gmp_failures) g_string_append_printf (message, " %d GMP scanner(s) not migrated.", gmp_failures); if (osp_failures) g_string_append_printf (message, " %d OSP sensor(s) not migrated.", osp_failures); return g_string_free (message, FALSE); } } /** * @brief Ensure that the database is in order. * * Only called by init_manage_internal, and ultimately only by the main process. * * @param[in] check_encryption_key Whether to check encryption key. * * @return 0 success, -1 error. */ static int check_db (int check_encryption_key) { /* The file locks managed at startup ensure that this is the only Manager * process accessing the db. Nothing else should be accessing the db, access * should always go through Manager. */ sql_begin_immediate (); if (check_db_extensions ()) goto fail; create_tables (); check_db_sequences (); set_db_version (GVMD_DATABASE_VERSION); check_db_roles (); check_db_nvt_selectors (); check_db_nvts (); check_db_port_lists (); clean_auth_cache (); if (check_db_scanners ()) goto fail; if (check_db_report_formats ()) goto fail; if (check_db_report_formats_trash ()) goto fail; check_db_permissions (); check_db_settings (); cleanup_schedule_times (); if (check_encryption_key && check_db_encryption_key ()) goto fail; sql_commit (); return 0; fail: sql_rollback (); return -1; } /** * @brief Stop any active tasks. */ static void stop_active_tasks () { iterator_t tasks; get_data_t get; /* Set requested and running tasks to stopped. */ assert (current_credentials.uuid == NULL); memset (&get, '\0', sizeof (get)); get.ignore_pagination = 1; init_task_iterator (&tasks, &get); while (next (&tasks)) { switch (task_iterator_run_status (&tasks)) { case TASK_STATUS_DELETE_REQUESTED: case TASK_STATUS_DELETE_ULTIMATE_REQUESTED: case TASK_STATUS_DELETE_ULTIMATE_WAITING: case TASK_STATUS_DELETE_WAITING: case TASK_STATUS_REQUESTED: case TASK_STATUS_RUNNING: case TASK_STATUS_QUEUED: case TASK_STATUS_STOP_REQUESTED: case TASK_STATUS_STOP_WAITING: { task_t index = get_iterator_resource (&tasks); /* Set the current user, for event checks. */ current_credentials.uuid = task_owner_uuid (index); task_last_report_any_status (index, &global_current_report); set_task_interrupted (index, "Task process exited abnormally" " (e.g. machine lost power or process was" " sent SIGKILL)." " Setting scan status to Interrupted."); global_current_report = 0; free (current_credentials.uuid); break; } default: break; } } cleanup_iterator (&tasks); current_credentials.uuid = NULL; /* Set requested and running reports to stopped. */ sql ("UPDATE reports SET scan_run_status = %u" " WHERE scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u;", TASK_STATUS_INTERRUPTED, TASK_STATUS_DELETE_REQUESTED, TASK_STATUS_DELETE_ULTIMATE_REQUESTED, TASK_STATUS_DELETE_ULTIMATE_WAITING, TASK_STATUS_DELETE_WAITING, TASK_STATUS_REQUESTED, TASK_STATUS_RUNNING, TASK_STATUS_QUEUED, TASK_STATUS_STOP_REQUESTED, TASK_STATUS_STOP_WAITING); } /** * @brief Clean up database tables. * * Remove superfluous entries from tables. */ static void cleanup_tables () { /* Remove group and role assignments of deleted users. * * This should be a migrator, but this way is easier to backport. */ sql ("DELETE FROM group_users" " WHERE \"user\" NOT IN (SELECT id FROM users);"); sql ("DELETE FROM group_users_trash" " WHERE \"user\" NOT IN (SELECT id FROM users);"); sql ("DELETE FROM role_users" " WHERE \"user\" NOT IN (SELECT id FROM users);"); sql ("DELETE FROM role_users_trash" " WHERE \"user\" NOT IN (SELECT id FROM users);"); /* * Remove permissions of deleted users, groups and roles. */ sql ("DELETE FROM permissions" " WHERE (subject_type = 'user'" " AND subject NOT IN (SELECT id FROM users))" " OR (subject_type = 'group'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject NOT IN (SELECT id FROM groups))" " OR (subject_type = 'group'" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) " AND subject NOT IN (SELECT id FROM groups_trash))" " OR (subject_type = 'role'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject NOT IN (SELECT id FROM roles))" " OR (subject_type = 'role'" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) " AND subject NOT IN (SELECT id FROM roles_trash));"); sql ("DELETE FROM permissions_trash" " WHERE (subject_type = 'user'" " AND subject NOT IN (SELECT id FROM users))" " OR (subject_type = 'group'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject NOT IN (SELECT id FROM groups))" " OR (subject_type = 'group'" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) " AND subject NOT IN (SELECT id FROM groups_trash))" " OR (subject_type = 'role'" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) " AND subject NOT IN (SELECT id FROM roles))" " OR (subject_type = 'role'" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) " AND subject NOT IN (SELECT id FROM roles_trash));"); sql ("DELETE FROM permissions_get_tasks" " WHERE \"user\" NOT IN (SELECT id FROM users);"); } /** * @brief Initialize the manage library. * * Check DB version, do startup database checks, load the NVT cache. * Optionally also stop active tasks. * * @param[in] log_config Log configuration. * @param[in] database Location of database. * @param[in] max_ips_per_target Max number of IPs per target. * @param[in] max_email_attachment_size Max size of email attachments. * @param[in] max_email_include_size Max size of email inclusions. * @param[in] max_email_message_size Max size of email user message text. * @param[in] stop_tasks Stop any active tasks. * @param[in] fork_connection Function to fork a connection that will * accept GMP requests. Used to start tasks * with GMP when an alert occurs. * @param[in] skip_db_check Skip DB check. * @param[in] check_encryption_key Check encryption key if doing DB check. * * @return 0 success, -1 error, -2 database is wrong version, * -4 max_ips_per_target out of range. */ static int init_manage_internal (GSList *log_config, const db_conn_info_t *database, int max_ips_per_target, int max_email_attachment_size, int max_email_include_size, int max_email_message_size, int stop_tasks, manage_connection_forker_t fork_connection, int skip_db_check, int check_encryption_key) { int ret; /* Summary of init cases: * * daemon [--foreground] * init_gmpd cache 0 * init_manage * serve_and_schedule * forks child (serve_gmp) * init_gmpd_process * init_manage_process * ... * event * fork_connection_for_event * fork one * init_gmpd_process * init_manage_process * serve_client * fork two * gmp_auth, gmp_start_task_report. * ... * manage_schedule * fork_connection_for_scheduler * fork one * init_gmpd_process * init_manage_process * serve_client * fork two * gmp_auth, gmp_start_task_report, gmp_resume_task_report. * --create-user --delete-user --get-users * manage_create, ... * init_manage_helper * --encrypt/decrypt-all-credentials * manage_encrypt_... * init_manage_helper * --migrate * manage_migrate * init_manage_process (sorts out db state itself) */ if ((max_ips_per_target <= 0) || (max_ips_per_target > MANAGE_ABSOLUTE_MAX_IPS_PER_TARGET)) return -4; max_hosts = max_ips_per_target; if (max_email_attachment_size) max_attach_length = max_email_attachment_size; if (max_email_include_size) max_content_length = max_email_include_size; if (max_email_message_size) max_email_message_length = max_email_message_size; g_log_set_handler (G_LOG_DOMAIN, ALL_LOG_LEVELS, (GLogFunc) gvm_log_func, log_config); memset (¤t_credentials, '\0', sizeof (current_credentials)); init_manage_open_db (database); /* Check that the versions of the databases are correct. */ ret = check_db_versions (); if (ret) return ret; init_manage_create_functions (); /* Ensure the database is complete, removing superfluous rows. * * Assume that all other running processes are from the same Manager version, * because some of these checks will modify the database if it is out of * date. This is relevant because the caller may be a command option process * like a --create-user process. */ if (skip_db_check == 0) { /* This only happens for init_manage callers with skip_db_check set to 0 * and init_manage_helper callers. So there are only 2 callers: * * 1 the main process * 2 a helper processes (--create-user, --get-users, etc) when the * main process is not running. */ ret = check_db (check_encryption_key); if (ret) return ret; cleanup_tables (); /* Set max_hosts in db, so database server side can access it. */ sql ("DELETE FROM meta WHERE name = 'max_hosts';"); sql ("INSERT INTO meta (name, value) VALUES ('max_hosts', %i);", max_hosts); } if (stop_tasks) /* Stop any active tasks. */ stop_active_tasks (); /* Load the NVT cache into memory. */ if (nvti_cache == NULL) update_nvti_cache (); if (skip_db_check == 0) /* Requires NVT cache. */ check_db_configs (); sql_close (); gvmd_db_conn_info.name = database->name ? g_strdup (database->name) : NULL; gvmd_db_conn_info.host = database->host ? g_strdup (database->host) : NULL; gvmd_db_conn_info.port = database->port ? g_strdup (database->port) : NULL; gvmd_db_conn_info.user = database->user ? g_strdup (database->user) : NULL; if (fork_connection) manage_fork_connection = fork_connection; return 0; } /** * @brief Initialize the manage library. * * Check DB version, do startup database checks, load the NVT cache. * * Ensure all tasks are in a clean initial state. * * Beware that calling this function while tasks are running may lead to * problems. * * @param[in] log_config Log configuration. * @param[in] database Location of database. * @param[in] max_ips_per_target Max number of IPs per target. * @param[in] max_email_attachment_size Max size of email attachments. * @param[in] max_email_include_size Max size of email inclusions. * @param[in] max_email_message_size Max size of email user message text. * @param[in] fork_connection Function to fork a connection that will * accept GMP requests. Used to start tasks * with GMP when an alert occurs. * @param[in] skip_db_check Skip DB check. * * @return 0 success, -1 error, -2 database is wrong version, -3 database needs * to be initialised from server, -4 max_ips_per_target out of range. */ int init_manage (GSList *log_config, const db_conn_info_t *database, int max_ips_per_target, int max_email_attachment_size, int max_email_include_size, int max_email_message_size, manage_connection_forker_t fork_connection, int skip_db_check) { return init_manage_internal (log_config, database, max_ips_per_target, max_email_attachment_size, max_email_include_size, max_email_message_size, 1, /* Stop active tasks. */ fork_connection, skip_db_check, 1); /* Check encryption key if checking db. */ } /** * @brief Initialize the manage library for a helper program. * * This should be called at the beginning of any program that accesses the * database. Forked processes should call init_manage_process. The daemon * itself calls init_manage, including in NVT cache mode. * * @param[in] log_config Log configuration. * @param[in] database Location of database. * @param[in] max_ips_per_target Max number of IPs per target. * * @return 0 success, -1 error, -2 database is wrong version, -3 database needs * to be initialised from server, -4 max_ips_per_target out of range. */ int init_manage_helper (GSList *log_config, const db_conn_info_t *database, int max_ips_per_target) { return init_manage_internal (log_config, database, max_ips_per_target, 0, /* Default max_email_attachment_size. */ 0, /* Default max_email_include_size. */ 0, /* Default max_email_message_size */ 0, /* Stop active tasks. */ NULL, /* Skip DB check if main process is running, to * avoid locking issues when creating tables. * * Safe because main process did the check. */ lockfile_locked ("gvm-serving") ? 1 /* Skip DB check. */ : 0, /* Do DB check. */ 0); /* Dummy. */ } /** * @brief Cleanup the manage library. * * Optionally put any running task in the interrupted state and close the * database. * * @param[in] cleanup If TRUE perform all cleanup operations, else only * those required at the start of a forked process. */ void cleanup_manage_process (gboolean cleanup) { if (sql_is_open ()) { if (cleanup) { if (current_scanner_task) { if (global_current_report) { result_t result; result = make_result (current_scanner_task, "", "", "", "", "Error Message", "Interrupting scan because GVM is" " exiting.", NULL); report_add_result (global_current_report, result); } set_task_run_status (current_scanner_task, TASK_STATUS_INTERRUPTED); } sql_close (); } else sql_close_fork (); } } /** * @brief Cleanup as immediately as possible. * * Put any running task in the error state and close the database. * * Intended for handlers for signals like SIGSEGV and SIGABRT. * * @param[in] signal Dummy argument for use as signal handler. */ void manage_cleanup_process_error (int signal) { g_debug ("Received %s signal", strsignal (signal)); if (sql_is_open ()) { if (current_scanner_task) { g_warning ("%s: Error exit, setting running task to Interrupted", __func__); set_task_interrupted (current_scanner_task, "Error exit, setting running task to" " Interrupted."); } sql_close (); } } /** * @brief Cleanup as immediately as possible. */ void manage_reset_currents () { global_current_report = 0; current_scanner_task = (task_t) 0; free_credentials (¤t_credentials); } /** * @brief Get user hash. * * This is for "file" users, now entirely stored in db. * * @param[in] username User name. * * @return Hash. */ gchar * manage_user_hash (const gchar *username) { gchar *hash, *quoted_username; quoted_username = sql_quote (username); hash = sql_string ("SELECT password FROM users WHERE name = '%s';", quoted_username); g_free (quoted_username); return hash; } /** * @brief Get user uuid. * * @param[in] username User name. * @param[in] method Authentication method. * * @return UUID. */ static gchar * user_uuid_method (const gchar *username, auth_method_t method) { gchar *uuid, *quoted_username, *quoted_method; quoted_username = sql_quote (username); quoted_method = sql_quote (auth_method_name (method)); uuid = sql_string ("SELECT uuid FROM users" " WHERE name = '%s' AND method = '%s';", quoted_username, quoted_method); g_free (quoted_username); g_free (quoted_method); return uuid; } /** * @brief Check whether LDAP is enabled. * * @return 0 no, else yes. */ static int ldap_auth_enabled () { if (gvm_auth_ldap_enabled ()) return sql_int ("SELECT coalesce ((SELECT CAST (value AS INTEGER) FROM meta" " WHERE name = 'ldap_enable')," " 0);"); return 0; } /** * @brief Check whether RADIUS is enabled. * * @return 0 no, else yes. */ static int radius_auth_enabled () { if (gvm_auth_radius_enabled ()) return sql_int ("SELECT coalesce ((SELECT CAST (value AS INTEGER) FROM meta" " WHERE name = 'radius_enable')," " 0);"); return 0; } /** * @brief Check if user exists. * * @param[in] name User name. * @param[in] method Auth method. * * @return 1 yes, 0 no. */ static int user_exists_method (const gchar *name, auth_method_t method) { gchar *quoted_name, *quoted_method; int ret; quoted_name = sql_quote (name); quoted_method = sql_quote (auth_method_name (method)); ret = sql_int ("SELECT count (*) FROM users" " WHERE name = '%s' AND method = '%s';", quoted_name, quoted_method); g_free (quoted_name); g_free (quoted_method); return ret; } /** * @brief Get user uuid, trying all authentication methods. * * @param[in] name User name. * * @return UUID. */ static gchar * user_uuid_any_method (const gchar *name) { if (ldap_auth_enabled () && user_exists_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT)) return user_uuid_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT); if (radius_auth_enabled () && user_exists_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT)) return user_uuid_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT); if (user_exists_method (name, AUTHENTICATION_METHOD_FILE)) return user_uuid_method (name, AUTHENTICATION_METHOD_FILE); return NULL; } /** * @brief Ensure the user exists in the database. * * @param[in] name User name. * @param[in] method Auth method. * * @return 0 success. */ static int user_ensure_in_db (const gchar *name, const gchar *method) { gchar *quoted_name, *quoted_method; if ((method == NULL) || (strcasecmp (method, "file") == 0)) /* A "file" user, now entirely stored in db. */ return 0; /* SELECT then INSERT instead of using "INSERT OR REPLACE", so that the * id stays the same. */ quoted_name = sql_quote (name); quoted_method = sql_quote (method); if (sql_int ("SELECT count(*)" " FROM users WHERE name = '%s' and method = '%s';", quoted_name, quoted_method)) { g_free (quoted_method); g_free (quoted_name); return 0; } sql ("INSERT INTO users" " (uuid, owner, name, comment, password, timezone, method, hosts," " hosts_allow, ifaces, ifaces_allow, creation_time, modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '', NULL, NULL, '%s', '', 2, '', 2, m_now (), m_now ());", current_credentials.uuid, quoted_name, quoted_method); g_free (quoted_method); g_free (quoted_name); return 0; } /** * @brief Check if user exists. * * @param[in] name User name. * * @return 1 yes, 0 no. */ static int user_exists (const gchar *name) { if (ldap_auth_enabled () && user_exists_method (name, AUTHENTICATION_METHOD_LDAP_CONNECT)) return 1; if (radius_auth_enabled () && user_exists_method (name, AUTHENTICATION_METHOD_RADIUS_CONNECT)) return 1; return user_exists_method (name, AUTHENTICATION_METHOD_FILE); } /** * @brief Set credentials for authenticate. * * @param[in] credentials Credentials. * * @return 0 success, 99 permission denied. */ static int credentials_setup (credentials_t *credentials) { assert (credentials->uuid); credentials->role = g_strdup (acl_user_is_super_admin (credentials->uuid) ? "Super Admin" : (acl_user_is_admin (credentials->uuid) ? "Admin" : (acl_user_is_observer (credentials->uuid) ? "Observer" : (acl_user_is_user (credentials->uuid) ? "User" : "")))); if (acl_user_may ("authenticate") == 0) { free (credentials->uuid); credentials->uuid = NULL; g_free (credentials->role); credentials->role = NULL; return 99; } credentials->timezone = sql_string ("SELECT timezone FROM users" " WHERE uuid = '%s';", credentials->uuid); credentials->severity_class = sql_string ("SELECT value FROM settings" " WHERE name = 'Severity Class'" " AND " ACL_GLOBAL_OR_USER_OWNS () " ORDER BY coalesce (owner, 0) DESC LIMIT 1;", credentials->uuid); credentials->dynamic_severity = sql_int ("SELECT value FROM settings" " WHERE name = 'Dynamic Severity'" " AND " ACL_GLOBAL_OR_USER_OWNS () " ORDER BY coalesce (owner, 0) DESC LIMIT 1;", credentials->uuid); credentials->default_severity = sql_double ("SELECT value FROM settings" " WHERE name = 'Default Severity'" " AND " ACL_GLOBAL_OR_USER_OWNS () " ORDER BY coalesce (owner, 0) DESC LIMIT 1;", credentials->uuid); return 0; } /** * @brief Search for LDAP or RADIUS credentials in the recently-used * authentication cache. * * @param[in] username Username. * @param[in] password Password. * @param[in] method 0 for LDAP, 1 for RADIUS. * * @return 0 on success, -1 on failure. */ static int auth_cache_find (const char *username, const char *password, int method) { char *hash, *quoted_username; int ret; quoted_username = sql_quote (username); hash = sql_string ("SELECT hash FROM auth_cache WHERE username = '%s'" " AND method = %i AND creation_time >= m_now () - 300;", quoted_username, method); g_free (quoted_username); if (!hash) return -1; // verify for VALID or OUTDATED but don't update ret = manage_authentication_verify(hash, password); switch(ret){ case GMA_HASH_INVALID: ret = 1; break; case GMA_HASH_VALID_BUT_DATED: ret = 0; break; case GMA_SUCCESS: ret = 0; break; default: ret = -1; break; } g_free (hash); return ret; } /** * @brief Add LDAP or RADIUS credentials to the recently-used authentication * cache. * * @param[in] username Username. * @param[in] password Password. * @param[in] method 0 for LDAP, 1 for RADIUS. */ static void auth_cache_insert (const char *username, const char *password, int method) { char *hash, *quoted_username; quoted_username = sql_quote (username); hash = manage_authentication_hash(password); sql ("INSERT INTO auth_cache (username, hash, method, creation_time)" " VALUES ('%s', '%s', %i, m_now ());", quoted_username, hash, method); /* Cleanup cache */ sql ("DELETE FROM auth_cache WHERE creation_time < m_now () - 300"); } /** * @brief Authenticate, trying any method. * * @param[in] username Username. * @param[in] password Password. * @param[out] auth_method Auth method return. * * @return 0 authentication success, 1 authentication failure, 99 permission * denied, -1 error. */ static int authenticate_any_method (const gchar *username, const gchar *password, auth_method_t *auth_method) { int ret; gchar *hash; if (gvm_auth_ldap_enabled () && ldap_auth_enabled () && user_exists_method (username, AUTHENTICATION_METHOD_LDAP_CONNECT)) { ldap_auth_info_t info; int allow_plaintext; gchar *authdn, *host, *cacert; *auth_method = AUTHENTICATION_METHOD_LDAP_CONNECT; /* Search the LDAP authentication cache first. */ if (auth_cache_find (username, password, 0) == 0) return 0; manage_get_ldap_info (NULL, &host, &authdn, &allow_plaintext, &cacert); info = ldap_auth_info_new (host, authdn, allow_plaintext); g_free (host); g_free (authdn); ret = ldap_connect_authenticate (username, password, info, cacert); ldap_auth_info_free (info); free (cacert); if (ret == 0) auth_cache_insert (username, password, 0); return ret; } if (gvm_auth_radius_enabled () && radius_auth_enabled () && user_exists_method (username, AUTHENTICATION_METHOD_RADIUS_CONNECT)) { char *key = NULL, *host = NULL; *auth_method = AUTHENTICATION_METHOD_RADIUS_CONNECT; if (auth_cache_find (username, password, 1) == 0) return 0; manage_get_radius_info (NULL, &host, &key); ret = radius_authenticate (host, key, username, password); g_free (host); g_free (key); if (ret == 0) auth_cache_insert (username, password, 1); return ret; } *auth_method = AUTHENTICATION_METHOD_FILE; hash = manage_user_hash (username); ret = manage_authentication_verify(hash, password); switch(ret){ case GMA_HASH_INVALID: ret = 1; break; case GMA_HASH_VALID_BUT_DATED: g_free(hash); hash = manage_authentication_hash(password); sql ("UPDATE users SET password = '%s', modification_time = m_now () WHERE name = '%s';", hash, username); ret = 0; break; case GMA_SUCCESS: ret = 0; break; default: ret = -1; break; } g_free (hash); return ret; } /** * @brief Authenticate credentials. * * @param[in] credentials Credentials. * * @return 0 authentication success, 1 authentication failure, 99 permission * denied, -1 error. */ int authenticate (credentials_t* credentials) { if (credentials->username && credentials->password) { int fail; auth_method_t auth_method; if (authenticate_allow_all) { /* This flag is set when Manager makes a connection to itself, for * scheduled tasks and alerts. Take the stored uuid * to be able to tell apart locally authenticated vs remotely * authenticated users (in order to fetch the correct rules). */ credentials->uuid = g_strdup (get_scheduled_user_uuid ()); if (*credentials->uuid) { if (credentials_setup (credentials)) return 99; manage_session_init (credentials->uuid); return 0; } return -1; } fail = authenticate_any_method (credentials->username, credentials->password, &auth_method); if (fail == 0) { gchar *quoted_name, *quoted_method; /* Authentication succeeded. */ user_ensure_in_db (credentials->username, auth_method_name (auth_method)); quoted_name = sql_quote (credentials->username); quoted_method = sql_quote (auth_method_name (auth_method)); credentials->uuid = sql_string ("SELECT uuid FROM users" " WHERE name = '%s'" " AND method = '%s';", quoted_name, quoted_method); g_free (quoted_name); g_free (quoted_method); if (credentials_setup (credentials)) { free (credentials->uuid); credentials->uuid = NULL; credentials->role = NULL; return 99; } manage_session_init (credentials->uuid); return 0; } return fail; } return 1; } /** * @brief Return number of resources of a certain type for current user. * * @param[in] type Type. * @param[in] get GET params. * * @return The number of resources associated with the current user. */ int resource_count (const char *type, const get_data_t *get) { static const char *filter_columns[] = { "owner", NULL }; static column_t select_columns[] = {{ "owner", NULL }, { NULL, NULL }}; get_data_t count_get; gchar *extra_where; int rc; memset (&count_get, '\0', sizeof (count_get)); count_get.trash = get->trash; if (type_owned (type)) count_get.filter = "rows=-1 first=1 permission=any owner=any"; else count_get.filter = "rows=-1 first=1 permission=any"; if (strcasecmp (type, "config") == 0) { const gchar *usage_type = get_data_get_extra (get, "usage_type"); extra_where = configs_extra_where (usage_type); } else if (strcmp (type, "task") == 0) { const gchar *usage_type = get_data_get_extra (get, "usage_type"); extra_where = tasks_extra_where (get->trash, usage_type); } else if (strcmp (type, "report") == 0) { extra_where = g_strdup (" AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 0"); } else if (strcmp (type, "result") == 0) { extra_where = g_strdup (" AND (severity != " G_STRINGIFY (SEVERITY_ERROR) ")"); } else if (strcmp (type, "vuln") == 0) { extra_where = g_strdup (" AND vuln_results_exist" " (vulns.uuid," " cast (null AS integer)," " cast (null AS integer)," " cast (null AS text))"); } else extra_where = NULL; rc = count (get->subtype ? get->subtype : type, &count_get, type_owned (type) ? select_columns : NULL, type_owned (type) ? select_columns : NULL, type_owned (type) ? filter_columns : NULL, 0, NULL, extra_where, type_owned (type)); g_free (extra_where); return rc; } /** * @brief Return the number of tasks associated with the current user. * * @param[in] get GET params. * * @return The number of tasks associated with the current user. */ unsigned int task_count (const get_data_t *get) { static const char *extra_columns[] = TASK_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TASK_ITERATOR_COLUMNS; static column_t where_columns[] = TASK_ITERATOR_WHERE_COLUMNS; char *filter; int overrides, min_qod; const char *usage_type; gchar *extra_tables, *extra_where; int ret; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; overrides = filter_term_apply_overrides (filter ? filter : get->filter); min_qod = filter_term_min_qod (filter ? filter : get->filter); free (filter); extra_tables = task_iterator_opts_table (overrides, min_qod, 0); usage_type = get_data_get_extra (get, "usage_type"); extra_where = tasks_extra_where (get->trash, usage_type); ret = count2 ("task", get, columns, columns, where_columns, where_columns, extra_columns, 0, extra_tables, extra_where, NULL, TRUE); g_free (extra_tables); g_free (extra_where); return ret; } /** * @brief Return the UUID of a task. * * @param[in] task Task. * @param[out] id Pointer to a newly allocated string. * * @return 0. */ int task_uuid (task_t task, char ** id) { *id = sql_string ("SELECT uuid FROM tasks WHERE id = %llu;", task); return 0; } /** * @brief Return whether a task is in the trashcan. * * @param[in] task Task. * * @return 1 if in trashcan, else 0. */ int task_in_trash (task_t task) { return sql_int ("SELECT hidden = 2" " FROM tasks WHERE id = %llu;", task); } /** * @brief Return whether a task is in the trashcan. * * Assume the UUID is properly formatted. * * @param[in] task_id Task UUID. * * @return 1 if in trashcan, else 0. */ int task_in_trash_id (const gchar *task_id) { return sql_int ("SELECT hidden = 2" " FROM tasks WHERE uuid = '%s';", task_id); } /** * @brief Return the name of the owner of a task. * * @param[in] task Task. * * @return Newly allocated user name. */ char* task_owner_name (task_t task) { return sql_string ("SELECT name FROM users WHERE id =" " (SELECT owner FROM tasks WHERE id = %llu);", task); } /** * @brief Return the name of the owner of a task. * * @param[in] task Task. * * @return Newly allocated user name. */ static char* task_owner_uuid (task_t task) { return sql_string ("SELECT uuid FROM users WHERE id =" " (SELECT owner FROM tasks WHERE id = %llu);", task); } /** * @brief Return the name of a task. * * @param[in] task Task. * * @return Task name. */ char* task_name (task_t task) { return sql_string ("SELECT name FROM tasks WHERE id = %llu;", task); } /** * @brief Return the comment of a task. * * @param[in] task Task. * * @return Comment of task. */ char* task_comment (task_t task) { return sql_string ("SELECT comment FROM tasks WHERE id = %llu;", task); } /** * @brief Return the hosts ordering of a task. * * @param[in] task Task. * * @return Hosts ordering of task. */ char* task_hosts_ordering (task_t task) { return sql_string ("SELECT hosts_ordering FROM tasks WHERE id = %llu;", task); } /** * @brief Return the observers of a task. * * @param[in] task Task. * * @return Observers of task. */ char* task_observers (task_t task) { iterator_t users; GString *observers; observers = g_string_new (""); init_task_user_iterator (&users, task); if (next (&users)) { g_string_append (observers, task_user_iterator_name (&users)); while (next (&users)) g_string_append_printf (observers, " %s", task_user_iterator_name (&users)); } cleanup_iterator (&users); return g_string_free (observers, FALSE); } /** * @brief Return the config of a task. * * @param[in] task Task. * * @return Config of task. */ config_t task_config (task_t task) { config_t config; switch (sql_int64 (&config, "SELECT config FROM tasks WHERE id = %llu;", task)) { case 0: return config; default: /* Programming error. */ case 1: /* Too few rows in result of query. */ case -1: /* Error. */ /* Every task should have a config. */ assert (0); return 0; break; } } /** * @brief Return the UUID of the config of a task. * * @param[in] task Task. * * @return UUID of config of task. */ char* task_config_uuid (task_t task) { if (task_config_in_trash (task)) return sql_string ("SELECT uuid FROM configs_trash WHERE id =" " (SELECT config FROM tasks WHERE id = %llu);", task); return sql_string ("SELECT uuid FROM configs WHERE id =" " (SELECT config FROM tasks WHERE id = %llu);", task); } /** * @brief Return the name of the config of a task. * * @param[in] task Task. * * @return Name of config of task. */ char* task_config_name (task_t task) { if (task_config_in_trash (task)) return sql_string ("SELECT name FROM configs_trash WHERE id =" " (SELECT config FROM tasks WHERE id = %llu);", task); return sql_string ("SELECT name FROM configs WHERE id =" " (SELECT config FROM tasks WHERE id = %llu);", task); } /** * @brief Return whether the config of a task is in the trashcan. * * @param[in] task Task. * * @return 1 if in trashcan, else 0. */ int task_config_in_trash (task_t task) { return sql_int ("SELECT config_location = " G_STRINGIFY (LOCATION_TRASH) " FROM tasks WHERE id = %llu;", task); } /** * @brief Set the config of a task. * * @param[in] task Task. * @param[in] config Config. */ void set_task_config (task_t task, config_t config) { sql ("UPDATE tasks SET config = %llu, modification_time = m_now ()" " WHERE id = %llu;", config, task); } /** * @brief Return the target of a task. * * @param[in] task Task. * * @return Target of task. */ target_t task_target (task_t task) { target_t target = 0; switch (sql_int64 (&target, "SELECT target FROM tasks WHERE id = %llu;", task)) { case 0: return target; break; case 1: /* Too few rows in result of query. */ default: /* Programming error. */ assert (0); case -1: return 0; break; } } /** * @brief Set the target of a task. * * @param[in] task Task. * @param[in] target Target. */ void set_task_target (task_t task, target_t target) { sql ("UPDATE tasks SET target = %llu, modification_time = m_now ()" " WHERE id = %llu;", target, task); } /** * @brief Set the hosts ordering of a task. * * @param[in] task Task. * @param[in] ordering Hosts ordering. */ void set_task_hosts_ordering (task_t task, const char *ordering) { char *quoted_ordering = sql_quote (ordering ?: ""); sql ("UPDATE tasks SET hosts_ordering = '%s', modification_time = m_now ()" " WHERE id = %llu;", quoted_ordering, task); g_free (quoted_ordering); } /** * @brief Return whether the target of a task is in the trashcan. * * @param[in] task Task. * * @return 1 if in trash, else 0. */ int task_target_in_trash (task_t task) { return sql_int ("SELECT target_location = " G_STRINGIFY (LOCATION_TRASH) " FROM tasks WHERE id = %llu;", task); } /** * @brief Return the scanner of a task. * * @param[in] task Task. * * @return scanner of task. */ scanner_t task_scanner (task_t task) { scanner_t scanner = 0; switch (sql_int64 (&scanner, "SELECT scanner FROM tasks WHERE id = %llu;", task)) { case 0: return scanner; break; case 1: /* Too few rows in result of query. */ default: /* Programming error. */ assert (0); case -1: return 0; break; } } /** * @brief Set the scanner of a task. * * @param[in] task Task. * @param[in] scanner Scanner. */ void set_task_scanner (task_t task, scanner_t scanner) { sql ("UPDATE tasks SET scanner = %llu, modification_time = m_now ()" " WHERE id = %llu;", scanner, task); if (scanner_type (scanner) == SCANNER_TYPE_CVE) sql ("UPDATE tasks SET config = 0 WHERE id = %llu;", task); } /** * @brief Return whether the scanner of a task is in the trashcan. * * @param[in] task Task. * * @return 1 if in trash, else 0. */ int task_scanner_in_trash (task_t task) { return sql_int ("SELECT scanner_location = " G_STRINGIFY (LOCATION_TRASH) " FROM tasks WHERE id = %llu;", task); } /** * @brief Set the usage_type of a task. * * @param[in] task Task. * @param[in] usage_type New usage type ("scan" or "audit"). */ void set_task_usage_type (task_t task, const char *usage_type) { const char *actual_usage_type; if (usage_type && strcasecmp (usage_type, "audit") == 0) actual_usage_type = "audit"; else actual_usage_type = "scan"; sql ("UPDATE tasks SET usage_type = '%s', modification_time = m_now ()" " WHERE id = %llu;", actual_usage_type, task); } /** * @brief Return the run state of a task. * * @param[in] task Task. * * @return Task run status. */ task_status_t task_run_status (task_t task) { return (unsigned int) sql_int ("SELECT run_status FROM tasks WHERE id = %llu;", task); } /** * @brief Set a report's scheduled flag. * * Set flag if task was scheduled, else clear flag. * * @param[in] report Report. */ void set_report_scheduled (report_t report) { if (authenticate_allow_all == 1) /* The task was scheduled. */ sql ("UPDATE reports SET flags = 1 WHERE id = %llu;", report); else sql ("UPDATE reports SET flags = 0 WHERE id = %llu;", report); } /** * @brief Get a report's scheduled flag. * * @param[in] report Report. * * @return Scheduled flag. */ static int report_scheduled (report_t report) { return sql_int ("SELECT flags FROM reports WHERE id = %llu;", report); } /** * @brief Set the run state of a task. * * @param[in] task Task. * @param[in] status New run status. */ static void set_task_run_status_internal (task_t task, task_status_t status) { if ((task == current_scanner_task) && global_current_report) { sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;", status, global_current_report); if (setting_auto_cache_rebuild_int ()) report_cache_counts (global_current_report, 0, 0, NULL); } sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;", status, task); } /** * @brief Set the run state of a task. * * Logs and generates event. * * @param[in] task Task. * @param[in] status New run status. */ void set_task_run_status (task_t task, task_status_t status) { char *uuid; char *name; set_task_run_status_internal (task, status); task_uuid (task, &uuid); name = task_name (task); g_log ("event task", G_LOG_LEVEL_MESSAGE, "Status of task %s (%s) has changed to %s", name, uuid, run_status_name (status)); free (uuid); free (name); event (EVENT_TASK_RUN_STATUS_CHANGED, (void*) status, task, (task == current_scanner_task) ? global_current_report : 0); } /** * @brief Return number of results in a task. * * @param[in] task Task. * @param[in] min_qod Minimum QOD. * * @return Result count. */ int task_result_count (task_t task, int min_qod) { return sql_int ("SELECT count (*) FROM results" " WHERE task = %llu" " AND qod > %i" " AND severity > " G_STRINGIFY (SEVERITY_ERROR) ";", task, min_qod); } /** * @brief Return the running report of a task. * * @param[in] task Task. * * @return Current report of task if task is active, else (report_t) 0. */ report_t task_running_report (task_t task) { task_status_t run_status = task_run_status (task); if (run_status == TASK_STATUS_REQUESTED || run_status == TASK_STATUS_RUNNING || run_status == TASK_STATUS_QUEUED) { return (unsigned int) sql_int ("SELECT max(id) FROM reports" " WHERE task = %llu AND end_time IS NULL" " AND (scan_run_status = %u " " OR scan_run_status = %u);", task, TASK_STATUS_RUNNING, TASK_STATUS_QUEUED); } return (report_t) 0; } /** * @brief Return the current report of a task. * * @param[in] iterator Iterator. * * @return Current report of task if task is active, else (report_t) 0. */ report_t task_iterator_current_report (iterator_t *iterator) { task_t task = get_iterator_resource (iterator); task_status_t run_status = task_iterator_run_status (iterator); if (run_status == TASK_STATUS_REQUESTED || run_status == TASK_STATUS_RUNNING || run_status == TASK_STATUS_QUEUED || run_status == TASK_STATUS_DELETE_REQUESTED || run_status == TASK_STATUS_DELETE_ULTIMATE_REQUESTED || run_status == TASK_STATUS_STOP_REQUESTED || run_status == TASK_STATUS_STOPPED || run_status == TASK_STATUS_INTERRUPTED) { return (unsigned int) sql_int ("SELECT max(id) FROM reports" " WHERE task = %llu" " AND (scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u" " OR scan_run_status = %u);", task, TASK_STATUS_REQUESTED, TASK_STATUS_RUNNING, TASK_STATUS_QUEUED, TASK_STATUS_DELETE_REQUESTED, TASK_STATUS_DELETE_ULTIMATE_REQUESTED, TASK_STATUS_STOP_REQUESTED, TASK_STATUS_STOPPED, TASK_STATUS_INTERRUPTED); } return (report_t) 0; } /** * @brief Return the upload progress of a task. * * @param[in] task Task. * * @return Task upload progress, as a percentage, or -1 on error. */ int task_upload_progress (task_t task) { report_t report; report = task_running_report (task); if (report) { int count; get_data_t get; memset (&get, 0, sizeof (get_data_t)); get.filter = g_strdup ("min_qod=0"); count = result_count (&get, report, NULL); get_data_reset (&get); return sql_int ("SELECT" " greatest (least (((%i * 100) / upload_result_count), 100), -1)" " FROM tasks" " WHERE id = %llu;", count, task); } return -1; } /** * @brief Set the start time of a task. * * @param[in] task Task. * @param[in] time New time. Seconds since epoch. */ void set_task_start_time_epoch (task_t task, int time) { sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()" " WHERE id = %llu;", time, task); } /** * @brief Set the start time of a task. * * @param[in] task Task. * @param[in] time New time. UTC ctime format. Freed before return. */ void set_task_start_time_ctime (task_t task, char* time) { sql ("UPDATE tasks SET start_time = %i, modification_time = m_now ()" " WHERE id = %llu;", parse_utc_ctime (time), task); free (time); } /** * @brief Get most recently completed report that precedes a report. * * @param[in] task The task. * @param[out] report Report. * @param[out] previous Report return, 0 if successfully failed to select report. * * @return 0 success, -1 error. */ static int task_report_previous (task_t task, report_t report, report_t *previous) { switch (sql_int64 (previous, "SELECT id FROM reports" " WHERE task = %llu" " AND scan_run_status = %u" " AND date < (SELECT date FROM reports" " WHERE id = %llu)" " ORDER BY date DESC LIMIT 1;", task, TASK_STATUS_DONE, report)) { case 0: break; case 1: /* Too few rows in result of query. */ *previous = 0; return 0; break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Get the report from the most recently completed invocation of task. * * @param[in] task The task. * @param[out] report Report return, 0 if successfully failed to select report. * * @return 0 success, -1 error. */ int task_last_report (task_t task, report_t *report) { switch (sql_int64 (report, "SELECT id FROM reports WHERE task = %llu" " AND scan_run_status = %u" " ORDER BY date DESC LIMIT 1;", task, TASK_STATUS_DONE)) { case 0: break; case 1: /* Too few rows in result of query. */ *report = 0; return 0; break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Get the report from the most recently invocation of task. * * @param[in] task The task. * @param[out] report Report return, 0 if successfully failed to select report. * * @return 0 success, -1 error. */ static int task_last_report_any_status (task_t task, report_t *report) { switch (sql_int64 (report, "SELECT id FROM reports WHERE task = %llu" " ORDER BY date DESC LIMIT 1;", task)) { case 0: break; case 1: /* Too few rows in result of query. */ *report = 0; return 0; break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Get the report from second most recently completed invocation of task. * * @param[in] task The task. * @param[out] report Report return, 0 if successfully failed to select report. * * @return 0 success, -1 error. */ static int task_second_last_report (task_t task, report_t *report) { switch (sql_int64 (report, "SELECT id FROM reports WHERE task = %llu" " AND scan_run_status = %u" " ORDER BY date DESC LIMIT 1 OFFSET 1;", task, TASK_STATUS_DONE)) { case 0: break; case 1: /* Too few rows in result of query. */ *report = 0; return 0; break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Get the report from the most recently stopped invocation of task. * * @param[in] task The task. * @param[out] report Report return, 0 if successfully failed to select report. * * @return 0 success, -1 error. */ int task_last_resumable_report (task_t task, report_t *report) { switch (sql_int64 (report, "SELECT id FROM reports WHERE task = %llu" " AND (scan_run_status = %u" " OR scan_run_status = %u)" " ORDER BY date DESC LIMIT 1;", task, TASK_STATUS_STOPPED, TASK_STATUS_INTERRUPTED)) { case 0: break; case 1: /* Too few rows in result of query. */ *report = 0; return 0; break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Get report ID from second most recently completed invocation of task. * * @param[in] task The task. * * @return The UUID of the report as a newly allocated string. */ gchar* task_second_last_report_id (task_t task) { return sql_string ("SELECT uuid FROM reports WHERE task = %llu" " AND scan_run_status = %u" " ORDER BY date DESC LIMIT 1 OFFSET 1;", task, TASK_STATUS_DONE); } /** * @brief Add an alert to a task. * * @param[in] task Task. * @param[in] alert Alert. */ void add_task_alert (task_t task, alert_t alert) { sql ("INSERT INTO task_alerts (task, alert, alert_location)" " VALUES (%llu, %llu, " G_STRINGIFY (LOCATION_TABLE) ");", task, alert); } /** * @brief Set the alerts on a task, removing any previous alerts. * * @param[in] task Task. * @param[in] alerts Alerts. * @param[out] alert_id_return ID of alert on "failed to find" error. * * @return 0 success, -1 error, 1 failed to find alert. */ static int set_task_alerts (task_t task, array_t *alerts, gchar **alert_id_return) { alert_t alert = 0; guint index; sql_begin_immediate (); sql ("DELETE FROM task_alerts where task = %llu;", task); index = alerts->len; while (index--) { gchar *alert_id; alert_id = (gchar*) g_ptr_array_index (alerts, index); if (strcmp (alert_id, "0") == 0) continue; if (find_alert_with_permission (alert_id, &alert, "get_alerts")) { sql_rollback (); return -1; } if (alert == 0) { sql_rollback (); if (alert_id_return) *alert_id_return = alert_id; return 1; } sql ("INSERT INTO task_alerts (task, alert, alert_location)" " VALUES (%llu, %llu, " G_STRINGIFY (LOCATION_TABLE) ");", task, alert); } sql_commit (); return 0; } /** * @brief Set the alterable state of a task. * * @param[in] task Task. * @param[in] alterable Whether task is alterable. */ void set_task_alterable (task_t task, int alterable) { sql ("UPDATE tasks SET alterable = %i WHERE id = %llu;", alterable, task); } /** * @brief Set observer groups on a task, removing any previous groups. * * @param[in] task Task. * @param[in] groups Groups. * @param[out] group_id_return ID of group on "failed to find" error. * * @return 0 success, -1 error, 1 failed to find group. */ int set_task_groups (task_t task, array_t *groups, gchar **group_id_return) { group_t group = 0; guint index; sql_begin_immediate (); sql ("DELETE FROM permissions" " WHERE resource_type = 'task'" " AND resource = %llu" " AND subject_type = 'group'" " AND name = 'get';", task); index = 0; while (index < groups->len) { gchar *group_id; group_id = (gchar*) g_ptr_array_index (groups, index); if (strcmp (group_id, "0") == 0) { index++; continue; } if (find_group_with_permission (group_id, &group, "modify_group")) { sql_rollback (); return -1; } if (group == 0) { sql_rollback (); if (group_id_return) *group_id_return = group_id; return 1; } sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " 'get_tasks', '', 'task', %llu," " (SELECT uuid FROM tasks WHERE tasks.id = %llu)," " " G_STRINGIFY (LOCATION_TABLE) ", 'group', %llu," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());", current_credentials.uuid, task, task, group); index++; } sql_commit (); return 0; } /** * @brief Set the schedule of a task. * * @param[in] task Task. * @param[in] schedule Schedule. * @param[in] periods Number of schedule periods. * * @return 0 success, -1 error. */ int set_task_schedule (task_t task, schedule_t schedule, int periods) { sql ("UPDATE tasks" " SET schedule = %llu," " schedule_periods = %i," " schedule_next_time = (SELECT next_time_ical (icalendar, timezone)" " FROM schedules" " WHERE id = %llu)," " modification_time = m_now ()" " WHERE id = %llu;", schedule, periods, schedule, task); return 0; } /** * @brief Set the schedule of a task. * * @param[in] task_id Task UUID. * @param[in] schedule Schedule. * @param[in] periods Number of schedule periods. -1 to use existing value. * * @return 0 success, -1 error. */ int set_task_schedule_uuid (const gchar *task_id, schedule_t schedule, int periods) { gchar *quoted_task_id, *schedule_periods; if (periods == -1) schedule_periods = g_strdup (""); else schedule_periods = g_strdup_printf ("schedule_periods = %i,", periods); quoted_task_id = sql_quote (task_id); sql ("UPDATE tasks" " SET schedule = %llu," "%s" " schedule_next_time = (SELECT next_time_ical (icalendar, timezone)" " FROM schedules" " WHERE id = %llu)," " modification_time = m_now ()" " WHERE uuid = '%s';", schedule, schedule_periods, schedule, quoted_task_id); g_free (quoted_task_id); g_free (schedule_periods); return 0; } /** * @brief Set the schedule periods of a task, given a UUID. * * The task modification time stays the same. * * @param[in] task_id Task UUID. * @param[in] periods Schedule periods. * * @return 0 success, -1 error. */ int set_task_schedule_periods (const gchar *task_id, int periods) { gchar *quoted_task_id; quoted_task_id = sql_quote (task_id); sql ("UPDATE tasks" " SET schedule_periods = %i" " WHERE uuid = '%s';", periods, quoted_task_id); g_free (quoted_task_id); return 0; } /** * @brief Set the schedule periods of a task, given an ID. * * The task modification time stays the same. * * @param[in] task Task UUID. * @param[in] periods Schedule periods. * * @return 0 success, -1 error. */ int set_task_schedule_periods_id (task_t task, int periods) { sql ("UPDATE tasks" " SET schedule_periods = %i" " WHERE id = %llu;", periods, task); return 0; } /** * @brief Return the schedule of a task. * * @param[in] task Task. * * @return Schedule. */ schedule_t task_schedule (task_t task) { schedule_t schedule = 0; switch (sql_int64 (&schedule, "SELECT schedule FROM tasks WHERE id = %llu;", task)) { case 0: return schedule; break; case 1: /* Too few rows in result of query. */ default: /* Programming error. */ assert (0); case -1: return 0; break; } } /** * @brief Return the schedule of a task. * * @param[in] task_id ID of task. * * @return Schedule. */ schedule_t task_schedule_uuid (const gchar *task_id) { schedule_t schedule; gchar *quoted_task_id; quoted_task_id = sql_quote (task_id); schedule = 0; switch (sql_int64 (&schedule, "SELECT schedule FROM tasks WHERE uuid = '%s';", quoted_task_id)) { case 0: g_free (quoted_task_id); return schedule; break; case 1: /* Too few rows in result of query. */ default: /* Programming error. */ assert (0); case -1: g_free (quoted_task_id); return 0; break; } } /** * @brief Get whether the task schedule is in the trash. * * @param[in] task Task. * * @return 1 if in trash, else 0. */ int task_schedule_in_trash (task_t task) { return sql_int ("SELECT schedule_location = " G_STRINGIFY (LOCATION_TRASH) " FROM tasks" " WHERE id = %llu;", task); } /** * @brief Get the number of times the period schedule should run on the task. * * @param[in] task Task. * * @return Number of times. */ int task_schedule_periods (task_t task) { return sql_int ("SELECT schedule_periods FROM tasks WHERE id = %llu;", task); } /** * @brief Set the next time a scheduled task will be due. * * @param[in] task_id Task UUID. * * @return Task schedule periods. */ int task_schedule_periods_uuid (const gchar *task_id) { gchar *quoted_task_id; int ret; quoted_task_id = sql_quote (task_id); ret = sql_int ("SELECT schedule_periods FROM tasks WHERE uuid = '%s';", quoted_task_id); g_free (quoted_task_id); return ret; } /** * @brief Get next time a scheduled task will run, following schedule timezone. * * @param[in] task Task. * * @return If the task has a schedule, the next time the task will run (0 if it * has already run), otherwise 0. */ int task_schedule_next_time (task_t task) { int next_time; next_time = sql_int ("SELECT schedule_next_time FROM tasks" " WHERE id = %llu;", task); return next_time; } /** * @brief Get the next time a scheduled task will be due. * * @param[in] task_id Task UUID. * * @return Next scheduled time. */ time_t task_schedule_next_time_uuid (const gchar *task_id) { gchar *quoted_task_id; time_t ret; quoted_task_id = sql_quote (task_id); ret = (time_t) sql_int ("SELECT schedule_next_time FROM tasks" " WHERE uuid = '%s';", quoted_task_id); g_free (quoted_task_id); return ret; } /** * @brief Set the next time a scheduled task will be due. * * @param[in] task Task. * @param[in] time New next time. */ void set_task_schedule_next_time (task_t task, time_t time) { sql ("UPDATE tasks SET schedule_next_time = %i WHERE id = %llu;", time, task); } /** * @brief Set the next time a scheduled task will be due. * * @param[in] task_id Task UUID. * @param[in] time New next time. */ void set_task_schedule_next_time_uuid (const gchar *task_id, time_t time) { gchar *quoted_task_id; quoted_task_id = sql_quote (task_id); sql ("UPDATE tasks SET schedule_next_time = %i WHERE uuid = '%s';", time, quoted_task_id); g_free (quoted_task_id); } /** * @brief Return the severity score of a task, taking overrides into account. * * @param[in] task Task. * @param[in] overrides Whether to apply overrides. * @param[in] min_qod Minimum QoD of results to count. * @param[in] offset Offset of report to get severity from: * 0 = use last report, 1 = use next to last report * * @return Severity score of last report on task as a double if there is one, * else SEVERITY_MISSING. */ static double task_severity_double (task_t task, int overrides, int min_qod, int offset) { report_t report; if (current_credentials.uuid == NULL || task_target (task) == 0 /* Container task. */) return SEVERITY_MISSING; sql_int64 (&report, "SELECT id FROM reports" " WHERE reports.task = %llu" " AND reports.scan_run_status = %u" " ORDER BY reports.date DESC" " LIMIT 1 OFFSET %d", task, TASK_STATUS_DONE, offset); return report_severity (report, overrides, min_qod); } /** * @brief Set the observers of a task. * * @param[in] task Task. * @param[in] observers Observers. * * @return 0 success, -1 error, 1 user name validation failed, 2 failed to find * user. */ int set_task_observers (task_t task, const gchar *observers) { gchar **split, **point; GList *added; // TODO the tricky bit here is if you have to own the task to set observers. assert (current_credentials.username); added = NULL; split = g_strsplit_set (observers, " ,", 0); sql_begin_immediate (); sql ("DELETE FROM permissions" " WHERE resource_type = 'task' AND resource = %llu" " AND subject_type = 'user';", task); point = split; while (*point) { user_t user; gchar *name; name = *point; g_strstrip (name); if (strcmp (name, "") == 0) { point++; continue; } if ((strcmp (name, current_credentials.username) == 0) || g_list_find_custom (added, name, (GCompareFunc) strcmp)) { point++; continue; } added = g_list_prepend (added, name); if (user_exists (name) == 0) { g_list_free (added); g_strfreev (split); sql_rollback (); return 2; } if (find_user_by_name (name, &user)) { g_list_free (added); g_strfreev (split); sql_rollback (); return -1; } if (user == 0) { gchar *uuid; if (validate_username (name)) { g_list_free (added); g_strfreev (split); sql_rollback (); return 1; } uuid = user_uuid_any_method (name); if (uuid == NULL) { g_list_free (added); g_strfreev (split); sql_rollback (); return -1; } if (sql_int ("SELECT count(*) FROM users WHERE uuid = '%s';", uuid) == 0) { gchar *quoted_name; quoted_name = sql_quote (name); sql ("INSERT INTO users" " (uuid, name, creation_time, modification_time)" " VALUES" " ('%s', '%s', m_now (), m_now ());", uuid, quoted_name); g_free (quoted_name); user = sql_last_insert_id (); } else { /* user_find should have found it. */ assert (0); g_free (uuid); g_list_free (added); g_strfreev (split); sql_rollback (); return -1; } g_free (uuid); } sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " 'get_tasks', '', 'task', %llu," " (SELECT uuid FROM tasks WHERE tasks.id = %llu)," " " G_STRINGIFY (LOCATION_TABLE) ", 'user', %llu," " " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());", current_credentials.uuid, task, task, user); point++; } g_list_free (added); g_strfreev (split); sql_commit (); return 0; } /** * @brief Clear once-off schedules from tasks where the duration has passed. * * @param[in] task Task. 0 for all. */ void clear_duration_schedules (task_t task) { gchar *task_element; const gchar *duration_expired_element; if (task) task_element = g_strdup_printf (" AND id = %llu", task); else task_element = g_strdup (""); duration_expired_element = " AND (SELECT first_time + duration FROM schedules" " WHERE schedules.id = schedule)" " < m_now ()"; sql ("UPDATE tasks" " SET schedule = 0," " schedule_next_time = 0," " modification_time = m_now ()" " WHERE schedule > 0" "%s" " AND NOT (SELECT icalendar LIKE '%%\nBEGIN:VEVENT%%\nRRULE%%'" " OR icalendar LIKE '%%\nBEGIN:VEVENT%%\nRDATE%%'" " FROM schedules WHERE schedules.id = schedule)" " AND (SELECT duration FROM schedules WHERE schedules.id = schedule) > 0" "%s" " AND run_status != %i" " AND run_status != %i;", task_element, task ? "" : duration_expired_element, TASK_STATUS_RUNNING, TASK_STATUS_REQUESTED); g_free (task_element); } /** * @brief Update tasks with limited run schedules which have durations. * * If a task is given, assume that the task has finished. Otherwise only * update the task if more time than the duration has passed the start time. * * @param[in] task Task. 0 for all. */ void update_duration_schedule_periods (task_t task) { gchar *task_element; const gchar *duration_expired_element; if (task) task_element = g_strdup_printf (" AND id = %llu", task); else task_element = g_strdup (""); duration_expired_element = /* The task has started, so assume that the start time was the last * most recent start of the period. */ " AND (SELECT next_time_ical (icalendar, timezone, -1)" " + duration" " FROM schedules" " WHERE schedules.id = schedule)" " < m_now ()"; sql ("UPDATE tasks" " SET schedule = 0," " schedule_next_time = 0," " modification_time = m_now ()" " WHERE schedule > 0" "%s" " AND schedule_periods = 1" " AND (SELECT icalendar LIKE '%%\nBEGIN:VEVENT%%\nRRULE%%'" " OR icalendar LIKE '%%\nBEGIN:VEVENT%%\nRDATE%%'" " FROM schedules WHERE schedules.id = schedule)" " AND (SELECT duration FROM schedules WHERE schedules.id = schedule) > 0" " AND schedule_next_time = 0" /* Set as flag when starting task. */ "%s" " AND run_status != %i" " AND run_status != %i;", task_element, task ? "" : duration_expired_element, TASK_STATUS_RUNNING, TASK_STATUS_REQUESTED); g_free (task_element); } /** * @brief Auto delete reports. */ void auto_delete_reports () { iterator_t tasks; g_debug ("%s", __func__); sql_begin_immediate (); /* As in delete_report, this prevents other processes from getting the * report ID. */ if (sql_int ("SELECT try_exclusive_lock('reports');") == 0) { sql_rollback (); return; } init_iterator (&tasks, "SELECT id, name," " (SELECT value FROM task_preferences" " WHERE name = 'auto_delete_data'" " AND task = tasks.id)" " FROM tasks" " WHERE owner is NOT NULL" " AND hidden = 0" " AND EXISTS (SELECT * FROM task_preferences" " WHERE task = tasks.id" " AND name = 'auto_delete'" " AND value = 'keep');"); while (next (&tasks)) { task_t task; iterator_t reports; const char *keep_string; int keep; task = iterator_int64 (&tasks, 0); keep_string = iterator_string (&tasks, 2); if (keep_string == NULL) continue; keep = atoi (keep_string); if (keep < AUTO_DELETE_KEEP_MIN || keep > AUTO_DELETE_KEEP_MAX) continue; g_debug ("%s: %s (%i)", __func__, iterator_string (&tasks, 1), keep); init_iterator (&reports, "SELECT id FROM reports" " WHERE task = %llu" " AND start_time IS NOT NULL" " AND start_time > 0" " ORDER BY start_time DESC LIMIT %s OFFSET %i;", task, sql_select_limit (-1), keep); while (next (&reports)) { int ret; report_t report; report = iterator_int64 (&reports, 0); assert (report); g_debug ("%s: delete %llu", __func__, report); ret = delete_report_internal (report); if (ret == 2) { /* Report is in use. */ g_debug ("%s: %llu is in use", __func__, report); continue; } if (ret) { g_warning ("%s: failed to delete %llu (%i)", __func__, report, ret); sql_rollback (); } } cleanup_iterator (&reports); } cleanup_iterator (&tasks); sql_commit (); } /** * @brief Get definitions file from a task's config. * * @param[in] task Task. * * @return Definitions file. */ static char * task_definitions_file (task_t task) { assert (task); return sql_string ("SELECT value FROM config_preferences" " WHERE name = 'definitions_file' AND config = %llu;", task_config (task)); } /** * @brief Set a task's schedule so that it runs again next scheduling round. * * @param task_id UUID of task. */ void reschedule_task (const gchar *task_id) { task_t task; task = 0; switch (sql_int64 (&task, "SELECT id FROM tasks WHERE uuid = '%s'" " AND hidden != 2;", task_id)) { case 0: g_warning ("%s: rescheduling task '%s'", __func__, task_id); set_task_schedule_next_time (task, time (NULL) - 1); break; case 1: /* Too few rows in result of query. */ break; default: /* Programming error. */ assert (0); case -1: break; } } /* Results. */ /** * @brief Find a result for a set of permissions, given a UUID. * * @param[in] uuid UUID of result. * @param[out] result Result return, 0 if successfully failed to find * result. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find result), TRUE on error. */ gboolean find_result_with_permission (const char* uuid, result_t* result, const char *permission) { gchar *quoted_uuid = sql_quote (uuid); if (acl_user_has_access_uuid ("result", quoted_uuid, permission, 0) == 0) { g_free (quoted_uuid); *result = 0; return FALSE; } switch (sql_int64 (result, "SELECT id FROM results WHERE uuid = '%s';", quoted_uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ *result = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return TRUE; break; } g_free (quoted_uuid); return FALSE; } /** * @brief Ensure an NVT occurs in the result_nvts table. * * @param[in] nvt NVT OID. */ static void result_nvt_notice (const gchar *nvt) { if (nvt == NULL) return; sql ("INSERT INTO result_nvts (nvt) VALUES ('%s') ON CONFLICT DO NOTHING;", nvt); } /** * @brief Make an OSP result. * * @param[in] task The task associated with the result. * @param[in] host Target host of result. * @param[in] hostname Hostname of the result. * @param[in] nvt The uuid of oval definition that produced the * result, a title for the result otherwise. * @param[in] type Type of result. "Alarm", etc. * @param[in] description Description of the result. * @param[in] port Result port. * @param[in] severity Result severity. * @param[in] qod Quality of detection. * @param[in] path Result path, e.g. file location of a product. * * @return A result descriptor for the new result, 0 if error. */ result_t make_osp_result (task_t task, const char *host, const char *hostname, const char *nvt, const char *type, const char *description, const char *port, const char *severity, int qod, const char *path) { char *nvt_revision = NULL, *quoted_desc, *quoted_nvt, *result_severity; char *quoted_port, *quoted_hostname, *quoted_path; assert (task); assert (type); quoted_desc = sql_quote (description ?: ""); quoted_nvt = sql_quote (nvt ?: ""); quoted_port = sql_quote (port ?: ""); quoted_hostname = sql_quote (hostname ? hostname : ""); quoted_path = sql_quote (path ? path : ""); if (nvt) { if (g_str_has_prefix (nvt, "1.3.6.1.4.1.25623.")) nvt_revision = sql_string ("SELECT iso_time (modification_time)" " FROM nvts WHERE oid='%s'", quoted_nvt); else if (g_str_has_prefix (nvt, "oval:")) nvt_revision = ovaldef_version (nvt); else if (g_str_has_prefix (nvt, "CVE-")) nvt_revision = sql_string ("SELECT iso_time (modification_time)" " FROM scap.cves WHERE uuid='%s'", quoted_nvt); } if (!severity || !strcmp (severity, "")) { if (!strcmp (type, severity_to_type (SEVERITY_ERROR))) result_severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR)); else { if (nvt && g_str_has_prefix (nvt, "CVE-")) { result_severity = cve_cvss_base (nvt); if (result_severity == NULL || strcmp (result_severity, "") == 0) { g_free (result_severity); result_severity = g_strdup_printf ("%0.1f", setting_default_severity_dbl ()); g_debug ("%s: OSP CVE result without severity for '%s'", __func__, nvt); } } else { /* result_severity = g_strdup_printf ("%0.1f", setting_default_severity_dbl ()); */ g_warning ("%s: Non-CVE OSP result without severity for test %s", __func__, nvt ? nvt : "(unknown)"); return 0; } } } else result_severity = sql_quote (severity); result_nvt_notice (quoted_nvt); sql ("INSERT into results" " (owner, date, task, host, hostname, port, nvt," " nvt_version, severity, type, qod, qod_type, description," " path, uuid, result_nvt)" " VALUES (NULL, m_now(), %llu, '%s', '%s', '%s', '%s'," " '%s', '%s', '%s', %d, '', '%s'," " '%s', make_uuid ()," " (SELECT id FROM result_nvts WHERE nvt = '%s'));", task, host ?: "", quoted_hostname, quoted_port, quoted_nvt, nvt_revision ?: "", result_severity ?: "0", type, qod, quoted_desc, quoted_path, quoted_nvt); g_free (result_severity); g_free (nvt_revision); g_free (quoted_desc); g_free (quoted_nvt); g_free (quoted_port); g_free (quoted_hostname); g_free (quoted_path); return sql_last_insert_id (); } /** * @brief Get QoD percentage for a qod_type string. * * @param[in] qod_type The QoD type string. * * @return A QoD percentage value, QOD_DEFAULT if string is NULL or unknown. */ int qod_from_type (const char *qod_type) { if (qod_type == NULL) return QOD_DEFAULT; else if (strcmp (qod_type, "exploit") == 0) return 100; else if (strcmp (qod_type, "remote_vul") == 0) return 99; else if (strcmp (qod_type, "remote_app") == 0) return 98; else if (strcmp (qod_type, "package") == 0) return 97; else if (strcmp (qod_type, "registry") == 0) return 97; else if (strcmp (qod_type, "remote_active") == 0) return 95; else if (strcmp (qod_type, "remote_banner") == 0) return 80; else if (strcmp (qod_type, "executable_version") == 0) return 80; else if (strcmp (qod_type, "remote_analysis") == 0) return 70; else if (strcmp (qod_type, "remote_probe") == 0) return 50; else if (strcmp (qod_type, "remote_banner_unreliable") == 0) return 30; else if (strcmp (qod_type, "executable_version_unreliable") == 0) return 30; else if (strcmp (qod_type, "general_note") == 0) return 1; else return QOD_DEFAULT; } /** * @brief Identify a host, given an identifier. * * Find a host which has an identifier of the same name and value, and * which has no identifiers of the same name and a different value. * * @param[in] host_name Host name. * @param[in] identifier_name Host identifier name. * @param[in] identifier_value Value of host identifier. * @param[in] source_type Source of identification: result. * @param[in] source Source identifier. * * @return Host if exists, else 0. */ static host_t host_identify (const char *host_name, const char *identifier_name, const char *identifier_value, const char *source_type, const char *source) { host_t host = 0; gchar *quoted_host_name, *quoted_identifier_name, *quoted_identifier_value; quoted_host_name = sql_quote (host_name); quoted_identifier_name = sql_quote (identifier_name); quoted_identifier_value = sql_quote (identifier_value); switch (sql_int64 (&host, "SELECT hosts.id FROM hosts, host_identifiers" " WHERE hosts.name = '%s'" " AND hosts.owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND host = hosts.id" " AND host_identifiers.owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND host_identifiers.name = '%s'" " AND value = '%s';", quoted_host_name, current_credentials.uuid, current_credentials.uuid, quoted_identifier_name, quoted_identifier_value)) { case 0: break; case 1: /* Too few rows in result of query. */ host = 0; break; default: /* Programming error. */ assert (0); case -1: host = 0; break; } if (host == 0) switch (sql_int64 (&host, "SELECT id FROM hosts" " WHERE name = '%s'" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND NOT EXISTS (SELECT * FROM host_identifiers" " WHERE host = hosts.id" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND name = '%s');", quoted_host_name, current_credentials.uuid, current_credentials.uuid, quoted_identifier_name)) { case 0: break; case 1: /* Too few rows in result of query. */ host = 0; break; default: /* Programming error. */ assert (0); case -1: host = 0; break; } g_free (quoted_host_name); g_free (quoted_identifier_name); g_free (quoted_identifier_value); return host; } /** * @brief Notice a host. * * When a host is detected during a scan, this makes the decision about which * asset host is used for the host, as described in \ref asset_rules. This * decision is revised at the end of the scan by \ref hosts_set_identifiers if * there are any identifiers for the host. * * @param[in] host_name Name of host. * @param[in] identifier_type Type of host identifier. * @param[in] identifier_value Value of host identifier. * @param[in] source_type Type of source identifier * @param[in] source_id Source identifier. * @param[in] check_add_to_assets Whether to check the 'Add to Assets' * task preference. * @param[in] check_for_existing_identifier Whether to check for an existing * identifier like this one. Used * for slaves, which call this * repeatedly. * * @return Host if existed, else 0. */ host_t host_notice (const char *host_name, const char *identifier_type, const char *identifier_value, const char *source_type, const char *source_id, int check_add_to_assets, int check_for_existing_identifier) { host_t host; gchar *quoted_identifier_value, *quoted_identifier_type, *quoted_source_type; gchar *quoted_source_id; /* Only add to assets if "Add to Assets" is set on the task. */ if (check_add_to_assets && g_str_has_prefix (source_type, "Report") && sql_int ("SELECT value = 'no' FROM task_preferences" " WHERE task = (SELECT task FROM reports WHERE uuid = '%s')" " AND name = 'in_assets';", source_id)) return 0; host = host_identify (host_name, identifier_type, identifier_value, source_type, source_id); if (host == 0) { gchar *quoted_host_name; quoted_host_name = sql_quote (host_name); sql ("INSERT into hosts" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', ''," " m_now (), m_now ());", current_credentials.uuid, quoted_host_name); g_free (quoted_host_name); host = sql_last_insert_id (); } quoted_identifier_value = sql_quote (identifier_value); quoted_source_id = sql_quote (source_id); quoted_source_type = sql_quote (source_type); quoted_identifier_type = sql_quote (identifier_type); if (check_for_existing_identifier && sql_int ("SELECT EXISTS (SELECT * FROM host_identifiers" " WHERE host = %llu" " AND owner = (SELECT id FROM users WHERE uuid = '%s')" " AND name = '%s'" " AND value = '%s'" " AND source_type = '%s'" " AND source_id = '%s');", host, current_credentials.uuid, quoted_identifier_type, quoted_identifier_value, quoted_source_type, quoted_source_id)) return 0; sql ("INSERT into host_identifiers" " (uuid, host, owner, name, comment, value, source_type, source_id," " source_data, creation_time, modification_time)" " VALUES" " (make_uuid (), %llu, (SELECT id FROM users WHERE uuid = '%s'), '%s'," " '', '%s', '%s', '%s', '', m_now (), m_now ());", host, current_credentials.uuid, quoted_identifier_type, quoted_identifier_value, quoted_source_type, quoted_source_id); sql ("UPDATE hosts SET modification_time = (SELECT modification_time" " FROM host_identifiers" " WHERE id = %llu)" " WHERE id = %llu;", sql_last_insert_id (), host); g_free (quoted_identifier_type); g_free (quoted_identifier_value); g_free (quoted_source_id); g_free (quoted_source_type); return host; } /** * @brief Get a severity string from an nvt and result type. * * @param[in] nvt_id NVT oid. * @param[in] type Result type. * * @return A severity string, NULL if unknown type or no nvt id for Alarm type. */ static char * nvt_severity (const char *nvt_id, const char *type) { char *severity = NULL; if (strcasecmp (type, "Alarm") == 0 && nvt_id) severity = sql_string ("SELECT coalesce(cvss_base, '0.0')" " FROM nvts WHERE uuid = '%s';", nvt_id); else if (strcasecmp (type, "Alarm") == 0) g_warning ("%s result type requires an NVT", type); else if (strcasecmp (type, "Log Message") == 0) severity = g_strdup (G_STRINGIFY (SEVERITY_LOG)); else if (strcasecmp (type, "Error Message") == 0) severity = g_strdup (G_STRINGIFY (SEVERITY_ERROR)); else g_warning ("Invalid result nvt type %s", type); return severity; } /** * @brief Make a result. * * @param[in] task The task associated with the result. * @param[in] host Host IP address. * @param[in] hostname Hostname. * @param[in] port The port the result refers to. * @param[in] nvt The OID of the NVT that produced the result. * @param[in] type Type of result: "Alarm", "Error Message" or * "Log Message". * @param[in] description Description of the result. * @param[in] path Result path, e.g. file location of a product. * * @return A result descriptor for the new result, 0 if error. */ result_t make_result (task_t task, const char* host, const char *hostname, const char* port, const char* nvt, const char* type, const char* description, const char* path) { result_t result; gchar *nvt_revision, *severity, *qod, *qod_type; gchar *quoted_nvt, *quoted_hostname, *quoted_descr, *quoted_path; nvt_t nvt_id = 0; if (nvt && strcmp (nvt, "") && (find_nvt (nvt, &nvt_id) || nvt_id <= 0)) { g_warning ("NVT '%s' not found. Result not created", nvt); return 0; } severity = nvt_severity (nvt, type); if (!severity) { g_warning ("NVT '%s' has no severity. Result not created.", nvt); return 0; } quoted_nvt = NULL; if (nvt && strcmp (nvt, "")) { quoted_nvt = sql_quote (nvt); qod = g_strdup_printf ("(SELECT qod FROM nvts WHERE id = %llu)", nvt_id); qod_type = g_strdup_printf ("(SELECT qod_type FROM nvts WHERE id = %llu)", nvt_id); if (g_str_has_prefix (nvt, "1.3.6.1.4.1.25623.")) nvt_revision = sql_string ("SELECT iso_time (modification_time)" " FROM nvts WHERE oid='%s'", quoted_nvt); else if (g_str_has_prefix (nvt, "oval:")) nvt_revision = ovaldef_version (nvt); else if (g_str_has_prefix (nvt, "CVE-")) nvt_revision = sql_string ("SELECT iso_time (modification_time)" " FROM scap.cves WHERE uuid='%s'", quoted_nvt); else nvt_revision = strdup (""); } else { qod = g_strdup (G_STRINGIFY (QOD_DEFAULT)); qod_type = g_strdup ("''"); nvt_revision = g_strdup (""); } if (!strcmp (severity, "")) { g_free (severity); severity = g_strdup ("0.0"); } quoted_hostname = sql_quote (hostname ? hostname : ""); quoted_descr = sql_quote (description ?: ""); quoted_path = sql_quote (path ? path : ""); result_nvt_notice (nvt); sql ("INSERT into results" " (owner, date, task, host, hostname, port," " nvt, nvt_version, severity, type," " description, uuid, qod, qod_type, path, result_nvt)" " VALUES" " (NULL, m_now (), %llu, '%s', '%s', '%s'," " '%s', '%s', '%s', '%s'," " '%s', make_uuid (), %s, %s, '%s'," " (SELECT id FROM result_nvts WHERE nvt = '%s'));", task, host ?: "", quoted_hostname, port ?: "", quoted_nvt ?: "", nvt_revision, severity, type, quoted_descr, qod, qod_type, quoted_path, quoted_nvt ? quoted_nvt : ""); g_free (quoted_nvt); g_free (quoted_hostname); g_free (quoted_descr); g_free (qod); g_free (qod_type); g_free (nvt_revision); g_free (severity); g_free (quoted_path); result = sql_last_insert_id (); return result; } /** * @brief Make a CVE result. * * @param[in] task The task associated with the result. * @param[in] host Host. * @param[in] nvt The OID of the NVT that produced the result. * @param[in] cvss CVSS base. * @param[in] description Description of the result. * * @return A result descriptor for the new result, 0 if error. */ result_t make_cve_result (task_t task, const char* host, const char *nvt, double cvss, const char* description) { gchar *quoted_descr; quoted_descr = sql_quote (description ?: ""); result_nvt_notice (nvt); sql ("INSERT into results" " (owner, date, task, host, port, nvt, nvt_version, severity, type," " description, uuid, qod, qod_type, path, result_nvt)" " VALUES" " (NULL, m_now (), %llu, '%s', '', '%s'," " (SELECT iso_time (modification_time)" " FROM scap.cves WHERE uuid='%s')," " '%1.1f', '%s', '%s', make_uuid (), %i, '', ''," " (SELECT id FROM result_nvts WHERE nvt = '%s'));", task, host ?: "", nvt, nvt, cvss, severity_to_type (cvss), quoted_descr, QOD_DEFAULT, nvt); g_free (quoted_descr); return sql_last_insert_id (); } /** * @brief Return the UUID of a result. * * @param[in] result Result. * @param[out] id Pointer to a newly allocated string. * * @return 0. */ int result_uuid (result_t result, char ** id) { *id = sql_string ("SELECT uuid FROM results WHERE id = %llu;", result); return 0; } /** * @brief Get product detection results corresponding to a given vulnerability * detection result. * * @param[in] result Vulnerability detection result. * @param[in] report Report of result. * @param[in] host Host of result. * @param[in] port Port of result. * @param[in] path Path of result. * @param[out] oid Detection script OID. * @param[out] ref Detection result UUID. * @param[out] product Product name. * @param[out] location Product location. * @param[out] name Detection script name. * * @return -1 on error, 0 on success. */ int result_detection_reference (result_t result, report_t report, const char *host, const char *port, const char *path, char **oid, char **ref, char **product, char **location, char **name) { gchar *quoted_location, *quoted_host; if ((ref == NULL) || (product == NULL) || (location == NULL) || (name == NULL)) return -1; if ((report == 0) || (host == NULL) || (oid == NULL)) return -1; quoted_location = NULL; *oid = *ref = *product = *location = *name = NULL; quoted_host = sql_quote (host); if (path && strcmp (path, "")) { *location = strdup (path); } else if (port && strcmp (port, "") && !(g_str_has_prefix (port, "general/"))) { *location = strdup (port); } else { *location = sql_string ("SELECT value" " FROM report_host_details" " WHERE report_host = (SELECT id" " FROM report_hosts" " WHERE report = %llu" " AND host = '%s')" " AND name = 'detected_at'" " AND source_name = (SELECT nvt" " FROM results" " WHERE id = %llu);", report, quoted_host, result); } if (*location == NULL) { goto detect_cleanup; } quoted_location = sql_quote (*location); *oid = sql_string ("SELECT value" " FROM report_host_details" " WHERE report_host = (SELECT id" " FROM report_hosts" " WHERE report = %llu" " AND host = '%s')" " AND name = 'detected_by@%s'" " AND source_name = (SELECT nvt" " FROM results" " WHERE id = %llu)" " LIMIT 1", report, quoted_host, quoted_location, result); if (*oid == NULL) { *oid = sql_string ("SELECT value" " FROM report_host_details" " WHERE report_host = (SELECT id" " FROM report_hosts" " WHERE report = %llu" " AND host = '%s')" " AND name = 'detected_by'" " AND source_name = (SELECT nvt" " FROM results" " WHERE id = %llu)" " LIMIT 1", report, quoted_host, result); } if (*oid == NULL) { goto detect_cleanup; } *product = sql_string ("SELECT name" " FROM report_host_details" " WHERE report_host = (SELECT id" " FROM report_hosts" " WHERE report = %llu" " AND host = '%s')" " AND source_name = '%s'" " AND name != 'detected_at'" " AND value = '%s';", report, quoted_host, *oid, quoted_location); if (*product == NULL) goto detect_cleanup; if (g_str_has_prefix (*oid, "CVE-")) *name = g_strdup (*oid); else *name = sql_string ("SELECT name FROM nvts WHERE oid = '%s';", *oid); if (*name == NULL) goto detect_cleanup; /* Get the result produced by the detection NVT when it detected the * product. The result port or description must include the product * location in order for this to work. */ *ref = sql_string ("SELECT uuid" " FROM results" " WHERE report = %llu" " AND host = '%s'" " AND nvt = '%s'" " AND (description LIKE '%%%s%%'" " OR port LIKE '%%%s%%');", report, quoted_host, *oid, quoted_location, quoted_location); if (*ref == NULL) goto detect_cleanup; g_free (quoted_host); g_free (quoted_location); return 0; detect_cleanup: g_free (quoted_host); g_free (quoted_location); return -1; } /* Prognostics. */ /** * @brief Initialize an iterator of locations of an App for a report's host. * * @param[in] iterator Iterator. * @param[in] report_host Report host. * @param[in] app CPE. */ void init_app_locations_iterator (iterator_t *iterator, report_host_t report_host, const gchar *app) { gchar *quoted_app; assert (app); quoted_app = sql_quote (app); init_iterator (iterator, "SELECT string_agg(DISTINCT value, ', ')" " FROM report_host_details" " WHERE report_host = %llu" " AND name = '%s'" " AND source_type = 'nvt'" " AND source_name" " IN (SELECT source_name FROM report_host_details" " WHERE report_host = %llu" " AND source_type = 'nvt'" " AND name = 'App'" " AND value = '%s');", report_host, quoted_app, report_host, quoted_app); g_free (quoted_app); } /** * @brief Get a location from an app locations iterator. * * @param[in] iterator Iterator. * * @return The location. */ const char * app_locations_iterator_location (iterator_t *iterator) { return iterator_string (iterator, 0); } /** * @brief Initialise a report host prognosis iterator. * * @param[in] iterator Iterator. * @param[in] report_host Report host whose prognosis the iterator loops over. * All report_hosts if NULL. */ void init_host_prognosis_iterator (iterator_t* iterator, report_host_t report_host) { init_iterator (iterator, "SELECT cves.name AS vulnerability," " max(cves.severity) AS severity," " max(cves.description) AS description," " cpes.name AS location," " (SELECT host FROM report_hosts" " WHERE id = %llu) AS host" " FROM scap.cves, scap.cpes, scap.affected_products," " report_host_details" " WHERE report_host_details.report_host = %llu" " AND cpes.name = report_host_details.value" " AND report_host_details.name = 'App'" " AND cpes.id=affected_products.cpe" " AND cves.id=affected_products.cve" " GROUP BY cves.id, vulnerability, location, host" " ORDER BY cves.id ASC" " LIMIT %s OFFSET 0;", report_host, report_host, sql_select_limit (-1)); } DEF_ACCESS (prognosis_iterator_cve, 0); /** * @brief Get the CVSS from a result iterator as a double. * * @param[in] iterator Iterator. * * @return CVSS. */ double prognosis_iterator_cvss_double (iterator_t* iterator) { if (iterator->done) return 0; return iterator_double (iterator, 1); } DEF_ACCESS (prognosis_iterator_description, 2); DEF_ACCESS (prognosis_iterator_cpe, 3); /* Reports. */ /** * @brief Whether to ignore the Max Rows Per Page settings. */ int ignore_max_rows_per_page = 0; /** * @brief Create a new GHashTable for containing resource rowids. * * @return The newly allocated GHashTable */ static GHashTable * new_resources_hashtable () { return g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, NULL); } /** * @brief Add reports affected by an override to an existing GHashtable. * This is used to add more reports to the hashtable from reports_for_override. * * @param[in] reports_table The GHashtable to contain the report rowids. * @param[in] override The override that selected reports must be affected by. */ static void reports_add_for_override (GHashTable *reports_table, override_t override) { result_t result; task_t task; gchar *nvt_id; iterator_t reports; if (override == 0) return; sql_int64 (&result, "SELECT result FROM overrides WHERE id = %llu", override); sql_int64 (&task, "SELECT task FROM overrides WHERE id = %llu", override); nvt_id = sql_string ("SELECT nvt FROM overrides WHERE id = %llu", override); if (result) { report_t *report = g_malloc0 (sizeof (report_t)); sql_int64 (report, "SELECT report FROM results" " WHERE id = %llu AND nvt = '%s'", result, nvt_id); if (*report) g_hash_table_add (reports_table, report); else g_free (report); return; } else if (task) { init_iterator (&reports, "SELECT DISTINCT report FROM results" " WHERE task = %llu AND nvt = '%s'", task, nvt_id); } else { init_iterator (&reports, "SELECT DISTINCT report FROM results" " WHERE nvt = '%s'", nvt_id); } while (next (&reports)) { report_t *report = g_malloc0 (sizeof (report_t)); *report = iterator_int64 (&reports, 0); if (g_hash_table_contains (reports_table, report) == 0) g_hash_table_add (reports_table, report); else g_free (report); } cleanup_iterator (&reports); } /** * @brief Get reports affected by an override in a GHashTable. * * @param[in] override The override that selected reports must be affected by. * * @return A GHashtable containing the affected report rowids. */ static GHashTable * reports_for_override (override_t override) { GHashTable *reports_table; reports_table = new_resources_hashtable (); reports_add_for_override (reports_table, override); return reports_table; } /** * @brief Add all reports to an existing GHashtable. * * @param[in] reports_table The GHashtable to contain the report rowids. */ static void reports_add_all (GHashTable *reports_table) { iterator_t reports; init_iterator (&reports, "SELECT id FROM reports"); while (next (&reports)) { report_t *report = g_malloc0 (sizeof (report_t)); *report = iterator_int64 (&reports, 0); if (g_hash_table_contains (reports_table, report) == 0) g_hash_table_add (reports_table, report); else g_free (report); } cleanup_iterator (&reports); } /** * @brief Get all reports in a GHashTable. * * @return A GHashtable containing the report rowids. */ static GHashTable * reports_hashtable () { GHashTable *reports_table; reports_table = new_resources_hashtable (); reports_add_all (reports_table); return reports_table; } /** * @brief Clear the report count cache for all reports of a user. * * @param[in] uuid UUID of user. */ static void reports_clear_count_cache (const gchar *uuid) { gchar *quoted_uuid; quoted_uuid = sql_quote (uuid); sql ("DELETE FROM report_counts" " WHERE report_counts.user = (SELECT id FROM users" " WHERE uuid = '%s');", quoted_uuid); g_free (quoted_uuid); } /** * @brief Clear all report counts for all dynamic severity users. */ void reports_clear_count_cache_dynamic () { sql ("DELETE FROM report_counts" " WHERE report_counts.user IN (SELECT owner FROM settings" " WHERE name = 'Dynamic Severity'" " AND value = '1');"); } /** * @brief Rebuild the report count cache for all reports and users. * * @param[in] clear Whether to clear the cache before rebuilding. * @param[out] changes_out The number of processed user/report combinations. */ static void reports_build_count_cache (int clear, int* changes_out) { int changes; iterator_t reports; changes = 0; /* Clear cache of trashcan reports, we won't count them. */ sql ("DELETE FROM report_counts" " WHERE (SELECT hidden = 2 FROM tasks" " WHERE tasks.id = (SELECT task FROM reports" " WHERE reports.id = report_counts.report));"); init_iterator (&reports, "SELECT id FROM reports" " WHERE (SELECT hidden = 0 FROM tasks" " WHERE tasks.id = task);"); while (next (&reports)) { report_t report = iterator_int64 (&reports, 0); report_cache_counts (report, clear, clear, NULL); changes ++; } cleanup_iterator (&reports); if (changes_out) *changes_out = changes; } /** * @brief Initializes an iterator for updating the report cache * * @param[in] iterator Iterator. * @param[in] report Report to select. * @param[in] min_qod_limit Limit for min_qod. * @param[in] add_defaults Whether to add default values. * @param[in] users_where Optional SQL clause to limit users. */ void init_report_counts_build_iterator (iterator_t *iterator, report_t report, int min_qod_limit, int add_defaults, const char *users_where) { gchar *report_id, *users_string; report_id = sql_string ("SELECT uuid FROM reports WHERE id = %llu;", report); users_string = acl_users_with_access_sql ("report", report_id, users_where); if (users_string == NULL) { init_iterator (iterator, "SELECT NULL WHERE NOT t();"); g_free (report_id); return; } if (add_defaults && MIN_QOD_DEFAULT <= min_qod_limit) { init_iterator (iterator, "SELECT * FROM" " (WITH users_with_access (\"user\") AS %s" " SELECT DISTINCT min_qod, override, \"user\"" " FROM report_counts" " WHERE report = %llu" " AND \"user\" IN (SELECT \"user\"" " FROM users_with_access)" " AND min_qod <= %d" " UNION SELECT 0 as min_qod, 0, \"user\"" " FROM users_with_access" " UNION SELECT 0 as min_qod, 1, \"user\"" " FROM users_with_access" " UNION SELECT %d as min_qod, 0, \"user\"" " FROM users_with_access" " UNION SELECT %d as min_qod, 1, \"user\"" " FROM users_with_access) AS inner_query" " ORDER BY \"user\"", users_string, report, min_qod_limit, MIN_QOD_DEFAULT, MIN_QOD_DEFAULT); } else if (add_defaults) { init_iterator (iterator, "SELECT * FROM" " (WITH users_with_access (\"user\") AS %s" " SELECT DISTINCT min_qod, override, \"user\"" " FROM report_counts" " WHERE report = %llu" " AND min_qod <= %d" " AND \"user\" IN (SELECT \"user\"" " FROM users_with_access)" " UNION SELECT 0 as min_qod, 0, \"user\"" " FROM users_with_access" " UNION SELECT 0 as min_qod, 1, \"user\"" " FROM users_with_access) AS inner_query" " ORDER BY \"user\"", users_string, report, min_qod_limit); } else { init_iterator (iterator, "WITH users_with_access (\"user\") AS %s" " SELECT DISTINCT min_qod, override, \"user\"" " FROM report_counts" " WHERE report = %llu" " AND min_qod <= %d" " AND \"user\" IN (SELECT \"user\"" " FROM users_with_access)" " ORDER BY \"user\"", users_string, report, min_qod_limit); } g_free (users_string); g_free (report_id); } /** * @brief Get the min_qod from a report_counts build iterator. * * @param[in] iterator Iterator. * * @return The min_qod. */ static int report_counts_build_iterator_min_qod (iterator_t *iterator) { return iterator_int (iterator, 0); } /** * @brief Get the override flag from a report_counts build iterator. * * @param[in] iterator Iterator. * * @return Whether the report counts are using overrides. */ static int report_counts_build_iterator_override (iterator_t *iterator) { return iterator_int (iterator, 1); } /** * @brief Get the user from a report_counts build iterator. * * @param[in] iterator Iterator. * * @return The min_qod. */ static user_t report_counts_build_iterator_user (iterator_t *iterator) { return iterator_int64 (iterator, 2); } /** * @brief Cache report counts and clear existing caches if requested. * * @param[in] report Report to cache counts of. * @param[in] clear_original Whether to clear existing cache for * original severity. * @param[in] clear_overridden Whether to clear existing cache for * overridden severity. * @param[in] users_where Optional SQL clause to limit users. */ static void report_cache_counts (report_t report, int clear_original, int clear_overridden, const char* users_where) { iterator_t cache_iterator; int holes, infos, logs, warnings, false_positives; double severity; get_data_t *get = NULL; gchar *old_user_id; old_user_id = current_credentials.uuid; init_report_counts_build_iterator (&cache_iterator, report, INT_MAX, 1, users_where); while (next (&cache_iterator)) { int override = report_counts_build_iterator_override (&cache_iterator); int min_qod = report_counts_build_iterator_min_qod (&cache_iterator); user_t user = report_counts_build_iterator_user (&cache_iterator); current_credentials.uuid = sql_string ("SELECT uuid FROM users WHERE id = %llu", user); manage_session_init (current_credentials.uuid); get = report_results_get_data (1, -1, override, min_qod); if ((clear_original && override == 0) || (clear_overridden && override)) { sql ("DELETE FROM report_counts" " WHERE report = %llu" " AND \"user\" = %llu" " AND override = %d" " AND min_qod = %d", report, user, override, min_qod); } report_counts_id (report, &holes, &infos, &logs, &warnings, &false_positives, &severity, get, NULL); get_data_reset (get); g_free (get); g_free (current_credentials.uuid); } cleanup_iterator (&cache_iterator); current_credentials.uuid = old_user_id; manage_session_init (current_credentials.uuid); } /** * @brief Clear report counts . * * @param[in] report Report. * @param[in] clear_original Whether to clear existing cache for * original severity. * @param[in] clear_overridden Whether to clear existing cache for * overridden severity. * @param[in] users_where Optional SQL clause to limit users. */ static void report_clear_count_cache (report_t report, int clear_original, int clear_overridden, const char* users_where) { gchar *extra_where = NULL; if (users_where) { extra_where = g_strdup_printf (" AND \"user\" IN (SELECT id FROM users WHERE %s)", users_where); } if (clear_original && clear_overridden) { sql ("DELETE FROM report_counts" " WHERE report = %llu" "%s", report, extra_where ? extra_where : ""); } else if (clear_original || clear_overridden) { int override = clear_overridden ? 1 : 0; sql ("DELETE FROM report_counts" " WHERE report = %llu" " AND override = %d" "%s", report, override, extra_where ? extra_where : ""); } } /** * @brief Make a report. * * @param[in] task The task associated with the report. * @param[in] uuid The UUID of the report. * @param[in] status The run status of the scan associated with the report. * * @return A report descriptor for the new report. */ report_t make_report (task_t task, const char* uuid, task_status_t status) { sql ("INSERT into reports (uuid, owner, task, date, comment," " scan_run_status, slave_progress)" " VALUES ('%s'," " (SELECT owner FROM tasks WHERE tasks.id = %llu)," " %llu, %i, '', %u, 0);", uuid, task, task, time (NULL), status); return sql_last_insert_id (); } /** * @brief Create the current report for a task. * * @param[in] task The task. * @param[out] report_id Report ID. * @param[in] status Run status of scan associated with report. * * @return 0 success, -1 global_current_report is already set, -2 failed to * generate ID. */ int create_current_report (task_t task, char **report_id, task_status_t status) { char *id; assert (global_current_report == (report_t) 0); if (global_current_report) return -1; if (report_id == NULL) report_id = &id; /* Generate report UUID. */ *report_id = gvm_uuid_make (); if (*report_id == NULL) return -2; /* Create the report. */ global_current_report = make_report (task, *report_id, status); set_report_scheduled (global_current_report); return 0; } /** * @brief Free a host detail. * * @param[in] detail Host detail. */ void host_detail_free (host_detail_t *detail) { g_free (detail->ip); g_free (detail->name); g_free (detail->source_desc); g_free (detail->source_name); g_free (detail->source_type); g_free (detail->value); } /** * @brief Insert a host detail into a report. * * @param[in] report The detail's report. * @param[in] host The detail's host. * @param[in] s_type The detail's source type. * @param[in] s_name The detail's source name. * @param[in] s_desc The detail's source description. * @param[in] name The detail's name. * @param[in] value The detail's value. */ void insert_report_host_detail (report_t report, const char *host, const char *s_type, const char *s_name, const char *s_desc, const char *name, const char *value) { char *quoted_host, *quoted_source_name, *quoted_source_type; char *quoted_source_desc, *quoted_name, *quoted_value; quoted_host = sql_quote (host); quoted_source_type = sql_quote (s_type); quoted_source_name = sql_quote (s_name); quoted_source_desc = sql_quote (s_desc); quoted_name = sql_quote (name); quoted_value = sql_quote (value); sql ("INSERT INTO report_host_details" " (report_host, source_type, source_name, source_description," " name, value)" " VALUES" " ((SELECT id FROM report_hosts" " WHERE report = %llu AND host = '%s')," " '%s', '%s', '%s', '%s', '%s');", report, quoted_host, quoted_source_type, quoted_source_name, quoted_source_desc, quoted_name, quoted_value); g_free (quoted_host); g_free (quoted_source_type); g_free (quoted_source_name); g_free (quoted_source_desc); g_free (quoted_name); g_free (quoted_value); } /** * @brief Maximum number of values per insert, when uploading report. */ #define CREATE_REPORT_INSERT_SIZE 300 /** * @brief Number of results per transaction, when uploading report. */ #define CREATE_REPORT_CHUNK_SIZE 10 /** * @brief Number of microseconds to sleep between insert chunks. */ #define CREATE_REPORT_CHUNK_SLEEP 1000 /** * @brief Create a report from an array of results. * * @param[in] results Array of create_report_result_t pointers. * @param[in] task_id UUID of container task, or NULL to create new one. * @param[in] in_assets Whether to create assets from the report. * @param[in] scan_start Scan start time text. * @param[in] scan_end Scan end time text. * @param[in] host_starts Array of create_report_result_t pointers. Host * name in host, time in description. * @param[in] host_ends Array of create_report_result_t pointers. Host * name in host, time in description. * @param[in] details Array of host_detail_t pointers. * @param[out] report_id Report ID. * * @return 0 success, 99 permission denied, -1 error, -2 failed to generate ID, * -3 task_id is NULL, -4 failed to find task, -5 task must be * container, -6 permission to create assets denied. */ int create_report (array_t *results, const char *task_id, const char *in_assets, const char *scan_start, const char *scan_end, array_t *host_starts, array_t *host_ends, array_t *details, char **report_id) { int index, in_assets_int, count, insert_count, first, rc; create_report_result_t *result, *end, *start; report_t report; user_t owner; task_t task; pid_t pid; host_detail_t *detail; GString *insert; in_assets_int = (in_assets && strcmp (in_assets, "") && strcmp (in_assets, "0")); if (in_assets_int && acl_user_may ("create_asset") == 0) return -6; g_debug ("%s", __func__); if (acl_user_may ("create_report") == 0) return 99; if (task_id == NULL) return -3; sql_begin_immediate (); /* Find the task. */ rc = 0; /* It's important that the task is not in the trash, because we * are inserting results below. This find function will fail if * the task is in the trash. */ if (find_task_with_permission (task_id, &task, "modify_task")) rc = -1; else if (task == 0) rc = -4; else if (task_target (task)) rc = -5; if (rc) { sql_rollback (); return rc; } /* Generate report UUID. */ *report_id = gvm_uuid_make (); if (*report_id == NULL) return -2; /* Create the report. */ report = make_report (task, *report_id, TASK_STATUS_RUNNING); if (scan_start) { sql ("UPDATE reports SET start_time = %i WHERE id = %llu;", parse_iso_time (scan_start), report); } if (scan_end) { sql ("UPDATE reports SET end_time = %i WHERE id = %llu;", parse_iso_time (scan_end), report); } /* Show that the upload has started. */ set_task_run_status (task, TASK_STATUS_RUNNING); sql ("UPDATE tasks SET upload_result_count = %llu WHERE id = %llu;", results->len, task); sql_commit (); /* Fork a child to import the results while the parent responds to the * client. */ pid = fork (); switch (pid) { case 0: { /* Child. * * Fork again so the parent can wait on the child, to prevent * zombies. */ cleanup_manage_process (FALSE); pid = fork (); switch (pid) { case 0: /* Grandchild. Reopen the database (required after fork) and carry on * to import the reports, . */ reinit_manage_process (); break; case -1: /* Grandchild's parent when error. */ g_warning ("%s: fork: %s", __func__, strerror (errno)); exit (EXIT_FAILURE); break; default: /* Grandchild's parent. Exit, to close parent's wait. */ g_debug ("%s: %i forked %i", __func__, getpid (), pid); exit (EXIT_SUCCESS); break; } } break; case -1: /* Parent when error. */ g_warning ("%s: fork: %s", __func__, strerror (errno)); global_current_report = report; set_task_interrupted (task, "Failed to fork child to import report." " Setting task status to Interrupted."); global_current_report = 0; return -1; break; default: { int status; /* Parent. Wait to prevent zombie, then return to respond to client. */ g_debug ("%s: %i forked %i", __func__, getpid (), pid); while (waitpid (pid, &status, 0) < 0) { if (errno == ECHILD) { g_warning ("%s: Failed to get child exit status", __func__); return -1; } if (errno == EINTR) continue; g_warning ("%s: waitpid: %s", __func__, strerror (errno)); return -1; } return 0; break; } } proctitle_set ("gvmd: Importing results"); /* Add the results. */ if (sql_int64 (&owner, "SELECT owner FROM tasks WHERE tasks.id = %llu", task)) { g_warning ("%s: failed to get owner of task", __func__); return -1; } sql_begin_immediate (); g_debug ("%s: add hosts", __func__); index = 0; while ((start = (create_report_result_t*) g_ptr_array_index (host_starts, index++))) if (start->host && start->description) manage_report_host_add (report, start->host, parse_iso_time (start->description), 0); g_debug ("%s: add results", __func__); insert = g_string_new (""); index = 0; first = 1; insert_count = 0; count = 0; while ((result = (create_report_result_t*) g_ptr_array_index (results, index++))) { gchar *quoted_host, *quoted_hostname, *quoted_port, *quoted_nvt_oid; gchar *quoted_description, *quoted_scan_nvt_version, *quoted_severity; gchar *quoted_qod, *quoted_qod_type; g_debug ("%s: add results: index: %i", __func__, index); quoted_host = sql_quote (result->host ? result->host : ""); quoted_hostname = sql_quote (result->hostname ? result->hostname : ""); quoted_port = sql_quote (result->port ? result->port : ""); quoted_nvt_oid = sql_quote (result->nvt_oid ? result->nvt_oid : ""); quoted_description = sql_quote (result->description ? result->description : ""); quoted_scan_nvt_version = sql_quote (result->scan_nvt_version ? result->scan_nvt_version : ""); quoted_severity = sql_quote (result->severity ? result->severity : ""); if (result->qod && strcmp (result->qod, "") && strcmp (result->qod, "0")) quoted_qod = sql_quote (result->qod); else quoted_qod = g_strdup (G_STRINGIFY (QOD_DEFAULT)); quoted_qod_type = sql_quote (result->qod_type ? result->qod_type : ""); result_nvt_notice (quoted_nvt_oid); if (first) g_string_append (insert, "INSERT INTO results" " (uuid, owner, date, task, host, hostname, port," " nvt, type, description," " nvt_version, severity, qod, qod_type," " result_nvt, report)" " VALUES"); else g_string_append (insert, ", "); first = 0; g_string_append_printf (insert, " (make_uuid (), %llu, m_now (), %llu, '%s'," " '%s', '%s', '%s', '%s', '%s', '%s', '%s'," " '%s', '%s'," " (SELECT id FROM result_nvts WHERE nvt = '%s')," " %llu)", owner, task, quoted_host, quoted_hostname, quoted_port, quoted_nvt_oid, result->threat ? threat_message_type (result->threat) : "Log Message", quoted_description, quoted_scan_nvt_version, quoted_severity, quoted_qod, quoted_qod_type, quoted_nvt_oid, report); /* Limit the number of results inserted at a time. */ if (insert_count == CREATE_REPORT_INSERT_SIZE) { sql ("%s", insert->str); g_string_truncate (insert, 0); count++; insert_count = 0; first = 1; if (count == CREATE_REPORT_CHUNK_SIZE) { report_cache_counts (report, 1, 1, NULL); sql_commit (); gvm_usleep (CREATE_REPORT_CHUNK_SLEEP); sql_begin_immediate (); count = 0; } } insert_count++; g_free (quoted_host); g_free (quoted_hostname); g_free (quoted_port); g_free (quoted_nvt_oid); g_free (quoted_description); g_free (quoted_scan_nvt_version); g_free (quoted_severity); g_free (quoted_qod); g_free (quoted_qod_type); } if (first == 0) { sql ("%s", insert->str); report_cache_counts (report, 1, 1, NULL); sql_commit (); gvm_usleep (CREATE_REPORT_CHUNK_SLEEP); sql_begin_immediate (); } sql ("INSERT INTO result_nvt_reports (result_nvt, report)" " SELECT distinct result_nvt, %llu FROM results" " WHERE results.report = %llu;", report, report); g_debug ("%s: add host ends", __func__); index = 0; count = 0; while ((end = (create_report_result_t*) g_ptr_array_index (host_ends, index++))) if (end->host) { gchar *quoted_host; quoted_host = sql_quote (end->host); if (end->description) sql ("UPDATE report_hosts SET end_time = %i" " WHERE report = %llu AND host = '%s';", parse_iso_time (end->description), report, quoted_host); else sql ("UPDATE report_hosts SET end_time = NULL" " WHERE report = %llu AND host = '%s';", report, quoted_host); g_free (quoted_host); count++; if (count == CREATE_REPORT_CHUNK_SIZE) { sql_commit (); gvm_usleep (CREATE_REPORT_CHUNK_SLEEP); sql_begin_immediate (); count = 0; } } g_debug ("%s: add host details", __func__); index = 0; first = 1; count = 0; insert_count = 0; g_string_truncate (insert, 0); while ((detail = (host_detail_t*) g_ptr_array_index (details, index++))) if (detail->ip && detail->name) { char *quoted_host, *quoted_source_name, *quoted_source_type; char *quoted_source_desc, *quoted_name, *quoted_value; quoted_host = sql_quote (detail->ip); quoted_source_type = sql_quote (detail->source_type ?: ""); quoted_source_name = sql_quote (detail->source_name ?: ""); quoted_source_desc = sql_quote (detail->source_desc ?: ""); quoted_name = sql_quote (detail->name); quoted_value = sql_quote (detail->value ?: ""); if (first) g_string_append (insert, "INSERT INTO report_host_details" " (report_host, source_type, source_name," " source_description, name, value)" " VALUES"); else g_string_append (insert, ", "); first = 0; g_string_append_printf (insert, " ((SELECT id FROM report_hosts" " WHERE report = %llu AND host = '%s')," " '%s', '%s', '%s', '%s', '%s')", report, quoted_host, quoted_source_type, quoted_source_name, quoted_source_desc, quoted_name, quoted_value); g_free (quoted_host); g_free (quoted_source_type); g_free (quoted_source_name); g_free (quoted_source_desc); g_free (quoted_name); g_free (quoted_value); /* Limit the number of details inserted at a time. */ if (insert_count == CREATE_REPORT_INSERT_SIZE) { sql ("%s", insert->str); g_string_truncate (insert, 0); count++; insert_count = 0; first = 1; if (count == CREATE_REPORT_CHUNK_SIZE) { sql_commit (); gvm_usleep (CREATE_REPORT_CHUNK_SLEEP); sql_begin_immediate (); count = 0; } } insert_count++; } sql_commit (); index = 0; sql_begin_immediate (); while ((end = (create_report_result_t*) g_ptr_array_index (host_ends, index++))) if (end->host) { sql_commit (); gvm_usleep (CREATE_REPORT_CHUNK_SLEEP); add_assets_from_host_in_report (report, end->host); sql_begin_immediate (); } if (first == 0) sql ("%s", insert->str); sql_commit (); g_string_free (insert, TRUE); current_scanner_task = task; global_current_report = report; set_task_run_status (task, TASK_STATUS_DONE); current_scanner_task = 0; global_current_report = 0; if (in_assets_int) { create_asset_report (*report_id, ""); } exit (EXIT_SUCCESS); return 0; } /** * @brief Return the UUID of a report. * * @param[in] report Report. * * @return Report UUID. */ char* report_uuid (report_t report) { return sql_string ("SELECT uuid FROM reports WHERE id = %llu;", report); } /** * @brief Return the task of a report. * * @param[in] report A report. * @param[out] task Task return, 0 if successfully failed to find task. * * @return FALSE on success (including if failed to find report), TRUE on error. */ gboolean report_task (report_t report, task_t *task) { switch (sql_int64 (task, "SELECT task FROM reports WHERE id = %llu;", report)) { case 0: break; case 1: /* Too few rows in result of query. */ *task = 0; break; default: /* Programming error. */ assert (0); case -1: return TRUE; break; } return FALSE; } /** * @brief Get compliance counts for a report. * * @param[in] report_id UUID of the report. * @param[out] compliance_yes Number of "YES" results. * @param[out] compliance_no Number of "NO" results. * @param[out] compliance_incomplete Number of "INCOMPLETE" results. */ void report_compliance_by_uuid (const char *report_id, int *compliance_yes, int *compliance_no, int *compliance_incomplete) { report_t report; gchar *quoted_uuid = sql_quote (report_id); sql_int64 (&report, "SELECT id FROM reports WHERE uuid = '%s';", quoted_uuid); if (compliance_yes) { *compliance_yes = sql_int ("SELECT count(*) FROM results" " WHERE report = %llu" " AND description LIKE 'Compliant:%%YES%%';", report); } if (compliance_no) { *compliance_no = sql_int ("SELECT count(*) FROM results" " WHERE report = %llu" " AND description LIKE 'Compliant:%%NO%%';", report); } if (compliance_incomplete) { *compliance_incomplete = sql_int ("SELECT count(*) FROM results" " WHERE report = %llu" " AND description LIKE 'Compliant:%%INCOMPLETE%%';", report); } g_free (quoted_uuid); } /** * @brief Return the source interface of a report. * * @param[in] report Report. * * @return Source interface. */ static char* report_source_iface (report_t report) { return sql_string ("SELECT source_iface FROM reports WHERE id = %llu;", report); } /** * @brief Add a result to a report. * * @param[in] report The report. * @param[in] result The result. */ static void report_add_result_for_buffer (report_t report, result_t result) { double severity, ov_severity; int qod; rowid_t rowid; iterator_t cache_iterator; user_t previous_user = 0; assert (result); if (report == 0) return; if (sql_int ("SELECT NOT EXISTS (SELECT * from result_nvt_reports" " WHERE result_nvt = (SELECT result_nvt" " FROM results" " WHERE id = %llu)" " AND report = %llu);", result, report)) sql ("INSERT INTO result_nvt_reports (result_nvt, report)" " VALUES ((SELECT result_nvt FROM results WHERE id = %llu)," " %llu);", result, report); qod = sql_int ("SELECT qod FROM results WHERE id = %llu;", result); severity = sql_double ("SELECT severity FROM results WHERE id = %llu;", result); ov_severity = severity; init_report_counts_build_iterator (&cache_iterator, report, qod, 1, NULL); while (next (&cache_iterator)) { int min_qod = report_counts_build_iterator_min_qod (&cache_iterator); int override = report_counts_build_iterator_override (&cache_iterator); user_t user = report_counts_build_iterator_user (&cache_iterator); if (override && user != previous_user) { char *ov_severity_str; gchar *owned_clause, *with_clause; owned_clause = acl_where_owned_for_get ("override", NULL, NULL, &with_clause); ov_severity_str = sql_string ("%s" " SELECT coalesce (overrides.new_severity, %1.1f)" " FROM overrides, results" " WHERE results.id = %llu" " AND overrides.nvt = results.nvt" " AND %s" " AND ((overrides.end_time = 0)" " OR (overrides.end_time >= m_now ()))" " AND (overrides.task =" " (SELECT reports.task FROM reports" " WHERE reports.id = %llu)" " OR overrides.task = 0)" " AND (overrides.result = results.id" " OR overrides.result = 0)" " AND (overrides.hosts is NULL" " OR overrides.hosts = ''" " OR hosts_contains (overrides.hosts," " results.host))" " AND (overrides.port is NULL" " OR overrides.port = ''" " OR overrides.port = results.port)" " AND severity_matches_ov (%1.1f," " overrides.severity)" " ORDER BY overrides.result DESC," " overrides.task DESC, overrides.port DESC," " overrides.severity ASC," " overrides.creation_time DESC" " LIMIT 1", with_clause ? with_clause : "", severity, result, owned_clause, report, severity); g_free (with_clause); g_free (owned_clause); if (ov_severity_str == NULL || (sscanf (ov_severity_str, "%lf", &ov_severity) != 1)) ov_severity = severity; free (ov_severity_str); previous_user = user; } rowid = 0; sql_int64 (&rowid, "SELECT id FROM report_counts" " WHERE report = %llu" " AND \"user\" = %llu" " AND override = %d" " AND severity = %1.1f" " AND min_qod = %d", report, user, override, override ? ov_severity : severity, min_qod); if (rowid) sql ("UPDATE report_counts" " SET count = count + 1" " WHERE id = %llu;", rowid); else sql ("INSERT INTO report_counts" " (report, \"user\", override, min_qod, severity, count, end_time)" " VALUES" " (%llu, %llu, %d, %d, %1.1f, 1, 0);", report, user, override, override ? ov_severity : severity, min_qod); } cleanup_iterator (&cache_iterator); } /** * @brief Add a result to a report. * * @param[in] report The report. * @param[in] result The result. */ void report_add_result (report_t report, result_t result) { if (report == 0 || result == 0) return; sql ("UPDATE results SET report = %llu," " owner = (SELECT reports.owner" " FROM reports WHERE id = %llu)" " WHERE id = %llu;", report, report, result); report_add_result_for_buffer (report, result); sql ("UPDATE report_counts" " SET end_time = (SELECT coalesce(min(overrides.end_time), 0)" " FROM overrides, results" " WHERE overrides.nvt = results.nvt" " AND results.report = %llu" " AND overrides.end_time >= m_now ())" " WHERE report = %llu AND override = 1;", report, report); } /** * @brief Add results from an array to a report. * * @param[in] report The report to add the results to. * @param[in] results GArray containing the row ids of the results to add. */ void report_add_results_array (report_t report, GArray *results) { GString *array_sql; int index; if (report == 0 || results == NULL || results->len == 0) return; array_sql = g_string_new ("("); for (index = 0; index < results->len; index++) { result_t result; result = g_array_index (results, result_t, index); if (index) g_string_append (array_sql, ", "); g_string_append_printf (array_sql, "%llu", result); } g_string_append_c (array_sql, ')'); sql ("UPDATE results SET report = %llu," " owner = (SELECT reports.owner" " FROM reports WHERE id = %llu)" " WHERE id IN %s;", report, report, array_sql->str); for (index = 0; index < results->len; index++) { result_t result; result = g_array_index (results, result_t, index); report_add_result_for_buffer (report, result); } sql ("UPDATE report_counts" " SET end_time = (SELECT coalesce(min(overrides.end_time), 0)" " FROM overrides, results" " WHERE overrides.nvt = results.nvt" " AND results.report = %llu" " AND overrides.end_time >= m_now ())" " WHERE report = %llu AND override = 1;", report, report); g_string_free (array_sql, TRUE); } /** * @brief Filter columns for report iterator. */ #define REPORT_ITERATOR_FILTER_COLUMNS \ { ANON_GET_ITERATOR_FILTER_COLUMNS, "task_id", "name", "date", "status", \ "task", "severity", "false_positive", "log", "low", "medium", "high", \ "hosts", "result_hosts", "fp_per_host", "log_per_host", "low_per_host", \ "medium_per_host", "high_per_host", "duration", "duration_per_host", \ NULL } /** * @brief Report iterator columns. */ #define REPORT_ITERATOR_COLUMNS \ { \ { "id", NULL, KEYWORD_TYPE_INTEGER }, \ { "uuid", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (start_time)", "name", KEYWORD_TYPE_STRING }, \ { "''", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (start_time)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (end_time)", NULL, KEYWORD_TYPE_STRING }, \ { "start_time", "created", KEYWORD_TYPE_INTEGER }, \ { "end_time", "modified", KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM users WHERE users.id = reports.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "owner", NULL, KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Report iterator columns. */ #define REPORT_ITERATOR_WHERE_COLUMNS \ { \ { "run_status_name (scan_run_status)", "status", KEYWORD_TYPE_STRING }, \ { \ "(SELECT uuid FROM tasks WHERE tasks.id = task)", \ "task_id", \ KEYWORD_TYPE_STRING \ }, \ { "date", NULL, KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM tasks WHERE tasks.id = task)", "task" }, \ { \ "report_severity (id, opts.override, opts.min_qod)", \ "severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "report_severity_count (id, opts.override, opts.min_qod," \ " 'False Positive')", \ "false_positive", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "report_severity_count (id, opts.override, opts.min_qod, 'Log')", \ "log", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "report_severity_count (id, opts.override, opts.min_qod, 'Low')", \ "low", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "report_severity_count (id, opts.override, opts.min_qod, 'Medium')", \ "medium", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "report_severity_count (id, opts.override, opts.min_qod, 'High')", \ "high", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT name FROM users WHERE users.id = reports.owner)", \ "_owner", \ KEYWORD_TYPE_STRING \ }, \ { \ "report_host_count (id)", \ "hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "report_result_host_count (id, opts.min_qod)", \ "result_hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "coalesce (report_severity_count (id, opts.override, opts.min_qod," \ " 'False Positive') * 1.0" \ " / nullif (report_result_host_count (id, opts.min_qod), 0),"\ " 0)", \ "fp_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "coalesce (report_severity_count (id, opts.override, opts.min_qod," \ " 'Log') * 1.0" \ " / nullif (report_result_host_count (id, opts.min_qod), 0),"\ " 0)", \ "log_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "coalesce (report_severity_count (id, opts.override, opts.min_qod," \ " 'Low') * 1.0" \ " / nullif (report_result_host_count (id, opts.min_qod), 0),"\ " 0)", \ "low_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "coalesce (report_severity_count (id, opts.override, opts.min_qod," \ " 'Medium') * 1.0" \ " / nullif (report_result_host_count (id, opts.min_qod), 0),"\ " 0)", \ "medium_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "coalesce (report_severity_count (id, opts.override, opts.min_qod," \ " 'High') * 1.0" \ " / nullif (report_result_host_count (id, opts.min_qod), 0),"\ " 0)", \ "high_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(CASE WHEN (start_time IS NULL or end_time IS NULL)" \ " THEN NULL ELSE end_time - start_time END)", \ "duration", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(CASE WHEN (start_time IS NULL or end_time IS NULL" \ " or report_result_host_count (id, opts.min_qod) = 0)" \ " THEN NULL" \ " ELSE (end_time - start_time)" \ " / report_result_host_count (id, opts.min_qod) END)", \ "duration_per_host", \ KEYWORD_TYPE_INTEGER \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Generate the extra_tables string for a report iterator. * * @param[in] override Whether to apply overrides. * @param[in] min_qod Minimum QoD of results to count. * * @return Newly allocated string with the extra_tables clause. */ static gchar* report_iterator_opts_table (int override, int min_qod) { return g_strdup_printf (", (SELECT" " %d AS override," " %d AS min_qod)" " AS opts", override, min_qod); } /** * @brief Count number of reports. * * @param[in] get GET params. * * @return Total number of reports in filtered set. */ int report_count (const get_data_t *get) { static const char *filter_columns[] = REPORT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = REPORT_ITERATOR_COLUMNS; static column_t where_columns[] = REPORT_ITERATOR_WHERE_COLUMNS; gchar *extra_tables; int ret; extra_tables = report_iterator_opts_table (0, MIN_QOD_DEFAULT); ret = count2 ("report", get, columns, NULL, where_columns, NULL, filter_columns, 0, extra_tables, get->trash ? " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 2" : " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 0", NULL, TRUE); g_free (extra_tables); return ret; } /** * @brief Initialise a report iterator, including observed reports. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find report, 2 failed to find filter, * -1 error. */ int init_report_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = REPORT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = REPORT_ITERATOR_COLUMNS; static column_t where_columns[] = REPORT_ITERATOR_WHERE_COLUMNS; char *filter; int overrides, min_qod; gchar *extra_tables; int ret; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; overrides = filter_term_apply_overrides (filter ? filter : get->filter); min_qod = filter_term_min_qod (filter ? filter : get->filter); free (filter); extra_tables = report_iterator_opts_table (overrides, min_qod); ret = init_get_iterator2 (iterator, "report", get, /* Columns. */ columns, NULL, /* Filterable columns not in SELECT columns. */ where_columns, NULL, filter_columns, 0, extra_tables, get->trash ? " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 2" : " AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 0", NULL, TRUE, FALSE, NULL); g_free (extra_tables); return ret; } /** * @brief Initialise a report iterator. * * @param[in] iterator Iterator. * @param[in] task Task whose reports the iterator loops over. */ void init_report_iterator_task (iterator_t* iterator, task_t task) { assert (task); init_iterator (iterator, "SELECT id, uuid FROM reports WHERE task = %llu;", task); } /** * @brief Get the UUID from a report iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (report_iterator_uuid, 1); /** * @brief Read the next report from an iterator. * * @param[in] iterator Task iterator. * @param[out] report Report. * * @return TRUE if there was a next task, else FALSE. */ gboolean next_report (iterator_t* iterator, report_t* report) { if (next (iterator)) { *report = iterator_int64 (iterator, 0); return TRUE; } return FALSE; } /** * @brief Return SQL WHERE for restricting a SELECT to levels. * * @param[in] levels String describing threat levels (message types) * to include in report (for example, "hmlg" for * High, Medium, Low and loG). All levels if NULL. * @param[in] new_severity_sql SQL for new severity. * * @return WHERE clause for levels if one is required, else NULL. */ static GString * where_levels_auto (const char *levels, const char *new_severity_sql) { int count; GString *levels_sql; /* Generate SQL for constraints on message type, according to levels. */ levels_sql = g_string_new (""); if (levels == NULL || strlen (levels) == 0) { g_string_append_printf (levels_sql, " AND %s != " G_STRINGIFY (SEVERITY_ERROR), new_severity_sql); return levels_sql; } count = 0; g_string_append_printf (levels_sql, " AND severity_in_levels (%s", new_severity_sql); if (strchr (levels, 'h')) { g_string_append (levels_sql, ", 'high'"); count++; } if (strchr (levels, 'm')) { g_string_append (levels_sql, ", 'medium'"); count++; } if (strchr (levels, 'l')) { g_string_append (levels_sql, ", 'low'"); count++; } if (strchr (levels, 'g')) { g_string_append (levels_sql, ", 'log'"); count++; } if (strchr (levels, 'f')) { g_string_append (levels_sql, ", 'false'"); count++; } if (count == 0) { g_string_free (levels_sql, TRUE); return NULL; } g_string_append (levels_sql, ")"); if (count == 5) { /* All levels. */ g_string_free (levels_sql, TRUE); levels_sql = g_string_new (""); /* It's not possible to override from or to the error severity, so no * need to use the overridden severity here (new_severity_sql). This * helps with the default result counting performance because the * overridden severity is complex. */ g_string_append_printf (levels_sql, " AND severity != " G_STRINGIFY (SEVERITY_ERROR)); } return levels_sql; } /** * @brief Return SQL WHERE for restricting a SELECT to a minimum QoD. * * @param[in] min_qod Minimum value for QoD. * * @return WHERE clause if one is required, else an empty string. */ static gchar* where_qod (int min_qod) { gchar *qod_sql; if (min_qod <= 0) qod_sql = g_strdup (""); else qod_sql = g_strdup_printf (" AND (results.qod >= CAST (%d AS INTEGER))", min_qod); return qod_sql; } /** * @brief Filter columns for result iterator. */ #define RESULT_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "host", "location", "nvt", \ "type", "original_type", \ "description", "task", "report", "cvss_base", "nvt_version", \ "severity", "original_severity", "vulnerability", "date", "report_id", \ "solution_type", "qod", "qod_type", "task_id", "cve", "hostname", \ "path", NULL } // TODO Combine with RESULT_ITERATOR_COLUMNS. /** * @brief Result iterator filterable columns, for severity only version . */ #define BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE \ { "id", NULL, KEYWORD_TYPE_INTEGER }, \ { "uuid", NULL, KEYWORD_TYPE_STRING }, \ { "(SELECT name FROM nvts WHERE nvts.oid = nvt)", \ "name", \ KEYWORD_TYPE_STRING }, \ { "''", "comment", KEYWORD_TYPE_STRING }, \ { " iso_time (date, opts.user_zone)", \ "creation_time", \ KEYWORD_TYPE_STRING }, \ { " iso_time (date, opts.user_zone)", \ "modification_time", \ KEYWORD_TYPE_STRING }, \ { "date", "created", KEYWORD_TYPE_INTEGER }, \ { "date", "modified", KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM users WHERE users.id = results.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "owner", NULL, KEYWORD_TYPE_INTEGER }, \ /* Result specific columns. */ \ { "host", NULL, KEYWORD_TYPE_STRING }, \ { "port", "location", KEYWORD_TYPE_STRING }, \ { "nvt", NULL, KEYWORD_TYPE_STRING }, \ { "severity_to_type (severity)", "original_type", KEYWORD_TYPE_STRING }, \ { "'Log Message'", /* Adjusted by init_result_get_iterator_severity. */ \ "type", \ KEYWORD_TYPE_STRING }, \ { "description", NULL, KEYWORD_TYPE_STRING }, \ { "task", NULL, KEYWORD_TYPE_INTEGER }, \ { "report", "report_rowid", KEYWORD_TYPE_INTEGER }, \ { "(SELECT cvss_base FROM nvts WHERE nvts.oid = nvt)", \ "cvss_base", \ KEYWORD_TYPE_DOUBLE }, \ { "nvt_version", NULL, KEYWORD_TYPE_STRING }, \ { "severity", "original_severity", KEYWORD_TYPE_DOUBLE }, \ { "(SELECT name FROM nvts WHERE nvts.oid = nvt)", \ "vulnerability", \ KEYWORD_TYPE_STRING }, \ { "date" , NULL, KEYWORD_TYPE_INTEGER }, \ { "(SELECT uuid FROM reports WHERE id = report)", \ "report_id", \ KEYWORD_TYPE_STRING }, \ { "(SELECT solution_type FROM nvts WHERE nvts.oid = nvt)", \ "solution_type", \ KEYWORD_TYPE_STRING }, \ { "qod", NULL, KEYWORD_TYPE_INTEGER }, \ { "qod_type", NULL, KEYWORD_TYPE_STRING }, \ { "(CASE WHEN (hostname IS NULL) OR (hostname = '')" \ " THEN (SELECT value FROM report_host_details" \ " WHERE name = 'hostname'" \ " AND report_host = (SELECT id FROM report_hosts" \ " WHERE report_hosts.host=results.host" \ " AND report_hosts.report = results.report)" \ " LIMIT 1)" \ " ELSE hostname" \ " END)", \ "hostname", \ KEYWORD_TYPE_STRING \ }, \ { "(SELECT uuid FROM tasks WHERE id = task)", \ "task_id", \ KEYWORD_TYPE_STRING }, \ { "(SELECT cve FROM nvts WHERE oid = nvt)", "cve", KEYWORD_TYPE_STRING }, \ { "path", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT CASE WHEN host IS NULL" \ " THEN NULL" \ " ELSE (SELECT uuid FROM hosts" \ " WHERE id = (SELECT host FROM host_identifiers" \ " WHERE source_type = 'Report Host'" \ " AND name = 'ip'" \ " AND source_id" \ " = (SELECT uuid" \ " FROM reports" \ " WHERE id = results.report)" \ " AND value = results.host" \ " LIMIT 1))" \ " END)", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT CASE" \ " WHEN EXISTS (SELECT * FROM notes" \ " WHERE (result = results.id" \ " OR (result = 0 AND nvt = results.nvt))" \ " AND (task = 0 OR task = results.task))" \ " THEN 1" \ " ELSE 0" \ " END)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "(SELECT CASE" \ " WHEN EXISTS (SELECT * FROM overrides" \ " WHERE (result = results.id" \ " OR (result = 0 AND nvt = results.nvt))" \ " AND (task = 0 OR task = results.task))" \ " THEN 1" \ " ELSE 0" \ " END)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { TICKET_SQL_RESULT_MAY_HAVE_TICKETS, \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM tasks WHERE tasks.id = task)", \ "task", \ KEYWORD_TYPE_STRING }, /** * @brief Result iterator columns. */ #define RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE \ { \ BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Result iterator columns, when CERT db is not loaded. */ #define RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE_NO_CERT \ { \ BASE_RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE \ { "0", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "0", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Result iterator columns. */ #define PRE_BASE_RESULT_ITERATOR_COLUMNS(new_severity_sql) \ { "results.id", NULL, KEYWORD_TYPE_INTEGER }, \ /* ^ 0 */ \ { "results.uuid", NULL, KEYWORD_TYPE_STRING }, \ { "nvts.name", \ "name", \ KEYWORD_TYPE_STRING }, \ { "''", "comment", KEYWORD_TYPE_STRING }, \ { " iso_time (date, opts.user_zone)", \ "creation_time", \ KEYWORD_TYPE_STRING }, \ { " iso_time (date, opts.user_zone)", \ "modification_time", \ KEYWORD_TYPE_STRING }, \ { "date", "created", KEYWORD_TYPE_INTEGER }, \ { "date", "modified", KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM users WHERE users.id = results.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "results.owner", NULL, KEYWORD_TYPE_INTEGER }, \ /* ^ 9 */ \ /* Result specific columns. */ \ { "host", NULL, KEYWORD_TYPE_STRING }, \ /* ^ 10 = 0 */ \ { "port", "location", KEYWORD_TYPE_STRING }, \ { "nvt", NULL, KEYWORD_TYPE_STRING }, \ { "severity_to_type (results.severity)", \ "original_type", \ KEYWORD_TYPE_STRING }, \ { "severity_to_type (" new_severity_sql ")", \ "type", \ KEYWORD_TYPE_STRING }, \ { "description", NULL, KEYWORD_TYPE_STRING }, \ { "task", NULL, KEYWORD_TYPE_INTEGER }, \ { "report", "report_rowid", KEYWORD_TYPE_INTEGER }, \ { "nvts.cvss_base", \ "cvss_base", \ KEYWORD_TYPE_DOUBLE }, \ { "nvt_version", NULL, KEYWORD_TYPE_STRING }, \ { "results.severity", "original_severity", KEYWORD_TYPE_DOUBLE }, \ /* ^ 20 = 10 */ \ { new_severity_sql, \ "severity", \ KEYWORD_TYPE_DOUBLE }, \ { "nvts.name", \ "vulnerability", \ KEYWORD_TYPE_STRING }, \ { "date" , NULL, KEYWORD_TYPE_INTEGER }, \ { "(SELECT uuid FROM reports WHERE id = report)", \ "report_id", \ KEYWORD_TYPE_STRING }, \ { "nvts.solution_type", \ "solution_type", \ KEYWORD_TYPE_STRING }, \ { "results.qod", "qod", KEYWORD_TYPE_INTEGER }, \ { "results.qod_type", NULL, KEYWORD_TYPE_STRING }, \ { "(CASE WHEN (hostname IS NULL) OR (hostname = '')" \ " THEN (SELECT value FROM report_host_details" \ " WHERE name = 'hostname'" \ " AND report_host = (SELECT id FROM report_hosts" \ " WHERE report_hosts.host=results.host" \ " AND report_hosts.report = results.report)" \ " LIMIT 1)" \ " ELSE hostname" \ " END)", \ "hostname", \ KEYWORD_TYPE_STRING \ }, \ { "(SELECT uuid FROM tasks WHERE id = task)", \ "task_id", \ KEYWORD_TYPE_STRING }, \ { "nvts.cve", "cve", KEYWORD_TYPE_STRING }, \ /* ^ 30 = 20 */ \ { "path", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT CASE WHEN host IS NULL" \ " THEN NULL" \ " ELSE (SELECT uuid FROM hosts" \ " WHERE id = (SELECT host FROM host_identifiers" \ " WHERE source_type = 'Report Host'" \ " AND name = 'ip'" \ " AND source_id" \ " = (SELECT uuid" \ " FROM reports" \ " WHERE id = results.report)" \ " AND value = results.host" \ " LIMIT 1))" \ " END)", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT CASE" \ " WHEN EXISTS (SELECT * FROM notes" \ " WHERE (result = results.id" \ " OR (result = 0 AND nvt = results.nvt))" \ " AND (task = 0 OR task = results.task))" \ " THEN 1" \ " ELSE 0" \ " END)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "(SELECT CASE" \ " WHEN EXISTS (SELECT * FROM overrides" \ " WHERE (result = results.id" \ " OR (result = 0 AND nvt = results.nvt))" \ " AND (task = 0 OR task = results.task))" \ " THEN 1" \ " ELSE 0" \ " END)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { TICKET_SQL_RESULT_MAY_HAVE_TICKETS, \ NULL, \ KEYWORD_TYPE_INTEGER }, \ /* ^ 35 = 25 */ \ { "(SELECT name FROM tasks WHERE tasks.id = task)", \ "task", \ KEYWORD_TYPE_STRING }, \ { "nvts.summary", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.insight", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.affected", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.impact", \ NULL, \ KEYWORD_TYPE_STRING }, \ /* ^ 40 = 30 */ \ { "nvts.solution", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.detection", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.family", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "nvts.tag", \ NULL, \ KEYWORD_TYPE_STRING }, \ /** * @brief Result iterator columns. */ #define BASE_RESULT_ITERATOR_COLUMNS \ PRE_BASE_RESULT_ITERATOR_COLUMNS("lateral_new_severity.new_severity") /** * @brief Result iterator columns. */ #define RESULT_ITERATOR_COLUMNS \ { \ BASE_RESULT_ITERATOR_COLUMNS \ { SECINFO_SQL_RESULT_CERT_BUNDS, \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { SECINFO_SQL_RESULT_DFN_CERTS, \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Result iterator columns, when CERT db is not loaded. */ #define RESULT_ITERATOR_COLUMNS_NO_CERT \ { \ BASE_RESULT_ITERATOR_COLUMNS \ { "0", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "0", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Generate the extra_tables string for a result iterator. * * @param[in] override Whether to apply overrides. * @param[in] dynamic Whether to use dynamic severity scores. * * @return Newly allocated string with the extra_tables clause. */ static gchar* result_iterator_opts_table (int override, int dynamic) { user_t user_id; gchar *user_zone, *quoted_user_zone, *ret; if (current_credentials.uuid) { user_id = sql_int64_0 ("SELECT id FROM users WHERE uuid = '%s';", current_credentials.uuid); if (user_id > 0) user_zone = sql_string ("SELECT" " coalesce ((SELECT current_setting" " ('gvmd.tz_override'))," " (SELECT timezone FROM users" " WHERE id = %llu));", user_id); else user_zone = g_strdup ("UTC"); } else { user_id = 0; user_zone = sql_string ("SELECT" " coalesce ((SELECT current_setting" " ('gvmd.tz_override'))," " 'UTC');"); } quoted_user_zone = sql_quote ("user_zone"); g_free (user_zone); ret = g_strdup_printf (", (SELECT" " '%s'::text AS user_zone," " %llu AS user_id," " %d AS override," " %d AS dynamic) AS opts", quoted_user_zone, user_id, override, dynamic); g_free (quoted_user_zone); return ret; } /** * @brief Get new severity clause. * * @param[in] apply_overrides Whether to apply overrides. * @param[in] dynamic_severity Whether to use dynamic severity. * * @return Newly allocated clause. */ static gchar* new_severity_clause (int apply_overrides, int dynamic_severity) { if (apply_overrides) { if (dynamic_severity) /* Overrides, dynamic. */ return g_strdup_printf ("(SELECT new_severity FROM result_new_severities_dynamic" " WHERE result_new_severities_dynamic.result = results.id" " AND result_new_severities_dynamic.user" " = (SELECT id FROM users WHERE uuid = '%s')" " LIMIT 1)", current_credentials.uuid); /* Overrides, no dynamic. */ return g_strdup_printf ("(SELECT new_severity FROM result_new_severities_static" " WHERE result_new_severities_static.result = results.id" " AND result_new_severities_static.user" " = (SELECT id FROM users WHERE uuid = '%s')" " LIMIT 1)", current_credentials.uuid); } if (dynamic_severity) /* Dynamic, no overrides. */ return g_strdup ("current_severity (results.severity," " results.nvt)"); /* No dynamic, no overrides. */ return g_strdup ("results.severity"); } /** * @brief Get extra_where string for a result iterator or count. * * @param[in] trash Whether to get results from trashcan. * @param[in] report Report to restrict returned results to. * @param[in] host Host to restrict returned results to. * @param[in] apply_overrides Whether to apply overrides. * @param[in] dynamic_severity Whether to use dynamic severity. * @param[in] filter Filter string. * @param[in] given_new_severity_sql SQL for new severity, or NULL. * * @return Newly allocated extra_where string. */ static gchar* results_extra_where (int trash, report_t report, const gchar* host, int apply_overrides, int dynamic_severity, const gchar *filter, const gchar *given_new_severity_sql) { gchar *extra_where; int min_qod; gchar *levels; gchar *report_clause, *host_clause, *min_qod_clause; GString *levels_clause; gchar *new_severity_sql; // Get filter values min_qod = filter_term_min_qod (filter); levels = filter_term_value (filter, "levels"); if (levels == NULL) levels = g_strdup ("hmlgdf"); // Build clause fragments if (given_new_severity_sql) new_severity_sql = NULL; else new_severity_sql = new_severity_clause (apply_overrides, dynamic_severity); // Build filter clauses report_clause = report ? g_strdup_printf (" AND (report = %llu) ", report) : NULL; if (host) { gchar *quoted_host = sql_quote (host); host_clause = g_strdup_printf (" AND (host = '%s') ", quoted_host); g_free (quoted_host); } else host_clause = NULL; min_qod_clause = where_qod (min_qod); levels_clause = where_levels_auto (levels ? levels : "hmlgdf", given_new_severity_sql ? given_new_severity_sql : new_severity_sql); g_free (levels); g_free (new_severity_sql); extra_where = g_strdup_printf("%s%s%s%s", report_clause ? report_clause : "", host_clause ? host_clause : "", levels_clause->str, min_qod_clause ? min_qod_clause : ""); g_free (min_qod_clause); g_string_free (levels_clause, TRUE); g_free (report_clause); g_free (host_clause); return extra_where; } /** * @brief Initialise the severity-only result iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * @param[in] report Report to restrict returned results to. * @param[in] host Host to limit results to. * @param[in] extra_order Extra text for ORDER term in SQL. * * @return 0 success, 1 failed to find result, 2 failed to find filter (filt_id), * -1 error. */ static int init_result_get_iterator_severity (iterator_t* iterator, const get_data_t *get, report_t report, const char* host, const gchar *extra_order) { column_t columns[2]; static column_t static_filterable_columns[] = RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE; static column_t static_filterable_columns_no_cert[] = RESULT_ITERATOR_COLUMNS_SEVERITY_FILTERABLE_NO_CERT; static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS; column_t *filterable_columns; int ret; gchar *filter; int apply_overrides, dynamic_severity; gchar *extra_tables, *extra_where, *extra_where_single, *opts, *with_clause; const gchar *lateral; assert (report); dynamic_severity = setting_dynamic_severity_int (); if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; apply_overrides = filter_term_apply_overrides (filter ? filter : get->filter); if (manage_cert_loaded ()) filterable_columns = column_array_copy (static_filterable_columns); else filterable_columns = column_array_copy (static_filterable_columns_no_cert); column_array_set (filterable_columns, "type", apply_overrides ? (dynamic_severity /* Overrides, dynamic. */ ? g_strdup_printf ("severity_to_type" " ((SELECT new_severity FROM result_new_severities_dynamic" " WHERE result_new_severities_dynamic.result = results.id" " AND result_new_severities_dynamic.user = opts.user_id" " LIMIT 1))") /* Overrides, no dynamic. */ : g_strdup_printf ("severity_to_type" " ((SELECT new_severity FROM result_new_severities_static" " WHERE result_new_severities_static.result = results.id" " AND result_new_severities_static.user = opts.user_id" " LIMIT 1))")) : (dynamic_severity /* Dynamic, no overrides. */ ? g_strdup ("severity_to_type (current_severity (results.severity," " results.nvt))") /* No dynamic, no overrides. */ : g_strdup ("severity_to_type (results.severity)"))); if (dynamic_severity) { if (apply_overrides) lateral = "coalesce ((SELECT new_severity FROM valid_overrides" " WHERE valid_overrides.result_nvt" " = results.result_nvt" " AND (valid_overrides.result = 0" " OR valid_overrides.result" " = results.id)" " AND (valid_overrides.hosts is NULL" " OR valid_overrides.hosts = ''" " OR hosts_contains" " (valid_overrides.hosts," " results.host))" " AND (valid_overrides.port is NULL" " OR valid_overrides.port = ''" " OR valid_overrides.port" " = results.port)" " AND severity_matches_ov" " (coalesce" " ((CASE WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base" " AS double precision)" " ELSE results.severity" " END)," " results.severity)," " valid_overrides.severity)" " LIMIT 1)," " coalesce ((CASE WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base" " AS double precision)" " ELSE results.severity" " END)," " results.severity))"; else lateral = "coalesce ((CASE WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base" " AS double precision)" " ELSE results.severity" " END)," " results.severity)"; } else { if (apply_overrides) lateral = "coalesce ((SELECT new_severity FROM valid_overrides" " WHERE valid_overrides.result_nvt" " = results.result_nvt" " AND (valid_overrides.result = 0" " OR valid_overrides.result" " = results.id)" " AND (valid_overrides.hosts is NULL" " OR valid_overrides.hosts = ''" " OR hosts_contains" " (valid_overrides.hosts," " results.host))" " AND (valid_overrides.port is NULL" " OR valid_overrides.port = ''" " OR valid_overrides.port" " = results.port)" " AND severity_matches_ov" " (results.severity," " valid_overrides.severity)" " LIMIT 1)," " results.severity)"; else lateral /* coalesce because results.severity gives syntax error. */ = "coalesce (results.severity, results.severity)"; } columns[0].select = "lateral_severity"; columns[0].filter = "severity"; columns[0].type = KEYWORD_TYPE_DOUBLE; columns[1].select = NULL; columns[1].filter = NULL; columns[1].type = KEYWORD_TYPE_UNKNOWN; opts = result_iterator_opts_table (apply_overrides, dynamic_severity); if (dynamic_severity) extra_tables = g_strdup_printf (" LEFT OUTER JOIN nvts" " ON results.nvt = nvts.oid," " LATERAL %s AS lateral_severity%s", lateral, opts); else extra_tables = g_strdup_printf (", LATERAL %s AS lateral_severity%s", lateral, opts); g_free (opts); extra_where = results_extra_where (get->trash, report, host, apply_overrides, dynamic_severity, filter ? filter : get->filter, "lateral_severity"); extra_where_single = results_extra_where (get->trash, report, host, apply_overrides, dynamic_severity, "min_qod=0", "lateral_severity"); free (filter); if (apply_overrides) { gchar *owned_clause, *overrides_with; char *user_id; user_id = sql_string ("SELECT id FROM users WHERE uuid = '%s';", current_credentials.uuid); // Do not get ACL with_clause as it will be added by // init_get_iterator2_with. owned_clause = acl_where_owned_for_get ("override", user_id, "valid_overrides_", &overrides_with); free (user_id); with_clause = g_strdup_printf (" %s," " valid_overrides" " AS (SELECT result_nvt, hosts, new_severity, port," " severity, result" " FROM overrides" " WHERE %s" /* Only use if override's NVT is in report. */ " AND EXISTS (SELECT * FROM result_nvt_reports" " WHERE report = %llu" " AND result_nvt" " = overrides.result_nvt)" " AND (task = 0" " OR task = (SELECT reports.task" " FROM reports" " WHERE reports.id = %llu))" " AND ((end_time = 0) OR (end_time >= m_now ()))" " ORDER BY result DESC, task DESC, port DESC," " severity ASC, creation_time DESC)" " ", overrides_with + strlen ("WITH "), owned_clause, report, report); g_free (overrides_with); g_free (owned_clause); } else with_clause = NULL; table_order_if_sort_not_specified = 1; ret = init_get_iterator2_with (iterator, "result", get, /* SELECT columns. */ columns, NULL, /* Filterable columns not in SELECT columns. */ filterable_columns, NULL, filter_columns, 0, extra_tables, extra_where, extra_where_single, TRUE, report ? TRUE : FALSE, extra_order, with_clause, 1, 1); table_order_if_sort_not_specified = 0; column_array_free (filterable_columns); g_free (with_clause); g_free (extra_tables); g_free (extra_where); g_free (extra_where_single); return ret; } /** * @brief SQL for getting current severity. */ #define CURRENT_SEVERITY_SQL \ "coalesce ((CASE WHEN results.severity > " G_STRINGIFY (SEVERITY_LOG) \ " THEN CAST (nvts.cvss_base AS double precision)" \ " ELSE results.severity" \ " END)," \ " results.severity)" /** * @brief Get LATERAL clause for result iterator. * * @param[in] apply_overrides Whether to apply overrides. * @param[in] dynamic_severity Whether to use dynamic severity. * * @return SQL clause for FROM. */ static const gchar * result_iterator_lateral (int apply_overrides, int dynamic_severity) { if (apply_overrides && dynamic_severity) /* Overrides, dynamic. */ return "(WITH curr AS (SELECT " CURRENT_SEVERITY_SQL " AS curr_severity)" " SELECT coalesce ((SELECT ov_new_severity FROM result_overrides" " WHERE result = results.id" " AND result_overrides.user = opts.user_id" " AND severity_matches_ov" " ((SELECT curr_severity FROM curr LIMIT 1)," " ov_old_severity)" " LIMIT 1)," " (SELECT curr_severity FROM curr LIMIT 1))" " AS new_severity)"; if (apply_overrides) /* Overrides, no dynamic. */ return "(SELECT new_severity" " FROM result_new_severities_static" " WHERE result_new_severities_static.result = results.id" " AND result_new_severities_static.user = opts.user_id" " LIMIT 1)"; if (dynamic_severity) /* No overrides, dynamic. */ return "(SELECT " CURRENT_SEVERITY_SQL " AS new_severity)"; /* No overrides, no dynamic. * * SELECT because results.severity gives syntax error. */ return "(SELECT results.severity AS new_severity)"; } /** * @brief Initialise a result iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * @param[in] report Report to restrict returned results to. * @param[in] host Host to limit results to. * @param[in] extra_order Extra text for ORDER term in SQL. * * @return 0 success, 1 failed to find result, 2 failed to find filter (filt_id), * -1 error. */ int init_result_get_iterator (iterator_t* iterator, const get_data_t *get, report_t report, const char* host, const gchar *extra_order) { static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = RESULT_ITERATOR_COLUMNS; static column_t columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT; int ret; gchar *filter, *extra_tables, *extra_where, *extra_where_single, *opts_tables; int apply_overrides, dynamic_severity; column_t *actual_columns; g_debug ("%s", __func__); if (report == -1) { init_iterator (iterator, "SELECT NULL WHERE false;"); return 0; } if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; apply_overrides = filter_term_apply_overrides (filter ? filter : get->filter); dynamic_severity = setting_dynamic_severity_int (); if (manage_cert_loaded ()) actual_columns = columns; else actual_columns = columns_no_cert; opts_tables = result_iterator_opts_table (apply_overrides, dynamic_severity); extra_tables = g_strdup_printf (" LEFT OUTER JOIN nvts" " ON results.nvt = nvts.oid %s," " LATERAL %s AS lateral_new_severity", opts_tables, result_iterator_lateral (apply_overrides, dynamic_severity)); g_free (opts_tables); extra_where = results_extra_where (get->trash, report, host, apply_overrides, dynamic_severity, filter ? filter : get->filter, NULL); extra_where_single = results_extra_where (get->trash, report, host, apply_overrides, dynamic_severity, "min_qod=0", NULL); free (filter); ret = init_get_iterator2 (iterator, "result", get, /* SELECT columns. */ actual_columns, NULL, /* Filterable columns not in SELECT columns. */ NULL, NULL, filter_columns, 0, extra_tables, extra_where, extra_where_single, TRUE, report ? TRUE : FALSE, extra_order); g_free (extra_tables); g_free (extra_where); g_free (extra_where_single); g_debug ("%s: done", __func__); return ret; } /** * @brief Count the number of results. * * @param[in] get GET params. * @param[in] report Report to limit results to. * @param[in] host Host to limit results to. * * @return Total number of results in filtered set. */ int result_count (const get_data_t *get, report_t report, const char* host) { static const char *filter_columns[] = RESULT_ITERATOR_FILTER_COLUMNS; static column_t columns[] = RESULT_ITERATOR_COLUMNS; static column_t columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT; int ret; gchar *filter, *extra_tables, *extra_where, *opts_tables; int apply_overrides, dynamic_severity; if (report == -1) return 0; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; apply_overrides = filter_term_apply_overrides (filter ? filter : get->filter); dynamic_severity = setting_dynamic_severity_int (); opts_tables = result_iterator_opts_table (apply_overrides, dynamic_severity); extra_tables = g_strdup_printf (" LEFT OUTER JOIN nvts" " ON results.nvt = nvts.oid %s," " LATERAL %s AS lateral_new_severity", opts_tables, result_iterator_lateral (apply_overrides, dynamic_severity)); g_free (opts_tables); extra_where = results_extra_where (get->trash, report, host, apply_overrides, dynamic_severity, filter ? filter : get->filter, NULL); ret = count ("result", get, manage_cert_loaded () ? columns : columns_no_cert, manage_cert_loaded () ? columns : columns_no_cert, filter_columns, 0, extra_tables, extra_where, TRUE); g_free (extra_tables); g_free (extra_where); return ret; } /** * @brief Get the result from a result iterator. * * @param[in] iterator Iterator. * * @return The result. */ result_t result_iterator_result (iterator_t* iterator) { if (iterator->done) return 0; return (result_t) iterator_int64 (iterator, 0); } /** * @brief Get the host from a result iterator. * * @param[in] iterator Iterator. * * @return The host of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_host, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the port from a result iterator. * * @param[in] iterator Iterator. * * @return The port of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_port, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the NVT OID from a result iterator. * * @param[in] iterator Iterator. * * @return The NVT OID of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the original type from a result iterator. * * This is the column 'type'. * * @param[in] iterator Iterator. * * @return The original type of the result. Caller must only use before calling * cleanup_iterator. */ static DEF_ACCESS (result_iterator_original_type, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the type from a result iterator. * * This is the overridden type. * * @param[in] iterator Iterator. * * @return The type of the result. Caller must only use before calling * cleanup_iterator. */ static const char* result_iterator_type (iterator_t *iterator) { if (iterator->done) return NULL; /* new_type */ return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the descr from a result iterator. * * @param[in] iterator Iterator. * * @return The descr of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_descr, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the task from a result iterator. * * @param[in] iterator Iterator. * * @return The task associated with the result, or 0 on error. */ task_t result_iterator_task (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Get the report from a result iterator. * * @param[in] iterator Iterator. * * @return The report associated with the result, or 0 on error. */ report_t result_iterator_report (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7); } /** * @brief Get the NVT CVSS base value from a result iterator. * * @param[in] iterator Iterator. * * @return The CVSS base of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_cvss_base, GET_ITERATOR_COLUMN_COUNT + 8); /** * @brief Get the NVT version used during the scan from a result iterator. * * @param[in] iterator Iterator. * * @return The version of NVT used by the scan that produced the result. * Caller must only use before calling cleanup_iterator. */ const char* result_iterator_scan_nvt_version (iterator_t *iterator) { const char* ret; if (iterator->done) return NULL; /* nvt_version */ ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9); return ret ? ret : ""; } /** * @brief Get the original severity from a result iterator. * * This is the original severity without overrides. * * @param[in] iterator Iterator. * * @return The original severity of the result. Caller must only use before * calling cleanup_iterator. */ const char* result_iterator_original_severity (iterator_t *iterator) { const char* ret; if (iterator->done) return NULL; /* severity */ ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10); return ret ? ret : ""; } /** * @brief Get the original severity/threat level from a result iterator. * * This is the original level without overrides. * * @param[in] iterator Iterator. * * @return The original threat level of the result. Caller must only use before * calling cleanup_iterator. */ const char* result_iterator_original_level (iterator_t *iterator) { double severity; const char* ret; if (iterator->done) return NULL; if (iterator_null (iterator, GET_ITERATOR_COLUMN_COUNT + 10)) return NULL; /* severity */ severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 10); ret = severity_to_level (severity, 0); return ret ? ret : ""; } /** * @brief Get the severity from a result iterator. * * This is the the overridden severity. * * @param[in] iterator Iterator. * * @return The severity of the result. Caller must only use before calling * cleanup_iterator. */ const char* result_iterator_severity (iterator_t *iterator) { const char* ret; if (iterator->done) return NULL; /* new_severity */ ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11); return ret ? ret : ""; } /** * @brief Get the severity from a result iterator as double. * * This is the the overridden severity. * * @param[in] iterator Iterator. * * @return The severity of the result. Caller must only use before calling * cleanup_iterator. */ double result_iterator_severity_double (iterator_t *iterator) { if (iterator->done) return 0.0; return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 11); } /** * @brief Get the severity/threat level from a result iterator. * * This is the the overridden level. * * @param[in] iterator Iterator. * * @return The threat level of the result. Caller must only use before * calling cleanup_iterator. */ const char* result_iterator_level (iterator_t *iterator) { double severity; const char* ret; if (iterator->done) return ""; /* new_severity */ if (iterator_null (iterator, GET_ITERATOR_COLUMN_COUNT + 11)) return ""; severity = iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 11); ret = severity_to_level (severity, 0); return ret ? ret : ""; } /** * @brief Get the solution type from a result iterator. * * @param[in] iterator Iterator. * * @return The solution type of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_solution_type, GET_ITERATOR_COLUMN_COUNT + 15); /** * @brief Get the qod from a result iterator. * * @param[in] iterator Iterator. * * @return The qod of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_qod, GET_ITERATOR_COLUMN_COUNT + 16); /** * @brief Get the qod_type from a result iterator. * * @param[in] iterator Iterator. * * @return The qod type of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_qod_type, GET_ITERATOR_COLUMN_COUNT + 17); /** * @brief Get the host from a result iterator. * * @param[in] iterator Iterator. * * @return The host of the result. Caller must only use before calling * cleanup_iterator. */ DEF_ACCESS (result_iterator_hostname, GET_ITERATOR_COLUMN_COUNT + 18); /** * @brief Get the path from a result iterator. * * @param[in] iterator Iterator. * * @return The path of the result. Caller must only use before * calling cleanup_iterator. */ DEF_ACCESS (result_iterator_path, GET_ITERATOR_COLUMN_COUNT + 21); /** * @brief Get the asset host ID from a result iterator. * * @param[in] iterator Iterator. * * @return The ID of the asset host. Caller must only use before * calling cleanup_iterator. */ DEF_ACCESS (result_iterator_asset_host_id, GET_ITERATOR_COLUMN_COUNT + 22); /** * @brief Get whether notes may exist from a result iterator. * * @param[in] iterator Iterator. * * @return 1 if notes may exist, else 0. */ int result_iterator_may_have_notes (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 23); } /** * @brief Get whether overrides may exist from a result iterator. * * @param[in] iterator Iterator. * * @return 1 if overrides may exist, else 0. */ int result_iterator_may_have_overrides (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 24); } /** * @brief Get whether tickets may exist from a result iterator. * * @param[in] iterator Iterator. * * @return 1 if notes may exist, else 0. */ int result_iterator_may_have_tickets (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 25); } /** * @brief Get the NVT summary from a result iterator. * * @param[in] iterator Iterator. * * @return The summary of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_summary, GET_ITERATOR_COLUMN_COUNT + 27); /** * @brief Get the NVT insight from a result iterator. * * @param[in] iterator Iterator. * * @return The insight of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_insight, GET_ITERATOR_COLUMN_COUNT + 28); /** * @brief Get the NVT affected from a result iterator. * * @param[in] iterator Iterator. * * @return The affected of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_affected, GET_ITERATOR_COLUMN_COUNT + 29); /** * @brief Get the NVT impact from a result iterator. * * @param[in] iterator Iterator. * * @return Impact text of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_impact, GET_ITERATOR_COLUMN_COUNT + 30); /** * @brief Get the NVT solution from a result iterator. * * @param[in] iterator Iterator. * * @return The solution of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_solution, GET_ITERATOR_COLUMN_COUNT + 31); /** * @brief Get the NVT detection from a result iterator. * * @param[in] iterator Iterator. * * @return The detection of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_detection, GET_ITERATOR_COLUMN_COUNT + 32); /** * @brief Get the NVT family from a result iterator. * * @param[in] iterator Iterator. * * @return The family of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_family, GET_ITERATOR_COLUMN_COUNT + 33); /** * @brief Get the NVT tags from a result iterator. * * @param[in] iterator Iterator. * * @return The tags of the NVT that produced the result, or NULL on error. */ DEF_ACCESS (result_iterator_nvt_tag, GET_ITERATOR_COLUMN_COUNT + 34); /** * @brief Get CERT-BUNDs from a result iterator. * * @param[in] iterator Iterator. * * @return CERT-BUND names if any, else NULL. */ gchar ** result_iterator_cert_bunds (iterator_t* iterator) { if (iterator->done) return 0; return iterator_array (iterator, GET_ITERATOR_COLUMN_COUNT + 35); } /** * @brief Get DFN-CERTs from a result iterator. * * @param[in] iterator Iterator. * * @return DFN-CERT names if any, else NULL. */ gchar ** result_iterator_dfn_certs (iterator_t* iterator) { if (iterator->done) return 0; return iterator_array (iterator, GET_ITERATOR_COLUMN_COUNT + 36); } /** * @brief Get the NVT name from a result iterator. * * @param[in] iterator Iterator. * * @return The name of the NVT that produced the result, or NULL on error. */ const char* result_iterator_nvt_name (iterator_t *iterator) { return get_iterator_name (iterator); } /** * @brief Get the NVT solution_type from a result iterator. * * @param[in] iterator Iterator. * * @return The solution_type of the NVT that produced the result, * or NULL on error. */ const char* result_iterator_nvt_solution_type (iterator_t *iterator) { return result_iterator_solution_type (iterator); } /** * @brief Get the NVT solution_method from a result iterator. * * @param[in] iterator Iterator. * * @return The solution_method of the NVT that produced the result, * or NULL on error. */ const char* result_iterator_nvt_solution_method (iterator_t *iterator) { /* When we used a cache this was never added to the cache. */ return NULL; } /** * @brief Append an NVT's references to an XML string buffer. * * @param[in] xml The buffer where to append to. * @param[in] oid The oid of the nvti object from where to collect the refs. * @param[in] first Marker for first element. */ void xml_append_nvt_refs (GString *xml, const char *oid, int *first) { nvti_t *nvti = lookup_nvti (oid); int i; if (!nvti) return; for (i = 0; i < nvti_vtref_len (nvti); i++) { vtref_t *ref; if (first && *first) { xml_string_append (xml, ""); *first = 0; } ref = nvti_vtref (nvti, i); xml_string_append (xml, "", vtref_type (ref), vtref_id (ref)); } } /** * @brief Check if the result_nvts are assigned to result * * @return 0 success, -1 error */ int cleanup_result_nvts () { iterator_t affected_iter; GArray *affected; int index; g_debug ("%s: Cleaning up results with wrong nvt ids", __func__); sql ("UPDATE results" " SET nvt = (SELECT oid FROM nvts WHERE name = nvt)," " result_nvt = NULL" " WHERE nvt IN (SELECT name FROM nvts WHERE name != oid);"); g_debug ("%s: Cleaning up result_nvts entries with wrong nvt ids", __func__); sql ("DELETE FROM result_nvts" " WHERE nvt IN (SELECT name FROM nvts WHERE name != oid);"); g_debug ("%s: Creating missing result_nvts entries", __func__); sql ("INSERT INTO result_nvts (nvt)" " SELECT DISTINCT nvt FROM results ON CONFLICT (nvt) DO NOTHING;"); // Get affected reports with overrides affected = g_array_new (TRUE, TRUE, sizeof (report_t)); init_iterator (&affected_iter, "SELECT DISTINCT report FROM results" " WHERE (result_nvt IS NULL" " OR report NOT IN" " (SELECT report FROM result_nvt_reports" " WHERE result_nvt IS NOT NULL))" " AND nvt IN (SELECT nvt FROM overrides);"); while (next (&affected_iter)) { report_t report; report = iterator_int64 (&affected_iter, 0); g_array_append_val (affected, report); } cleanup_iterator(&affected_iter); g_debug ("%s: Adding missing result_nvt values to results", __func__); sql ("UPDATE results" " SET result_nvt" " = (SELECT id FROM result_nvts" " WHERE result_nvts.nvt = results.nvt)" " WHERE result_nvt IS NULL"); g_debug ("%s: Cleaning up NULL result_nvt_reports entries", __func__); sql ("DELETE FROM result_nvt_reports WHERE result_nvt IS NULL;"); g_debug ("%s: Adding missing result_nvt_reports entries", __func__); sql ("INSERT INTO result_nvt_reports (report, result_nvt)" " SELECT DISTINCT report, result_nvts.id FROM results" " JOIN result_nvts ON result_nvts.nvt = results.nvt" " WHERE report NOT IN (SELECT report FROM result_nvt_reports" " WHERE result_nvt IS NOT NULL)"); // Re-cache affected reports with overrides for (index = 0; index < affected->len; index++) { report_t report; report = g_array_index (affected, report_t, index); g_debug ("%s: Updating cache of affected report %llu", __func__, report); report_cache_counts (report, 0, 1, NULL); } g_array_free (affected, TRUE); return 0; } /** * @brief Initialise a host iterator. * * @param[in] iterator Iterator. * @param[in] report Report whose hosts the iterator loops over. * @param[in] host Single host to iterate over. All hosts if NULL. * @param[in] report_host Single report host to iterate over. All if 0. */ void init_report_host_iterator (iterator_t* iterator, report_t report, const char *host, report_host_t report_host) { if (report) { if (report_host) init_iterator (iterator, "SELECT id, host, iso_time (start_time)," " iso_time (end_time), current_port, max_port, report," " (SELECT uuid FROM reports WHERE id = report)," " (SELECT uuid FROM hosts" " WHERE id = (SELECT host FROM host_identifiers" " WHERE source_type = 'Report Host'" " AND name = 'ip'" " AND source_id = (SELECT uuid" " FROM reports" " WHERE id = report)" " AND value = report_hosts.host" " LIMIT 1))" " FROM report_hosts WHERE id = %llu" " AND report = %llu" "%s%s%s" " ORDER BY order_inet (host);", report_host, report, host ? " AND host = '" : "", host ? host : "", host ? "'" : ""); else init_iterator (iterator, "SELECT id, host, iso_time (start_time)," " iso_time (end_time), current_port, max_port, report," " (SELECT uuid FROM reports WHERE id = report)," " (SELECT uuid FROM hosts" " WHERE id = (SELECT host FROM host_identifiers" " WHERE source_type = 'Report Host'" " AND name = 'ip'" " AND source_id = (SELECT uuid" " FROM reports" " WHERE id = report)" " AND value = report_hosts.host" " LIMIT 1))" " FROM report_hosts WHERE report = %llu" "%s%s%s" " ORDER BY order_inet (host);", report, host ? " AND host = '" : "", host ? host : "", host ? "'" : ""); } else { if (report_host) init_iterator (iterator, "SELECT id, host, iso_time (start_time)," " iso_time (end_time), current_port, max_port, report," " (SELECT uuid FROM reports WHERE id = report)," " ''" " FROM report_hosts WHERE id = %llu" "%s%s%s" " ORDER BY order_inet (host);", report_host, host ? " AND host = '" : "", host ? host : "", host ? "'" : ""); else init_iterator (iterator, "SELECT id, host, iso_time (start_time)," " iso_time (end_time), current_port, max_port, report," " (SELECT uuid FROM reports WHERE id = report)," " ''" " FROM report_hosts" "%s%s%s" " ORDER BY order_inet (host);", host ? " WHERE host = '" : "", host ? host : "", host ? "'" : ""); } } /** * @brief Get the report host from a host iterator. * * @param[in] iterator Iterator. * * @return Report host. */ static report_host_t host_iterator_report_host (iterator_t* iterator) { if (iterator->done) return 0; return (report_host_t) iterator_int64 (iterator, 0); } /** * @brief Get the host from a host iterator. * * @param[in] iterator Iterator. * * @return The host of the host. Caller must use only before calling * cleanup_iterator. */ DEF_ACCESS (host_iterator_host, 1); /** * @brief Get the start time from a host iterator. * * @param[in] iterator Iterator. * * @return The start time of the host. Caller must use only before calling * cleanup_iterator. */ DEF_ACCESS (host_iterator_start_time, 2); /** * @brief Get the end time from a host iterator. * * @param[in] iterator Iterator. * * @return The end time of the host. Caller must use only before calling * cleanup_iterator. */ DEF_ACCESS (host_iterator_end_time, 3); /** * @brief Get the current port from a host iterator. * * @param[in] iterator Iterator. * * @return Current port. */ int host_iterator_current_port (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, 4); return ret; } /** * @brief Get the max port from a host iterator. * * @param[in] iterator Iterator. * * @return Current port. */ int host_iterator_max_port (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, 5); return ret; } /** * @brief Get the asset UUID from a host iterator. * * @param[in] iterator Iterator. * * @return The UUID of the assset associate with the host. Caller must use * only before calling cleanup_iterator. */ static DEF_ACCESS (host_iterator_asset_uuid, 8); /** * @brief Initialise a report errors iterator. * * @param[in] iterator Iterator. * @param[in] report The report. */ void init_report_errors_iterator (iterator_t* iterator, report_t report) { if (report) init_iterator (iterator, "SELECT results.host, results.port, results.nvt," " results.description," " coalesce((SELECT name FROM nvts" " WHERE nvts.oid = results.nvt), '')," " coalesce((SELECT cvss_base FROM nvts" " WHERE nvts.oid = results.nvt), '')," " results.nvt_version, results.severity," " results.id" " FROM results" " WHERE results.type = 'Error Message'" " AND results.report = %llu", report); } /** * @brief Get the host from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The host of the report error message. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_host, 0); /** * @brief Get the port from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The port of the report error message. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_port, 1); /** * @brief Get the nvt oid from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The nvt of the report error message. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_nvt_oid, 2); /** * @brief Get the description from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The description of the report error message. Caller must use only * before calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_desc, 3); /** * @brief Get the nvt name from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The nvt of the report error message. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_nvt_name, 4); /** * @brief Get the nvt cvss base from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The nvt cvss base of the report error message. Caller must use only * before calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_nvt_cvss, 5); /** * @brief Get the nvt cvss base from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The nvt version at scan time of the report error message. * Caller must use only before calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_scan_nvt_version, 6); /** * @brief Get the nvt cvss base from a report error messages iterator. * * @param[in] iterator Iterator. * * @return The severity at scan time of the report error message. * Caller must use only before calling cleanup_iterator. */ static DEF_ACCESS (report_errors_iterator_severity, 7); /** * @brief Get the result from a report error messages iterator. * * @param[in] iterator Iterator. * * @return Result. */ static result_t report_errors_iterator_result (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, 8); } /** * @brief Initialise a report host details iterator. * * @param[in] iterator Iterator. * @param[in] report_host Report host whose details the iterator loops over. * All report_hosts if NULL. */ static void init_report_host_details_iterator (iterator_t* iterator, report_host_t report_host) { /* The 'detected_at' and 'detected_by' entries are filtered out of the final * reports as they are only used internally for product detection. */ init_iterator (iterator, "SELECT id, name, value, source_type, source_name," " source_description, NULL" " FROM report_host_details WHERE report_host = %llu" " AND NOT name IN ('detected_at', 'detected_by')" " AND NOT name LIKE 'detected_by@%%'" " UNION SELECT 0, 'Closed CVE', cve, 'openvasmd', oid," " nvts.name, cvss_base" " FROM nvts, report_host_details" " WHERE cve != ''" " AND family IN (" LSC_FAMILY_LIST ")" " AND nvts.oid = report_host_details.source_name" " AND report_host = %llu" " AND report_host_details.name = 'EXIT_CODE'" " AND report_host_details.value = 'EXIT_NOTVULN';", report_host, report_host); } /** * @brief Get the name from a report host details iterator. * * @param[in] iterator Iterator. * * @return The name of the report host detail. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_name, 1); /** * @brief Get the value from a report host details iterator. * * @param[in] iterator Iterator. * * @return The value of the report host detail. Caller must use only before * calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_value, 2); /** * @brief Get the source type from a report host details iterator. * * @param[in] iterator Iterator. * * @return The source type of the report host detail. Caller must use only * before calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_source_type, 3); /** * @brief Get the source name from a report host details iterator. * * @param[in] iterator Iterator. * * @return The source name of the report host detail. Caller must use only * before calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_source_name, 4); /** * @brief Get the source description from a report host details iterator. * * @param[in] iterator Iterator. * * @return The source description of the report host detail. Caller must use * only before calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_source_desc, 5); /** * @brief Get the extra info from a report host details iterator. * * @param[in] iterator Iterator. * * @return Extra info of the report host detail. Caller must use * only before calling cleanup_iterator. */ static DEF_ACCESS (report_host_details_iterator_extra, 6); /** * @brief Set the end time of a task. * * @param[in] task Task. * @param[in] time New time. Freed before return. If NULL, clear end time. */ void set_task_end_time (task_t task, char* time) { if (time) { sql ("UPDATE tasks SET end_time = %i WHERE id = %llu;", parse_iso_time (time), task); free (time); } else sql ("UPDATE tasks SET end_time = NULL WHERE id = %llu;", task); } /** * @brief Set the end time of a task. * * @param[in] task Task. * @param[in] time New time. Freed before return. If NULL, clear end time. */ void set_task_end_time_epoch (task_t task, time_t time) { if (time) sql ("UPDATE tasks SET end_time = %i WHERE id = %llu;", time, task); else sql ("UPDATE tasks SET end_time = NULL WHERE id = %llu;", task); } /** * @brief Get the start time of a scan. * * @param[in] report The report associated with the scan. * * @return Start time of scan, in a newly allocated string. */ static char* scan_start_time (report_t report) { char *time = sql_string ("SELECT iso_time (start_time)" " FROM reports WHERE id = %llu;", report); return time ? time : g_strdup (""); } /** * @brief Get the start time of a scan, in seconds since the epoch. * * @param[in] report The report associated with the scan. * * @return Start time of scan, in seconds. */ int scan_start_time_epoch (report_t report) { return sql_int ("SELECT start_time FROM reports WHERE id = %llu;", report); } /** * @brief Get the start time of a scan. * * @param[in] uuid The report associated with the scan. * * @return Start time of scan, in a newly allocated string. */ char* scan_start_time_uuid (const char *uuid) { char *time, *quoted_uuid; quoted_uuid = sql_quote (uuid); time = sql_string ("SELECT iso_time (start_time)" " FROM reports WHERE uuid = '%s';", quoted_uuid); return time ? time : g_strdup (""); } /** * @brief Set the start time of a scan. * * @param[in] report The report associated with the scan. * @param[in] timestamp Start time. Epoch format. */ void set_scan_start_time_epoch (report_t report, time_t timestamp) { sql ("UPDATE reports SET start_time = %i WHERE id = %llu;", timestamp, report); } /** * @brief Set the start time of a scan. * * @param[in] report The report associated with the scan. * @param[in] timestamp Start time. In UTC ctime format. */ void set_scan_start_time_ctime (report_t report, const char* timestamp) { sql ("UPDATE reports SET start_time = %i WHERE id = %llu;", parse_utc_ctime (timestamp), report); } /** * @brief Get the end time of a scan. * * @param[in] report The report associated with the scan. * * @return End time of scan, in a newly allocated string. */ static char* scan_end_time (report_t report) { char *time = sql_string ("SELECT iso_time (end_time)" " FROM reports WHERE id = %llu;", report); return time ? time : g_strdup (""); } /** * @brief Get the end time of a scan. * * @param[in] uuid The report associated with the scan. * * @return End time of scan, in a newly allocated string. */ char* scan_end_time_uuid (const char *uuid) { char *time, *quoted_uuid; quoted_uuid = sql_quote (uuid); time = sql_string ("SELECT iso_time (end_time)" " FROM reports WHERE uuid = '%s';", quoted_uuid); return time ? time : g_strdup (""); } /** * @brief Set the end time of a scan. * * @param[in] report The report associated with the scan. * @param[in] timestamp End time. Epoch format. */ void set_scan_end_time_epoch (report_t report, time_t timestamp) { if (timestamp) sql ("UPDATE reports SET end_time = %i WHERE id = %llu;", timestamp, report); } /** * @brief Set the end time of a scan. * * @param[in] report The report associated with the scan. * @param[in] timestamp End time. ISO format. If NULL, clear end time. */ void set_scan_end_time (report_t report, const char* timestamp) { if (timestamp) sql ("UPDATE reports SET end_time = %i WHERE id = %llu;", parse_iso_time (timestamp), report); else sql ("UPDATE reports SET end_time = NULL WHERE id = %llu;", report); } /** * @brief Set the end time of a scan. * * @param[in] report The report associated with the scan. * @param[in] timestamp End time. In UTC ctime format. If NULL, clear end * time. */ void set_scan_end_time_ctime (report_t report, const char* timestamp) { if (timestamp) sql ("UPDATE reports SET end_time = %i WHERE id = %llu;", parse_utc_ctime (timestamp), report); else sql ("UPDATE reports SET end_time = NULL WHERE id = %llu;", report); } /** * @brief Get the end time of a scanned host. * * @param[in] report Report associated with the scan. * @param[in] host Host. * * @return End time. */ int scan_host_end_time (report_t report, const char* host) { gchar *quoted_host; int ret; quoted_host = sql_quote (host); ret = sql_int ("SELECT end_time FROM report_hosts" " WHERE report = %llu AND host = '%s';", report, quoted_host); g_free (quoted_host); return ret; } /** * @brief Set the end time of a scanned host. * * @param[in] report Report associated with the scan. * @param[in] host Host. * @param[in] timestamp End time. ISO format. */ void set_scan_host_end_time (report_t report, const char* host, const char* timestamp) { gchar *quoted_host; quoted_host = sql_quote (host); if (sql_int ("SELECT COUNT(*) FROM report_hosts" " WHERE report = %llu AND host = '%s';", report, quoted_host)) sql ("UPDATE report_hosts SET end_time = %i" " WHERE report = %llu AND host = '%s';", parse_iso_time (timestamp), report, quoted_host); else manage_report_host_add (report, host, 0, parse_iso_time (timestamp)); g_free (quoted_host); } /** * @brief Set the end time of a scanned host. * * @param[in] report Report associated with the scan. * @param[in] host Host. * @param[in] timestamp End time. In UTC ctime format. */ void set_scan_host_end_time_ctime (report_t report, const char* host, const char* timestamp) { gchar *quoted_host; quoted_host = sql_quote (host); if (sql_int ("SELECT COUNT(*) FROM report_hosts" " WHERE report = %llu AND host = '%s';", report, quoted_host)) sql ("UPDATE report_hosts SET end_time = %i" " WHERE report = %llu AND host = '%s';", parse_utc_ctime (timestamp), report, quoted_host); else manage_report_host_add (report, host, 0, parse_utc_ctime (timestamp)); g_free (quoted_host); } /** * @brief Set the start time of a scanned host. * * @param[in] report Report associated with the scan. * @param[in] host Host. * @param[in] timestamp Start time. In UTC ctime format. */ void set_scan_host_start_time_ctime (report_t report, const char* host, const char* timestamp) { gchar *quoted_host; quoted_host = sql_quote (host); if (sql_int ("SELECT COUNT(*) FROM report_hosts" " WHERE report = %llu AND host = '%s';", report, quoted_host)) sql ("UPDATE report_hosts SET start_time = %i" " WHERE report = %llu AND host = '%s';", parse_utc_ctime (timestamp), report, quoted_host); else manage_report_host_add (report, host, parse_utc_ctime (timestamp), 0); g_free (quoted_host); } /** * @brief Get the timestamp of a report. * * @todo Lacks permission check. Caller contexts all have permission * checks before calling this so it's safe. Rework callers so * they pass report_t instead of UUID string. * * @param[in] report_id UUID of report. * @param[out] timestamp Timestamp on success. Caller must free. * * @return 0 on success, -1 on error. */ int report_timestamp (const char* report_id, gchar** timestamp) { const char* stamp; time_t time = sql_int ("SELECT date FROM reports where uuid = '%s';", report_id); stamp = iso_time (&time); if (stamp == NULL) return -1; *timestamp = g_strdup (stamp); return 0; } /** * @brief Return the run status of the scan associated with a report. * * @param[in] report Report. * @param[out] status Scan run status. * * @return 0 on success, -1 on error. */ static int report_scan_run_status (report_t report, task_status_t* status) { *status = sql_int ("SELECT scan_run_status FROM reports" " WHERE reports.id = %llu;", report); return 0; } /** * @brief Return the run status of the scan associated with a report. * * @param[in] report Report. * @param[out] status Scan run status. * * @return 0 on success, -1 on error. */ int set_report_scan_run_status (report_t report, task_status_t status) { sql ("UPDATE reports SET scan_run_status = %u WHERE id = %llu;", status, report); if (setting_auto_cache_rebuild_int ()) report_cache_counts (report, 0, 0, NULL); return 0; } /** * @brief Get the result severity counts for a report. * * @param[in] report Report. * @param[in] host Host to which to limit the count. NULL to allow all. * @param[in] get Report "get" data to retrieve filter info from. * @param[out] severity_data The severity data struct to store counts in. * @param[out] filtered_severity_data The severity data struct to store counts in. */ static void report_severity_data (report_t report, const char *host, const get_data_t* get, severity_data_t* severity_data, severity_data_t* filtered_severity_data) { iterator_t results; gchar *filter; int apply_overrides; if (report == 0) return; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); } else filter = NULL; apply_overrides = filter_term_apply_overrides (filter ? filter : get->filter); if (severity_data) { get_data_t *get_all; get_all = report_results_get_data (1, -1, apply_overrides, 0); ignore_max_rows_per_page = 1; init_result_get_iterator_severity (&results, get_all, report, host, NULL); ignore_max_rows_per_page = 0; while (next (&results)) { double severity; if (results.done) severity = 0.0; else severity = iterator_double (&results, 0); severity_data_add (severity_data, severity); } cleanup_iterator (&results); get_data_reset (get_all); free (get_all); } if (filtered_severity_data) { get_data_t get_filtered; memset (&get_filtered, 0, sizeof (get_data_t)); get_filtered.filt_id = get->filt_id; get_filtered.filter = get->filter; get_filtered.type = get->type; get_filtered.ignore_pagination = 1; ignore_max_rows_per_page = 1; init_result_get_iterator_severity (&results, &get_filtered, report, host, NULL); ignore_max_rows_per_page = 0; while (next (&results)) { double severity; if (results.done) severity = 0.0; else severity = iterator_double (&results, 0); severity_data_add (filtered_severity_data, severity); } cleanup_iterator (&results); } } /** * @brief Get the message counts for a report given the UUID. * * @todo Lacks permission check. Caller contexts all have permission * checks before calling this so it's safe. Rework callers to * use report_counts_id instead. * * @param[in] report_id ID of report. * @param[out] holes Number of hole messages. * @param[out] infos Number of info messages. * @param[out] logs Number of log messages. * @param[out] warnings Number of warning messages. * @param[out] false_positives Number of false positives. * @param[out] severity Maximum severity score. * @param[in] override Whether to override the threat. * @param[in] min_qod Min QOD. * * @return 0 on success, -1 on error. */ int report_counts (const char* report_id, int* holes, int* infos, int* logs, int* warnings, int* false_positives, double* severity, int override, int min_qod) { report_t report; int ret; get_data_t *get; // TODO Wrap in transaction. if (find_report_with_permission (report_id, &report, "get_reports")) return -1; // TODO Check if report was found. get = report_results_get_data (1, -1, override, min_qod); ret = report_counts_id (report, holes, infos, logs, warnings, false_positives, severity, get, NULL); get_data_reset (get); free (get); return ret; } /** * @brief Test if a counts cache exists for a report and the current user. * @param[in] report The report to check. * @param[in] override Whether to check for overridden results. * @param[in] min_qod Minimum QoD of results to count. * * @return 1 if cache exists, 0 otherwise. */ static int report_counts_cache_exists (report_t report, int override, int min_qod) { return sql_int ("SELECT EXISTS (SELECT * FROM report_counts" " WHERE report = %llu" " AND override = %d" " AND \"user\" = (SELECT id FROM users" " WHERE users.uuid = '%s')" " AND min_qod = %d" " AND (end_time = 0 OR end_time >= m_now ()));", report, override, current_credentials.uuid, min_qod); } /** * @brief Get cached result counts for a report and the current user. * * @param[in] report The report to get counts from. * @param[in] override Whether to get overridden results. * @param[in] min_qod Minimum QoD of results to count. * @param[out] data The severity_data_t to save counts in. */ static void report_counts_from_cache (report_t report, int override, int min_qod, severity_data_t* data) { iterator_t iterator; init_iterator (&iterator, "SELECT severity, count FROM report_counts" " WHERE report = %llu" " AND override = %i" " AND \"user\" = (SELECT id FROM users" " WHERE users.uuid = '%s')" " AND min_qod = %d" " AND (end_time = 0 OR end_time >= m_now ());", report, override, current_credentials.uuid, min_qod); while (next (&iterator)) { severity_data_add_count (data, iterator_double (&iterator, 0), iterator_int (&iterator, 1)); } cleanup_iterator (&iterator); } /** * @brief Cache the message counts for a report. * * @param[in] report Report. * @param[in] override Whether overrides were applied to the results. * @param[in] min_qod The minimum QoD of the results. * @param[in] data Severity data struct containing the message counts. * * @return 0 if successful, 1 gave up, -1 error (see sql_giveup). */ static int cache_report_counts (report_t report, int override, int min_qod, severity_data_t* data) { int i, ret; double severity; int end_time; /* Try cache results. */ ret = sql_giveup ("DELETE FROM report_counts" " WHERE report = %llu" " AND override = %i" " AND min_qod = %i" " AND \"user\" = (SELECT id FROM users" " WHERE users.uuid = '%s');", report, override, min_qod, current_credentials.uuid); if (ret) { return ret; } if (data->total == 0) { /* Create dummy entry for empty reports */ ret = sql_giveup ("INSERT INTO report_counts" " (report, \"user\", override, min_qod, severity," " count, end_time)" " VALUES (%llu," " (SELECT id FROM users" " WHERE users.uuid = '%s')," " %d, %d, " G_STRINGIFY (SEVERITY_MISSING) "," " 0, 0);", report, current_credentials.uuid, override, min_qod); if (ret) { return ret; } } else { GString *insert; int first; i = 0; if (override) end_time = sql_int ("SELECT coalesce(min(end_time), 0)" " FROM overrides, results" " WHERE overrides.nvt = results.nvt" " AND results.report = %llu" " AND overrides.end_time >= m_now ();", report); else end_time = 0; severity = severity_data_value (i); insert = g_string_new ("INSERT INTO report_counts" " (report, \"user\", override, min_qod," " severity, count, end_time)" " VALUES"); first = 1; while (severity <= (data->max + (1.0 / SEVERITY_SUBDIVISIONS / SEVERITY_SUBDIVISIONS)) && severity != SEVERITY_MISSING) { if (data->counts[i] > 0) { g_string_append_printf (insert, "%s (%llu," " (SELECT id FROM users" " WHERE users.uuid = '%s')," " %d, %d, %1.1f, %d, %d)", first == 1 ? "" : ",", report, current_credentials.uuid, override, min_qod, severity, data->counts[i], end_time); first = 0; } i++; severity = severity_data_value (i); } if (i) { g_string_append_printf (insert, ";"); ret = sql_giveup (insert->str); if (ret) { g_string_free (insert, TRUE); return ret; } } g_string_free (insert, TRUE); } return 0; } /** * @brief Get the message counts for a report. * * @param[in] report Report. * @param[out] holes Number of hole messages. * @param[out] infos Number of info messages. * @param[out] logs Number of log messages. * @param[out] warnings Number of warning messages. * @param[out] false_positives Number of false positive messages. * @param[out] severity Maximum severity of the report. * @param[in] get Get data. * @param[in] host Host to which to limit the count. * @param[out] filtered_holes Number of hole messages after filtering. * @param[out] filtered_infos Number of info messages after filtering. * @param[out] filtered_logs Number of log messages after filtering. * @param[out] filtered_warnings Number of warning messages after filtering. * @param[out] filtered_false_positives Number of false positive messages after * filtering. * @param[out] filtered_severity Maximum severity after filtering. * * @return 0 on success, -1 on error. */ static int report_counts_id_full (report_t report, int* holes, int* infos, int* logs, int* warnings, int* false_positives, double* severity, const get_data_t* get, const char* host, int* filtered_holes, int* filtered_infos, int* filtered_logs, int* filtered_warnings, int* filtered_false_positives, double* filtered_severity) { const char *filter; keyword_t **point; array_t *split; int filter_cacheable, unfiltered_requested, filtered_requested, cache_exists; int override, min_qod_int; severity_data_t severity_data, filtered_severity_data; unfiltered_requested = (holes || warnings || infos || logs || false_positives || severity); filtered_requested = (filtered_holes || filtered_warnings || filtered_infos || filtered_logs || filtered_false_positives || filtered_severity); if (current_credentials.uuid == NULL || strcmp (current_credentials.uuid, "") == 0) g_warning ("%s: called by NULL or dummy user", __func__); if (get->filt_id && strlen (get->filt_id) && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) { return -1; } } else { filter = get->filter; } filter_cacheable = TRUE; override = 0; min_qod_int = MIN_QOD_DEFAULT; if (filter == NULL) filter = ""; split = split_filter (filter); point = (keyword_t**) split->pdata; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column == NULL) { filter_cacheable = FALSE; } else if (strcasecmp (keyword->column, "first") == 0 || strcasecmp (keyword->column, "rows") == 0 || strcasecmp (keyword->column, "sort") == 0 || strcasecmp (keyword->column, "sort-reverse") == 0 || strcasecmp (keyword->column, "notes") == 0 || strcasecmp (keyword->column, "overrides") == 0) { // ignore } else if (strcasecmp (keyword->column, "apply_overrides") == 0) { if (keyword->string && strcmp (keyword->string, "") && strcmp (keyword->string, "0")) override = 1; } else if (strcasecmp (keyword->column, "min_qod") == 0) { if (keyword->string == NULL || sscanf (keyword->string, "%d", &min_qod_int) != 1) min_qod_int = MIN_QOD_DEFAULT; } else { filter_cacheable = FALSE; } point++; } filter_free (split); cache_exists = filter_cacheable && report_counts_cache_exists (report, override, min_qod_int); init_severity_data (&severity_data); init_severity_data (&filtered_severity_data); if (cache_exists && filter_cacheable) { /* Get unfiltered counts from cache. */ if (unfiltered_requested) report_counts_from_cache (report, override, min_qod_int, &severity_data); if (filtered_requested) report_counts_from_cache (report, override, min_qod_int, &filtered_severity_data); } else { /* Recalculate. */ report_severity_data (report, host, get, unfiltered_requested ? &severity_data : NULL, filtered_requested ? &filtered_severity_data : NULL); } severity_data_level_counts (&severity_data, NULL, false_positives, logs, infos, warnings, holes); severity_data_level_counts (&filtered_severity_data, NULL, filtered_false_positives, filtered_logs, filtered_infos, filtered_warnings, filtered_holes); if (severity) *severity = severity_data.max; if (filtered_severity && filtered_requested) *filtered_severity = filtered_severity_data.max; if (filter_cacheable && !cache_exists) { if (unfiltered_requested) cache_report_counts (report, override, 0, &severity_data); if (filtered_requested) cache_report_counts (report, override, min_qod_int, &filtered_severity_data); } cleanup_severity_data (&severity_data); cleanup_severity_data (&filtered_severity_data); return 0; } /** * @brief Get only the filtered message counts for a report. * * @param[in] report Report. * @param[out] holes Number of hole messages. * @param[out] infos Number of info messages. * @param[out] logs Number of log messages. * @param[out] warnings Number of warning messages. * @param[out] false_positives Number of false positive messages. * @param[out] severity Maximum severity score. * @param[in] get Get data. * @param[in] host Host to which to limit the count. NULL to allow all. * * @return 0 on success, -1 on error. */ int report_counts_id (report_t report, int* holes, int* infos, int* logs, int* warnings, int* false_positives, double* severity, const get_data_t *get, const char *host) { int ret; ret = report_counts_id_full (report, NULL, NULL, NULL, NULL, NULL, NULL, get, host, holes, infos, logs, warnings, false_positives, severity); return ret; } /** * @brief Get the maximum severity of a report. * * @param[in] report Report. * @param[in] overrides Whether to apply overrides. * @param[in] min_qod Minimum QoD of results to count. * * @return Severity score of the report. */ double report_severity (report_t report, int overrides, int min_qod) { double severity; iterator_t iterator; init_iterator (&iterator, "SELECT max(severity)" " FROM report_counts" " WHERE report = %llu" " AND override = %d" " AND \"user\" = (SELECT id FROM users WHERE uuid = '%s')" " AND min_qod = %d" " AND (end_time = 0 or end_time >= m_now ());", report, overrides, current_credentials.uuid, min_qod); if (next (&iterator) && (iterator_null (&iterator, 0) == 0)) { g_debug ("%s: max(severity)=%s", __func__, iterator_string (&iterator, 0)); severity = iterator_double (&iterator, 0); } else { g_debug ("%s: could not get max from cache", __func__); get_data_t *get = report_results_get_data (1, -1, overrides, min_qod); report_counts_id (report, NULL, NULL, NULL, NULL, NULL, &severity, get, NULL); get_data_reset (get); free (get); } cleanup_iterator (&iterator); return severity; } /** * @brief Delete a report. * * It's up to the caller to provide the transaction. * * @param[in] report Report. * * @return 0 success, 2 report is in use, -1 error. */ int delete_report_internal (report_t report) { task_t task; if (sql_int ("SELECT count(*) FROM reports WHERE id = %llu" " AND (scan_run_status = %u OR scan_run_status = %u" " OR scan_run_status = %u OR scan_run_status = %u" " OR scan_run_status = %u);", report, TASK_STATUS_RUNNING, TASK_STATUS_QUEUED, TASK_STATUS_REQUESTED, TASK_STATUS_DELETE_REQUESTED, TASK_STATUS_DELETE_ULTIMATE_REQUESTED, TASK_STATUS_STOP_REQUESTED, TASK_STATUS_STOP_WAITING)) return 2; /* This needs to have exclusive access to reports because otherwise at this * point another process (like a RESUME_TASK handler) could store the report * ID and then start trying to access that report after we've deleted it. */ if (report_task (report, &task)) return -1; /* Remove the report data. */ sql ("DELETE FROM report_host_details WHERE report_host IN" " (SELECT id FROM report_hosts WHERE report = %llu);", report); sql ("DELETE FROM report_hosts WHERE report = %llu;", report); sql ("DELETE FROM tag_resources" " WHERE resource_type = 'result'" " AND resource IN" " (SELECT id FROM results WHERE report = %llu);", report); sql ("DELETE FROM tag_resources_trash" " WHERE resource_type = 'result'" " AND resource IN" " (SELECT id FROM results WHERE report = %llu);", report); sql ("DELETE FROM results WHERE report = %llu;", report); sql ("DELETE FROM results_trash WHERE report = %llu;", report); sql ("DELETE FROM tag_resources" " WHERE resource_type = 'report'" " AND resource = %llu;", report); sql ("DELETE FROM tag_resources_trash" " WHERE resource_type = 'report'" " AND resource = %llu;", report); sql ("DELETE FROM report_counts WHERE report = %llu;", report); sql ("DELETE FROM result_nvt_reports WHERE report = %llu;", report); sql ("DELETE FROM reports WHERE id = %llu;", report); /* Adjust permissions. */ permissions_set_orphans ("report", report, LOCATION_TABLE); tags_remove_resource ("report", report, LOCATION_TABLE); tickets_remove_report (report); /* Update the task state. */ switch (sql_int64 (&report, "SELECT max (id) FROM reports WHERE task = %llu", task)) { case 0: if (report) { task_status_t status; if (report_scan_run_status (report, &status)) return -1; sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;", status, task); } else sql ("UPDATE tasks SET run_status = %u WHERE id = %llu;", TASK_STATUS_NEW, task); break; case 1: /* Too few rows in result of query. */ break; default: /* Programming error. */ assert (0); case -1: return -1; break; } return 0; } /** * @brief Delete a report. * * @param[in] report_id UUID of report. * @param[in] dummy Dummy arg to match other delete functions. * * @return 0 success, 2 failed to find report, 99 permission denied, -1 error. */ int delete_report (const char *report_id, int dummy) { report_t report; int ret, lock_ret, lock_retries; sql_begin_immediate (); /* This prevents other processes (in particular a RESUME_TASK) from getting * a reference to the report ID, and then using that reference to try access * the deleted report. * * If the report is running already then delete_report_internal will * ROLLBACK. */ lock_retries = LOCK_RETRIES; lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); while ((lock_ret == 0) && (lock_retries > 0)) { sleep(LOCK_RETRY_DELAY); lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); lock_retries--; } if (lock_ret == 0) { sql_rollback (); return -1; } if (acl_user_may ("delete_report") == 0) { sql_rollback (); return 99; } report = 0; if (find_report_with_permission (report_id, &report, "delete_report")) { sql_rollback (); return -1; } if (report == 0) { if (find_trash_report_with_permission (report_id, &report, "delete_report")) { sql_rollback (); return -1; } if (report == 0) { sql_rollback (); return 2; } } ret = delete_report_internal (report); if (ret) { sql_rollback (); return ret; } sql_commit (); return 0; } /** * @brief Return the slave progress of a report. * * @param[in] report Report. * * @return Number of reports. */ static int report_slave_progress (report_t report) { return sql_int ("SELECT slave_progress FROM reports WHERE id = %llu;", report); } /** * @brief Set slave progress of a report. * * @param[in] report The report. * @param[in] progress The new progress value. * * @return 0 success. */ int set_report_slave_progress (report_t report, int progress) { sql ("UPDATE reports SET slave_progress = %i WHERE id = %llu;", progress, report); return 0; } /** * @brief Prepare a partial report for restarting the scan from the beginning. * * @param[in] report The report. */ void trim_report (report_t report) { /* Remove results for all hosts. */ sql ("DELETE FROM results WHERE id IN" " (SELECT results.id FROM results" " WHERE results.report = %llu);", report); /* Remove all hosts and host details. */ sql ("DELETE FROM report_host_details WHERE report_host IN" " (SELECT id FROM report_hosts WHERE report = %llu);", report); sql ("DELETE FROM report_hosts" " WHERE report = %llu;", report); /* Clear and rebuild counts cache */ if (setting_auto_cache_rebuild_int ()) report_cache_counts (report, 1, 1, NULL); else report_clear_count_cache (report, 1, 1, NULL); } /** * @brief Prepare a partial report for resumption of the scan. * * @param[in] report The report. */ void trim_partial_report (report_t report) { /* Remove results for partial hosts. */ sql ("DELETE FROM results WHERE id IN" " (SELECT results.id FROM results, report_hosts" " WHERE results.report = %llu" " AND report_hosts.report = %llu" " AND results.host = report_hosts.host" " AND report_hosts.end_time = 0);", report, report); /* Remove partial hosts and host details. */ sql ("DELETE FROM report_host_details WHERE report_host IN" " (SELECT report_hosts.id FROM report_hosts" " WHERE report_hosts.report = %llu" " AND report_hosts.end_time = 0);", report); sql ("DELETE FROM report_hosts" " WHERE report = %llu" " AND end_time is NULL;", report); /* Clear and rebuild counts cache */ if (setting_auto_cache_rebuild_int ()) report_cache_counts (report, 1, 1, NULL); else report_clear_count_cache (report, 1, 1, NULL); } /** * @brief Compares two textual port representations, sorting descending * @brief by severity * * @param[in] arg_one First threat level. * @param[in] arg_two Second threat level. * * @return 1, 0 or -1 if first given severity is less than, equal to or greater * than second. */ static gint compare_severity_desc (gconstpointer arg_one, gconstpointer arg_two) { double one_severity, two_severity; gchar *one = *((gchar**) arg_one); gchar *two = *((gchar**) arg_two); gint host; one += strlen (one) + 1; two += strlen (two) + 1; one_severity = g_strtod (one, NULL); two_severity = g_strtod (two, NULL); one += strlen (one) + 1; two += strlen (two) + 1; host = strcmp (one, two); if (host == 0) { if (one_severity > two_severity) return -1; else if (one_severity < two_severity) return 1; else { one = *((gchar**) arg_one); two = *((gchar**) arg_two); return strcmp (two, one); } } return host; } /** * @brief Compares two textual port representations, sorting descending * @brief by severity * * @param[in] arg_one First port. * @param[in] arg_two Second port. * * @return -1, 0 or 1 if first given severity is less than, equal to or greater * than second. */ static gint compare_severity_asc (gconstpointer arg_one, gconstpointer arg_two) { double one_severity, two_severity; gchar *one = *((gchar**) arg_one); gchar *two = *((gchar**) arg_two); gint host; one += strlen (one) + 1; two += strlen (two) + 1; one_severity = g_strtod (one, NULL); two_severity = g_strtod (two, NULL); one += strlen (one) + 1; two += strlen (two) + 1; host = strcmp (one, two); if (host == 0) { if (one_severity < two_severity) return -1; else if (one_severity > two_severity) return 1; else { one = *((gchar**) arg_one); two = *((gchar**) arg_two); return strcmp (one, two); } } return host; } /** * @brief Some result info, for sorting. */ struct result_buffer { gchar *host; ///< Host. gchar *port; ///< Port. gchar *severity; ///< Severity. double severity_double; ///< Severity. }; /** * @brief Buffer host type. */ typedef struct result_buffer result_buffer_t; /** * @brief Create a result buffer. * * @param[in] host Host. * @param[in] port Port. * @param[in] severity Severity. * @param[in] severity_double Severity. * * @return Freshly allocated result buffer. */ static result_buffer_t* result_buffer_new (const gchar *host, const gchar *port, const gchar *severity, double severity_double) { result_buffer_t *result_buffer; result_buffer = g_malloc (sizeof (result_buffer_t)); result_buffer->host = g_strdup (host); result_buffer->port = g_strdup (port); result_buffer->severity = g_strdup (severity); result_buffer->severity_double = severity_double; return result_buffer; } /** * @brief Free a result buffer. * * @param[in] result_buffer Result buffer. */ static void result_buffer_free (result_buffer_t *result_buffer) { g_free (result_buffer->host); g_free (result_buffer->port); g_free (result_buffer->severity); g_free (result_buffer); } /** * @brief Compares two buffered results, sorting by host, port then severity. * * @param[in] arg_one First result. * @param[in] arg_two Second result. * * @return -1, 0 or 1 if first given result is less than, equal to or greater * than second. */ static gint compare_port_severity (gconstpointer arg_one, gconstpointer arg_two) { int host; result_buffer_t *one, *two; one = *((result_buffer_t**) arg_one); two = *((result_buffer_t**) arg_two); host = strcmp (one->host, two->host); if (host == 0) { double severity_cmp; int port; port = strcmp (one->port, two->port); if (port != 0) return port; severity_cmp = two->severity_double - one->severity_double; if (severity_cmp > 0) return 1; else if (severity_cmp < 0) return -1; else return 0; } return host; } /** @todo Defined in gmp.c! */ void buffer_results_xml (GString *, iterator_t *, task_t, int, int, int, int, int, int, int, const char *, iterator_t *, int, int, int); /** * @brief Comparison returns. */ typedef enum { COMPARE_RESULTS_CHANGED, COMPARE_RESULTS_ERROR, COMPARE_RESULTS_GONE, COMPARE_RESULTS_NEW, COMPARE_RESULTS_SAME } compare_results_t; /** * @brief Return the sort order of two results. * * @param[in] results Iterator containing first result. * @param[in] delta_results Iterator containing second result. * @param[in] sort_order Whether to sort ascending or descending. * @param[in] sort_field Field to sort on, or NULL for "type". * * @return < 0 if first comes before second, 0 if equal, > 0 if first comes * after second. */ static compare_results_t result_cmp (iterator_t *results, iterator_t *delta_results, int sort_order, const char* sort_field) { const char *host, *delta_host, *port, *delta_port; const char *nvt, *delta_nvt, *name, *delta_name, *descr, *delta_descr; int ret; double severity, delta_severity; if (sort_field == NULL) sort_field = "type"; g_debug (" delta: %s: sort_order: %i", __func__, sort_order); g_debug (" delta: %s: sort_field: %s", __func__, sort_field); host = result_iterator_host (results); delta_host = result_iterator_host (delta_results); port = result_iterator_port (results); delta_port = result_iterator_port (delta_results); severity = result_iterator_severity_double (results); delta_severity = result_iterator_severity_double (delta_results); nvt = result_iterator_nvt_oid (results); delta_nvt = result_iterator_nvt_oid (delta_results); name = result_iterator_nvt_name (results); delta_name = result_iterator_nvt_name (delta_results); descr = result_iterator_descr (results); delta_descr = result_iterator_descr (delta_results); /* For delta reports to work correctly, the order must be the same as in * init_delta_iterators, except that description should not be checked * unless it is the sort_field. * * If description is not the sort_field it is checked after the result_cmp * in compare_results. */ /* Check sort_field first, also using sort_order (0 is descending). */ if (strcmp (sort_field, "host") == 0) { ret = collate_ip (NULL, strlen (host), host, strlen (delta_host), delta_host); if (sort_order == 0) ret = -ret; g_debug (" delta: %s: host (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", host, delta_host, ret); if (ret) return ret; } else if (strcmp (sort_field, "port") == 0 || strcmp (sort_field, "location") == 0) { ret = strcmp (port, delta_port); if (sort_order == 0) ret = -ret; g_debug (" delta: %s: port (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", port, delta_port, ret); if (ret) return ret; } else if (strcmp (sort_field, "severity") == 0) { if (severity > delta_severity) ret = sort_order ? 1 : -1; else if (severity < delta_severity) ret = sort_order ? -1 : 1; else ret = 0; g_debug (" delta: %s: severity (%s): %f VS %f (%i)", __func__, sort_order ? "desc" : "asc", severity, delta_severity, ret); if (ret) return ret; } /* NVT OID, not name/vulnerability. */ else if (strcmp (sort_field, "nvt") == 0) { ret = strcmp (nvt, delta_nvt); if (sort_order) ret = -ret; g_debug (" delta: %s: nvt (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", nvt, delta_nvt, ret); if (ret) return ret; } else if (strcmp (sort_field, "description") == 0) { ret = strcmp (descr, delta_descr); if (sort_order == 0) ret = -ret; g_debug (" delta: %s: description (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", descr, delta_descr, ret); if (ret) return ret; } else if (strcmp (sort_field, "type") == 0) { const char *type, *delta_type; type = result_iterator_type (results); delta_type = result_iterator_type (delta_results); ret = strcmp (type, delta_type); if (sort_order == 0) ret = -ret; g_debug (" delta: %s: type (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", descr, delta_descr, ret); if (ret) return ret; } else if (strcmp (sort_field, "original_type") == 0) { const char *type, *delta_type; type = result_iterator_original_type (results); delta_type = result_iterator_original_type (delta_results); ret = strcmp (type, delta_type); if (sort_order == 0) ret = -ret; g_debug (" delta: %s: original_type (%s): %s VS %s (%i)", __func__, sort_order ? "desc" : "asc", descr, delta_descr, ret); if (ret) return ret; } else { /* Default to "vulnerability" (a.k.a "name") for unknown sort fields. * * Also done in print_report_xml_start, so this is just a safety check. */ ret = strcmp (name ? name : "", delta_name ? delta_name : ""); if (sort_order == 0) ret = -ret; if (ret) return ret; } /* Check remaining fields */ if (strcmp (sort_field, "host")) { ret = collate_ip (NULL, strlen (host), host, strlen (delta_host), delta_host); g_debug (" delta: %s: host: %s VS %s (%i)", __func__, host, delta_host, ret); if (ret) return ret; } if (strcmp (sort_field, "port") && strcmp (sort_field, "location")) { ret = strcmp (port, delta_port); g_debug (" delta: %s: port: %s VS %s (%i)", __func__, port, delta_port, ret); if (ret) return ret; } if (strcmp (sort_field, "severity")) { if (severity > delta_severity) ret = 1; else if (severity < delta_severity) ret = -1; else ret = 0; g_debug (" delta: %s: severity: %f VS %f (%i)", __func__, severity, delta_severity, ret); if (ret) return ret; } if (strcmp (sort_field, "nvt")) { ret = strcmp (nvt, delta_nvt); g_debug (" delta: %s: nvt: %s VS %s (%i)", __func__, nvt, delta_nvt, ret); if (ret) return ret; } return 0; } /** * @brief Test if two strings are equal, ignoring whitespace. * * @param[in] one First string. * @param[in] two Second string. * * @return 1 if equal, else 0. */ static int streq_ignore_ws (const gchar *one, const gchar *two) { if (one == NULL) return two == NULL; if (two == NULL) return 0; while (1) { /* Skip space in both. */ while (isspace (*one)) one++; while (isspace (*two)) two++; /* Check for end. */ if (*one == '\0') break; if (*two == '\0') return 0; /* Compare. */ if (*one != *two) return 0; /* Next. */ one++; two++; } if (*two) return 0; return 1; } /** * @brief Compare two results. * * @param[in] results Iterator containing first result. * @param[in] delta_results Iterator containing second result. * @param[in] sort_order Whether to sort ascending or descending. * @param[in] sort_field Field to sort on, or NULL for "type". * * @return Result of comparison. */ static compare_results_t compare_results (iterator_t *results, iterator_t *delta_results, int sort_order, const char* sort_field) { int ret; const char *descr, *delta_descr; g_debug (" delta: %s", __func__); ret = result_cmp (results, delta_results, sort_order, sort_field); if (ret > 0) /* The delta result sorts first, so it is new. */ return COMPARE_RESULTS_NEW; if (ret < 0) /* The 'results' result sorts first, so it has gone. */ return COMPARE_RESULTS_GONE; descr = result_iterator_descr (results); delta_descr = result_iterator_descr (delta_results); g_debug (" delta: %s: descr: %s VS %s (%i)", __func__, descr ? descr : "NULL", delta_descr ? delta_descr : "NULL", (descr && delta_descr) ? strcmp (descr, delta_descr) : 0); /* This comparison ignores whitespace to match the diff output created by * strdiff in gmp.c. The down side of this is that the comparison may be * affected by the locale. * * An alternate would be to use the strdiff result as the comparison, but * strdiff is only called for the results on the page (and not for the * rest of the results, which must also be compared for the counts). * Using strdiff for all results could also be slow, because it's running * the diff command. */ if (descr && delta_descr && (streq_ignore_ws (descr, delta_descr) == 0)) return COMPARE_RESULTS_CHANGED; return COMPARE_RESULTS_SAME; } /** * @brief Compare two results, optionally writing associated XML to a buffer. * * This is called with buffer NULL to compare results after the page limit * (filter keyword "max") is reached. These results need to be compared to be * included in the counts. * * @param[in] buffer Buffer. NULL to skip writing to buffer. * @param[in] results Iterator containing first result. * @param[in] delta_results Iterator containing second result. * @param[in] task Task associated with report. * @param[in] notes Whether to include notes. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides Whether to include overrides. * @param[in] overrides_details If overrides, Whether to include details. * @param[in] sort_order Whether to sort ascending or descending. * @param[in] sort_field Field to sort on, or NULL for "type". * @param[in] changed Whether to include changed results. * @param[in] gone Whether to include gone results. * @param[in] new Whether to include new results. * @param[in] same Whether to include same results. * @param[in] max_results Value to decrement if result is buffered. * @param[in] first_result Skip result and decrement if positive. * @param[in] used 0 if used, 1 if skipped. * @param[in] would_use 0 if would use (first_result aside), 1 if skipped. * * @return Result of comparison. */ static compare_results_t compare_and_buffer_results (GString *buffer, iterator_t *results, iterator_t *delta_results, task_t task, int notes, int notes_details, int overrides, int overrides_details, int sort_order, const char* sort_field, int changed, int gone, int new, int same, int *max_results, int *first_result, int *used, int *would_use) { compare_results_t state; state = compare_results (results, delta_results, sort_order, sort_field); *used = 0; *would_use = 0; switch (state) { case COMPARE_RESULTS_CHANGED: if (changed) { *would_use = 1; if (*first_result) { g_debug (" delta: skip"); (*first_result)--; break; } *used = 1; (*max_results)--; if (buffer) buffer_results_xml (buffer, results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "changed", delta_results, /* This is the only case that uses 1. */ 1, /* Whether result is "changed". */ -1, 0); } break; case COMPARE_RESULTS_GONE: if (gone) { *would_use = 1; if (*first_result) { g_debug (" delta: skip"); (*first_result)--; break; } *used = 1; (*max_results)--; if (buffer) buffer_results_xml (buffer, results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "gone", delta_results, 0, -1, 0); } break; case COMPARE_RESULTS_NEW: if (new) { *would_use = 1; if (*first_result) { g_debug (" delta: skip"); (*first_result)--; break; } *used = 1; (*max_results)--; if (buffer) buffer_results_xml (buffer, delta_results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "new", delta_results, 0, -1, 0); } break; case COMPARE_RESULTS_SAME: if (same) { *would_use = 1; if (*first_result) { g_debug (" delta: skip"); (*first_result)--; break; } *used = 1; (*max_results)--; if (buffer) buffer_results_xml (buffer, results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "same", delta_results, 0, -1, 0); } break; default: return COMPARE_RESULTS_ERROR; } return state; } /** * @brief Write XML to a file or close stream and return. * * @param[in] stream Stream to write to. * @param[in] xml XML. */ #define PRINT_XML(stream, xml) \ do \ { \ if (fprintf (stream, "%s", xml) < 0) \ { \ fclose (stream); \ return -1; \ } \ } \ while (0) /** * @brief Add a port to a port tree. * * @param[in] ports The tree. * @param[in] results Result iterator on result whose port to add. */ static void add_port (GTree *ports, iterator_t *results) { const char *port, *host; double *old_severity, *severity; GTree *host_ports; /* Ensure there's an inner tree for the host. */ host = result_iterator_host (results); host_ports = g_tree_lookup (ports, host); if (host_ports == NULL) { host_ports = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free, g_free); g_tree_insert (ports, g_strdup (host), host_ports); } /* Ensure the highest threat is recorded for the port in the inner tree. */ port = result_iterator_port (results); severity = g_malloc (sizeof (double)); *severity = result_iterator_severity_double (results); old_severity = g_tree_lookup (host_ports, port); g_debug (" delta: %s: adding %s severity %1.1f on host %s", __func__, port, *severity, host); if (old_severity == NULL) g_tree_insert (host_ports, g_strdup (port), severity); else if (severity > old_severity) { *old_severity = *severity; g_free (severity); } else { g_free (severity); } } /** * @brief Print delta host ports. * * @param[in] key Port. * @param[in] value Threat. * @param[in] data Host and stream. * * @return Always FALSE. */ static gboolean print_host_port (gpointer key, gpointer value, gpointer data) { gpointer *host_and_stream; host_and_stream = (gpointer*) data; g_debug (" delta: %s: host %s port %s", __func__, (gchar*) host_and_stream[0], (gchar*) key); fprintf ((FILE*) host_and_stream[1], "" "%s" "%s" "%1.1f" "%s" "", (gchar*) host_and_stream[0], (gchar*) key, *((double*) value), severity_to_level (*((double*) value), 0)); return FALSE; } /** * @brief Print delta ports. * * @param[in] key Host. * @param[in] value Port tree. * @param[in] stream Stream. * * @return Always FALSE. */ static gboolean print_host_ports (gpointer key, gpointer value, gpointer stream) { gpointer host_and_stream[2]; host_and_stream[0] = key; host_and_stream[1] = stream; g_debug (" delta: %s: host %s", __func__, (gchar*) key); g_tree_foreach ((GTree*) value, print_host_port, host_and_stream); return FALSE; } /** * @brief Add port to ports array. * * @param[in] key Port. * @param[in] value Threat. * @param[in] ports Ports array. * * @return Always FALSE. */ static gboolean array_add_port (gpointer key, gpointer value, gpointer ports) { gpointer *port_threat; port_threat = g_malloc (2 * sizeof (gpointer)); port_threat[0] = key; port_threat[1] = value; array_add ((array_t*) ports, port_threat); return FALSE; } /** * @brief Print delta ports, in descending order. * * @param[in] key Host. * @param[in] value Port tree. * @param[in] stream Stream. * * @return Always FALSE. */ static gboolean print_host_ports_desc (gpointer key, gpointer value, gpointer stream) { guint index; array_t *ports; g_debug (" delta: %s: host %s", __func__, (gchar*) key); /* Convert tree to array. */ ports = make_array (); g_tree_foreach ((GTree*) value, array_add_port, ports); /* Print the array backwards. */ index = ports->len; while (index--) { gpointer *port_threat; port_threat = g_ptr_array_index (ports, index); fprintf ((FILE*) stream, "" "%s" "%s" "%1.1f" "%s" "", (gchar*) key, (gchar*) port_threat[0], *((double*) port_threat[1]), severity_to_level (*((double*) port_threat[1]), 0)); } array_free (ports); return FALSE; } /** * @brief Compare port severities, ascending. * * @param[in] one First. * @param[in] two Second. * * @return 1 one greater, -1 two greater, 0 equal. */ static gint compare_ports_severity (gconstpointer one, gconstpointer two) { gpointer *port_threat_one, *port_threat_two; port_threat_one = *((gpointer**) one); port_threat_two = *((gpointer**) two); if (*((double*) port_threat_one[1]) > *((double*) port_threat_two[1])) return 1; else if (*((double*) port_threat_one[1]) < *((double*) port_threat_two[1])) return -1; else return 0; } /** * @brief Compare port severities, descending. * * @param[in] one First. * @param[in] two Second. * * @return 1 one less, -1 two less, 0 equal. */ static gint compare_ports_severity_desc (gconstpointer one, gconstpointer two) { gpointer *port_threat_one, *port_threat_two; port_threat_one = *((gpointer**) one); port_threat_two = *((gpointer**) two); if (*((double*) port_threat_one[1]) < *((double*) port_threat_two[1])) return 1; else if (*((double*) port_threat_one[1]) > *((double*) port_threat_two[1])) return -1; else return 0; } /** * @brief Print delta ports, ordering by severity. * * @param[in] key Host. * @param[in] value Port tree. * @param[in] stream Stream. * @param[in] ascending Ascending or descending. * * @return Always FALSE. */ static gboolean print_host_ports_by_severity (gpointer key, gpointer value, gpointer stream, int ascending) { guint index, len; array_t *ports; g_debug (" delta: %s: host %s", __func__, (gchar*) key); /* Convert tree to array. */ ports = make_array (); g_tree_foreach ((GTree*) value, array_add_port, ports); /* Sort the array. */ if (ascending) g_ptr_array_sort (ports, compare_ports_severity); else g_ptr_array_sort (ports, compare_ports_severity_desc); /* Print the sorted array. */ index = 0; len = ports->len; while (index < len) { gpointer *port_threat; port_threat = g_ptr_array_index (ports, index); fprintf ((FILE*) stream, "" "%s" "%s" "%1.1f" "%s" "", (gchar*) key, (gchar*) port_threat[0], *((double*) port_threat[1]), severity_to_level (*((double*) port_threat[1]), 0)); index++; } array_free (ports); return FALSE; } /** * @brief Print delta ports, ordering by severity descending. * * @param[in] key Host. * @param[in] value Port tree. * @param[in] stream Stream. * * @return Always FALSE. */ static gboolean print_host_ports_by_severity_desc (gpointer key, gpointer value, gpointer stream) { return print_host_ports_by_severity (key, value, stream, 0); } /** * @brief Print delta ports, ordering by severity ascending. * * @param[in] key Host. * @param[in] value Port tree. * @param[in] stream Stream. * * @return Always FALSE. */ static gboolean print_host_ports_by_severity_asc (gpointer key, gpointer value, gpointer stream) { return print_host_ports_by_severity (key, value, stream, 1); } /** * @brief Free delta host ports. * * @param[in] host_ports Ports. * @param[in] dummy Dummy. * * @return Always FALSE. */ static gboolean free_host_ports (GTree *host_ports, gpointer dummy) { g_tree_destroy (host_ports); return FALSE; } /** * @brief Get N'th last report_host given a host. * * The last report_host is at position 1, the second last at position 2, and * so on. * * @param[in] host Host. * @param[in] report_host Report host. * @param[in] position Position from end. * * @return N'th last report_host. */ gboolean host_nthlast_report_host (const char *host, report_host_t *report_host, int position) { gchar *quoted_host; assert (current_credentials.uuid); if (position == 0) position = 1; quoted_host = sql_quote (host); switch (sql_int64 (report_host, "SELECT id FROM report_hosts WHERE host = '%s'" " AND user_owns ('task'," " (SELECT reports.task FROM reports" " WHERE reports.id" " = report_hosts.report))" " AND (SELECT tasks.hidden FROM tasks, reports" " WHERE reports.task = tasks.id" " AND reports.id = report_hosts.report)" " = 0" " AND (SELECT value FROM task_preferences, tasks," " reports" " WHERE reports.task = tasks.id" " AND reports.id = report_hosts.report" " AND task_preferences.task = tasks.id" " AND task_preferences.name = 'in_assets')" " = 'yes'" " AND report_hosts.end_time > 0" " AND NOT EXISTS (SELECT * FROM report_host_details" " WHERE report_host = report_hosts.id" " AND name = 'CVE Scan')" " ORDER BY id DESC LIMIT 1 OFFSET %i;", quoted_host, position - 1)) { case 0: break; case 1: /* Too few rows in result of query. */ *report_host = 0; break; default: /* Programming error. */ assert (0); case -1: return TRUE; break; } g_free (quoted_host); return FALSE; } /** * @brief Count a report's total number of hosts. * * @param[in] report Report. * * @return Host count. */ int report_host_count (report_t report) { return sql_int ("SELECT count (DISTINCT id) FROM report_hosts" " WHERE report = %llu;", report); } /** * @brief Count a report's total number of hosts with results. * * @param[in] report Report. * @param[in] min_qod Minimum QoD of results to count. * * @return The number of hosts with results */ int report_result_host_count (report_t report, int min_qod) { return sql_int ("SELECT count (DISTINCT id) FROM report_hosts" " WHERE report_hosts.report = %llu" " AND EXISTS (SELECT * FROM results" " WHERE results.host = report_hosts.host" " AND results.qod >= %d)", report, min_qod); } /** * @brief Count a report's total number of tcp/ip ports. * * Ignores port entries in "general/..." form. * * @param[in] report Report. * * @return Ports count. */ static int report_port_count (report_t report) { return sql_int ("SELECT count (DISTINCT port) FROM results" " WHERE report = %llu AND port != ''" " AND port NOT %s 'general/%%';", report, sql_ilike_op ()); } /** * @brief Count a report's total number of closed cves. * * @param[in] report Report. * * @return Closed CVE count. */ static int report_closed_cve_count (report_t report) { return sql_int (" SELECT count(id) FROM nvts" " WHERE cve != ''" " AND family IN (" LSC_FAMILY_LIST ")" " AND oid IN" " (SELECT source_name FROM report_host_details" " WHERE report_host IN " " (SELECT id FROM report_hosts WHERE report = %llu)" " AND name = 'EXIT_CODE'" " AND value = 'EXIT_NOTVULN');", report); } /** * @brief Count a report's total number of vulnerabilities. * * @param[in] report Report. * * @return Vulnerabilities count. */ static int report_vuln_count (report_t report) { return sql_int ("SELECT count (DISTINCT nvt) FROM results" " WHERE report = %llu" " AND severity != " G_STRINGIFY (SEVERITY_ERROR) ";", report); } /** * @brief Count a report's total number of detected Operating Systems. * * @param[in] report Report. * * @return OS count. */ static int report_os_count (report_t report) { return sql_int ("SELECT count (DISTINCT value) FROM report_host_details" " WHERE report_host IN" " (SELECT id from report_hosts WHERE report = %llu)" " AND name = 'best_os_cpe';", report); } /** * @brief Count a report's total number of detected Apps. * * @param[in] report Report. * * @return App count. */ static int report_app_count (report_t report) { return sql_int ("SELECT count (DISTINCT value) FROM report_host_details" " WHERE report_host IN" " (SELECT id from report_hosts WHERE report = %llu)" " AND name = 'App';", report); } /** * @brief Count a report's total number of found SSL Certificates. * * @param[in] report Report. * * @return SSL Certificates count. */ static int report_ssl_cert_count (report_t report) { return sql_int ("SELECT count (DISTINCT id) FROM report_host_details" " WHERE report_host IN" " (SELECT id from report_hosts WHERE report = %llu)" " AND name = 'SSLInfo';", report); } /** * @brief Count a report's total number of error messages. * * @param[in] report Report. * * @return Error Messages count. */ static int report_error_count (report_t report) { return sql_int ("SELECT count (id) FROM results" " WHERE report = %llu and type = 'Error Message';", report); } /** * @brief Get a list string of finished hosts in a report. * * @param[in] report The report to get the finished hosts from. * * @return String containing finished hosts as comma separated list. */ char * report_finished_hosts_str (report_t report) { char *ret; ret = sql_string ("SELECT string_agg (host, ',' ORDER BY host)" " FROM report_hosts" " WHERE report = %llu" " AND end_time != 0;", report); return ret; } /** * @brief Write report host detail to file stream. * * On error close stream. * * @param[in] stream Stream to write to. * @param[in] details Report host details iterator. * @param[in] lean Whether to return reduced info. * * @return 0 success, -1 error. */ static int print_report_host_detail (FILE *stream, iterator_t *details, int lean) { const char *name, *value; name = report_host_details_iterator_name (details); value = report_host_details_iterator_value (details); if (lean) { /* Skip certain host details. */ if (strcmp (name, "EXIT_CODE") == 0 && strcmp (value, "EXIT_NOTVULN") == 0) return 0; if (strcmp (name, "scanned_with_scanner") == 0) return 0; if (strcmp (name, "scanned_with_feedtype") == 0) return 0; if (strcmp (name, "scanned_with_feedversion") == 0) return 0; if (strcmp (name, "OS") == 0) return 0; if (strcmp (name, "traceroute") == 0) return 0; } PRINT (stream, "" "%s" "%s" "", name, value); if (lean == 0) PRINT (stream, "%s", report_host_details_iterator_source_type (details)); PRINT (stream, "%s", report_host_details_iterator_source_name (details)); if (report_host_details_iterator_source_desc (details) && strlen (report_host_details_iterator_source_desc (details))) PRINT (stream, "%s", report_host_details_iterator_source_desc (details)); else if (lean == 0) PRINT (stream, ""); PRINT (stream, ""); if (report_host_details_iterator_extra (details) && strlen (report_host_details_iterator_extra (details))) PRINT (stream, "%s", report_host_details_iterator_extra (details)); else if (lean == 0) PRINT (stream, ""); PRINT (stream, ""); return 0; } /** * @brief Print the XML for a report's host details to a file stream. * @param[in] report_host The report host. * @param[in] stream File stream to write to. * @param[in] lean Report host details iterator. * * @return 0 on success, -1 error. */ static int print_report_host_details_xml (report_host_t report_host, FILE *stream, int lean) { iterator_t details; init_report_host_details_iterator (&details, report_host); while (next (&details)) if (print_report_host_detail (stream, &details, lean)) return -1; cleanup_iterator (&details); return 0; } /** * @brief Write report error message to file stream. * * @param[in] stream Stream to write to. * @param[in] errors Pointer to report error messages iterator. * @param[in] asset_id Asset ID. */ #define PRINT_REPORT_ERROR(stream, errors, asset_id) \ do \ { \ PRINT (stream, \ "" \ "" \ "%s" \ "" \ "" \ "%s" \ "%s" \ "" \ "nvt" \ "%s" \ "%s" \ "" \ "%s" \ "%s" \ "", \ report_errors_iterator_host (errors) ?: "", \ asset_id ? asset_id : "", \ report_errors_iterator_port (errors), \ report_errors_iterator_desc (errors), \ report_errors_iterator_nvt_oid (errors), \ report_errors_iterator_nvt_name (errors), \ report_errors_iterator_nvt_cvss (errors), \ report_errors_iterator_scan_nvt_version (errors), \ report_errors_iterator_severity (errors)); \ } \ while (0) /** * @brief Print the XML for a report's error messages to a file stream. * @param[in] report The report. * @param[in] stream File stream to write to. * * @return 0 on success, -1 error. */ static int print_report_errors_xml (report_t report, FILE *stream) { iterator_t errors; init_report_errors_iterator (&errors, report); PRINT (stream, "%i", report_error_count (report)); while (next (&errors)) { char *asset_id; asset_id = result_host_asset_id (report_errors_iterator_host (&errors), report_errors_iterator_result (&errors)); PRINT_REPORT_ERROR (stream, &errors, asset_id); free (asset_id); } cleanup_iterator (&errors); PRINT (stream, ""); return 0; } /** * @brief Print the XML for a report port summary to a file. * * @param[in] report The report. * @param[in] out File stream. * @param[in] get Result get data. * @param[in] first_result The result to start from. The results are 0 * indexed. * @param[in] max_results The maximum number of results returned. * @param[in] sort_order Whether to sort ascending or descending. * @param[in] sort_field Field to sort on. * @param[out] host_ports Hash table for counting ports per host. * @param[in,out] results Result iterator. For caller to reuse. * * @return 0 on success, -1 error. */ static int print_report_port_xml (report_t report, FILE *out, const get_data_t *get, int first_result, int max_results, int sort_order, const char *sort_field, GHashTable *host_ports, iterator_t *results) { result_buffer_t *last_item; GArray *ports = g_array_new (TRUE, FALSE, sizeof (gchar*)); init_result_get_iterator (results, get, report, NULL, NULL); /* Buffer the results, removing duplicates. */ last_item = NULL; while (next (results)) { const char *port = result_iterator_port (results); const char *host = result_iterator_host (results); double cvss_double; cvss_double = result_iterator_severity_double (results); if (last_item && strcmp (port, last_item->port) == 0 && strcmp (host, last_item->host) == 0 && last_item->severity_double <= cvss_double) { last_item->severity_double = cvss_double; g_free (last_item->severity); last_item->severity = g_strdup (result_iterator_severity (results)); } else { const char *cvss; result_buffer_t *item; cvss = result_iterator_severity (results); if (cvss == NULL) { cvss_double = 0.0; cvss = "0.0"; } item = result_buffer_new (host, port, cvss, cvss_double); g_array_append_val (ports, item); last_item = item; } } /* Handle sorting by threat and ROWID. */ if (sort_field == NULL || strcmp (sort_field, "port")) { int index, length; /** @todo Sort by ROWID if was requested. */ /* Sort by port then severity. */ g_array_sort (ports, compare_port_severity); /* Remove duplicates. */ last_item = NULL; for (index = 0, length = ports->len; index < length; index++) { result_buffer_t *item; item = g_array_index (ports, result_buffer_t*, index); if (last_item && (strcmp (item->port, last_item->port) == 0) && (strcmp (item->host, last_item->host) == 0)) { if (item->severity_double > last_item->severity_double) { gchar *severity; severity = last_item->severity; last_item->severity = item->severity; item->severity = severity; last_item->severity_double = item->severity_double; } g_array_remove_index (ports, index); length = ports->len; index--; } else last_item = item; } /* Sort by severity. */ if (sort_order) g_array_sort (ports, compare_severity_asc); else g_array_sort (ports, compare_severity_desc); } /* Write to file from the buffer. */ PRINT (out, "" "%i", /* Add 1 for 1 indexing. */ first_result + 1, max_results, report_port_count (report)); { result_buffer_t *item; int index = 0; while ((item = g_array_index (ports, result_buffer_t*, index++))) { int host_port_count = GPOINTER_TO_INT (g_hash_table_lookup (host_ports, item->host)); PRINT (out, "" "%s" "%s" "%1.1f" "%s" "", item->host, item->port, item->severity_double, severity_to_level (g_strtod (item->severity, NULL), 0)); if (g_str_has_prefix(item->port, "general/") == FALSE) { g_hash_table_replace (host_ports, g_strdup (item->host), GINT_TO_POINTER (host_port_count + 1)); } result_buffer_free (item); } g_array_free (ports, TRUE); } PRINT (out, ""); return 0; } /** * @brief Calculate the progress of a report. * * @param[in] report Report. * * @return Progress. */ int report_progress (report_t report) { if (report == 0) return -1; return report_slave_progress (report); } /** * @brief Restore original TZ. * * @param[in] zone Only revert if this is at least one character. * Freed here always. * @param[in] tz Original TZ. Freed here if revert occurs. * @param[in] old_tz_override Original tz_override. Freed here on revert. * * @return 0 success, -1 error. */ static int tz_revert (gchar *zone, char *tz, char *old_tz_override) { if (zone && strlen (zone)) { gchar *quoted_old_tz_override; /* Revert to stored TZ. */ if (tz) { if (setenv ("TZ", tz, 1) == -1) { g_warning ("%s: Failed to switch to original TZ", __func__); g_free (tz); g_free (zone); free (old_tz_override); return -1; } } else unsetenv ("TZ"); quoted_old_tz_override = sql_insert (old_tz_override); sql ("SET SESSION \"gvmd.tz_override\" = %s;", quoted_old_tz_override); g_free (quoted_old_tz_override); free (old_tz_override); g_free (tz); } g_free (zone); return 0; } /** * @brief Print the XML for a report to a file. * * @param[in] host_summary_buffer Summary. * @param[in] host Host. * @param[in] start_iso Start time, in ISO format. * @param[in] end_iso End time, in ISO format. */ static void host_summary_append (GString *host_summary_buffer, const char *host, const char *start_iso, const char *end_iso) { if (host_summary_buffer) { char start[200], end[200]; if (start_iso) { struct tm start_tm; memset (&start_tm, 0, sizeof (struct tm)); #if !defined(__GLIBC__) if (strptime (start_iso, "%Y-%m-%dT%H:%M:%S", &start_tm) == NULL) #else if (strptime (start_iso, "%FT%H:%M:%S", &start_tm) == NULL) #endif { g_warning ("%s: Failed to parse start", __func__); return; } if (strftime (start, 200, "%b %d, %H:%M:%S", &start_tm) == 0) { g_warning ("%s: Failed to format start", __func__); return; } } else strcpy (start, "(not started)"); if (end_iso) { struct tm end_tm; memset (&end_tm, 0, sizeof (struct tm)); #if !defined(__GLIBC__) if (strptime (end_iso, "%Y-%m-%dT%H:%M:%S", &end_tm) == NULL) #else if (strptime (end_iso, "%FT%H:%M:%S", &end_tm) == NULL) #endif { g_warning ("%s: Failed to parse end", __func__); return; } if (strftime (end, 200, "%b %d, %H:%M:%S", &end_tm) == 0) { g_warning ("%s: Failed to format end", __func__); return; } } else strcpy (end, "(not finished)"); g_string_append_printf (host_summary_buffer, " %-15s %-16s %s\n", host, start, end); } } /** * @brief Init delta iterators for print_report_xml. * * @param[in] report The report. * @param[in] results Report result iterator. * @param[in] delta Delta report. * @param[in] delta_results Delta report result iterator. * @param[in] get GET command data. * @param[in] term Filter term. * @param[out] sort_field Sort field. * * @return 0 on success, -1 error. */ static int init_delta_iterators (report_t report, iterator_t *results, report_t delta, iterator_t *delta_results, const get_data_t *get, const char *term, const char *sort_field) { int res; gchar *order; get_data_t delta_get; /* * Order must be the same as in result_cmp, except for description * which isn't checked there. */ if ((strcmp (sort_field, "name") == 0) || (strcmp (sort_field, "vulnerability") == 0)) order = g_strdup (", host, port, severity, nvt, description"); else if (strcmp (sort_field, "host") == 0) order = g_strdup (", port, severity, nvt, description"); else if ((strcmp (sort_field, "port") == 0) || (strcmp (sort_field, "location") == 0)) order = g_strdup (", host, severity, nvt, description"); else if (strcmp (sort_field, "severity") == 0) order = g_strdup (", host, port, nvt, description"); else if (strcmp (sort_field, "nvt") == 0) order = g_strdup (", host, port, severity, description"); else order = g_strdup (", host, port, severity, nvt, description"); delta_get = *get; delta_get.filt_id = NULL; delta_get.filter = g_strdup_printf ("rows=-1 first=1 sort=%s %s", sort_field, term); ignore_max_rows_per_page = 1; #if 0 /* For debugging. */ iterator_t results2; res = init_result_get_iterator (results, &delta_get, report, NULL, order); if (res) return -1; res = init_result_get_iterator (&results2, &delta_get, delta, NULL, order); if (res) return -1; g_debug (" delta: %s: REPORT 1:", __func__); while (next (results)) g_debug (" delta: %s: %s %s %s %s %.30s", __func__, result_iterator_nvt_name (results), result_iterator_host (results), result_iterator_type (results), result_iterator_port (results), result_iterator_descr (results)); cleanup_iterator (results); g_debug (" delta: %s: REPORT 1 END", __func__); g_debug (" delta: %s: REPORT 2:", __func__); while (next (&results2)) g_debug (" delta: %s: %s %s %s %s %.30s", __func__, result_iterator_nvt_name (&results2), result_iterator_host (&results2), result_iterator_type (&results2), result_iterator_port (&results2), result_iterator_descr (&results2)); cleanup_iterator (&results2); g_debug (" delta: %s: REPORT 2 END", __func__); #endif res = init_result_get_iterator (results, &delta_get, report, NULL, order); if (res) { ignore_max_rows_per_page = 0; g_free (order); return -1; } res = init_result_get_iterator (delta_results, &delta_get, delta, NULL, order); if (res) { ignore_max_rows_per_page = 0; g_free (order); return -1; } g_free (delta_get.filter); ignore_max_rows_per_page = 0; g_free (order); return 0; } /** * @brief Print delta results for print_report_xml. * * @param[in] out File stream to write to. * @param[in] results Report result iterator. * @param[in] delta_results Delta report result iterator. * @param[in] delta_states String describing delta states to include in count * (for example, "sngc" Same, New, Gone and Changed). * All levels if NULL. * @param[in] first_result First result. * @param[in] max_results Max results. * @param[in] task The task. * @param[in] notes Whether to include notes. * @param[in] notes_details Whether to include note details. * @param[in] overrides Whether to include overrides. * @param[in] overrides_details Whether to include override details. * @param[in] sort_order Sort order. * @param[in] sort_field Sort field. * @param[in] result_hosts_only Whether to only include hosts with results. * @param[in] orig_filtered_result_count Result count. * @param[in] filtered_result_count Result count. * @param[in] orig_f_holes Result count. * @param[in] f_holes Result count. * @param[in] orig_f_infos Result count. * @param[in] f_infos Result count. * @param[in] orig_f_logs Result count. * @param[in] f_logs Result count. * @param[in] orig_f_warnings Result count. * @param[in] f_warnings Result count. * @param[in] orig_f_false_positives Result count. * @param[in] f_false_positives Result count. * @param[in] result_hosts Result hosts. * * @return 0 on success, -1 error. */ static int print_report_delta_xml (FILE *out, iterator_t *results, iterator_t *delta_results, const char *delta_states, int first_result, int max_results, task_t task, int notes, int notes_details, int overrides, int overrides_details, int sort_order, const char *sort_field, int result_hosts_only, int *orig_filtered_result_count, int *filtered_result_count, int *orig_f_holes, int *f_holes, int *orig_f_infos, int *f_infos, int *orig_f_logs, int *f_logs, int *orig_f_warnings, int *f_warnings, int *orig_f_false_positives, int *f_false_positives, array_t *result_hosts) { gboolean done, delta_done; int changed, gone, new, same; /* A tree of host, tree pairs, where the inner tree is a sorted tree * of port, threat pairs. */ GTree *ports; gchar *msg; *orig_f_holes = *f_holes; *orig_f_infos = *f_infos; *orig_f_logs = *f_logs; *orig_f_warnings = *f_warnings; *orig_f_false_positives = *f_false_positives; *orig_filtered_result_count = *filtered_result_count; changed = (strchr (delta_states, 'c') != NULL); gone = (strchr (delta_states, 'g') != NULL); new = (strchr (delta_states, 'n') != NULL); same = (strchr (delta_states, 's') != NULL); ports = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, g_free, (GDestroyNotify) free_host_ports); /* Compare the results in the two iterators, which are sorted. */ g_debug (" delta: %s: start", __func__); g_debug (" delta: %s: sort_field: %s", __func__, sort_field); g_debug (" delta: %s: sort_order: %i", __func__, sort_order); g_debug (" delta: %s: max_results: %i", __func__, max_results); done = !next (results); delta_done = !next (delta_results); while (1) { GString *buffer; compare_results_t state; int used, would_use; if (max_results == 0) break; if (done) { if (delta_done) break; if (new) /* Extra results in 'delta_results'. */ do { const char *level; g_debug (" delta: %s: extra from report 2: %s", __func__, result_iterator_nvt_oid (results)); if (first_result) { g_debug (" delta: skip"); first_result--; continue; } /* Increase the result count. */ level = result_iterator_level (delta_results); (*orig_filtered_result_count)++; (*filtered_result_count)++; if (strcmp (level, "High") == 0) { (*orig_f_holes)++; (*f_holes)++; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)++; (*f_warnings)++; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)++; (*f_infos)++; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)++; (*f_logs)++; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)++; (*f_false_positives)++; } g_debug (" delta: %s: extra from report 2: %s", __func__, result_iterator_nvt_oid (delta_results)); buffer = g_string_new (""); buffer_results_xml (buffer, delta_results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "new", NULL, 0, -1, 0); if (fprintf (out, "%s", buffer->str) < 0) return -1; g_string_free (buffer, TRUE); if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (delta_results)); add_port (ports, delta_results); max_results--; if (max_results == 0) break; } while (next (delta_results)); delta_done = TRUE; break; } if (delta_done) { /* Extra results in 'results'. */ if (gone) do { g_debug (" delta: %s: extra from report 1: %s", __func__, result_iterator_nvt_oid (results)); if (first_result) { g_debug (" delta: skip"); first_result--; continue; } buffer = g_string_new (""); buffer_results_xml (buffer, results, task, notes, notes_details, overrides, overrides_details, 0, 0, 0, "gone", NULL, 0, -1, 0); if (fprintf (out, "%s", buffer->str) < 0) return -1; g_string_free (buffer, TRUE); if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (results)); add_port (ports, results); max_results--; if (max_results == 0) break; } while (next (results)); else do { const char *level; /* Decrease the result count. */ level = result_iterator_level (results); (*orig_filtered_result_count)--; (*filtered_result_count)--; if (strcmp (level, "High") == 0) { (*orig_f_holes)--; (*f_holes)--; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)--; (*f_warnings)--; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)--; (*f_infos)--; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)--; (*f_logs)--; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)--; (*f_false_positives)--; } } while (next (results)); done = TRUE; break; } /* Compare the two results. */ buffer = g_string_new (""); state = compare_and_buffer_results (buffer, results, delta_results, task, notes, notes_details, overrides, overrides_details, sort_order, sort_field, changed, gone, new, same, &max_results, &first_result, &used, &would_use); if (state == COMPARE_RESULTS_ERROR) { g_warning ("%s: compare_and_buffer_results failed", __func__); return -1; } if (fprintf (out, "%s", buffer->str) < 0) return -1; g_string_free (buffer, TRUE); if ((used == 0) && ((state == COMPARE_RESULTS_GONE) || (state == COMPARE_RESULTS_SAME) || (state == COMPARE_RESULTS_CHANGED))) { const char *level; /* Decrease the result count. */ level = result_iterator_level (results); (*filtered_result_count)--; if (strcmp (level, "High") == 0) { (*f_holes)--; } else if (strcmp (level, "Medium") == 0) { (*f_warnings)--; } else if (strcmp (level, "Low") == 0) { (*f_infos)--; } else if (strcmp (level, "Log") == 0) { (*f_logs)--; } else if (strcmp (level, "False Positive") == 0) { (*f_false_positives)--; } } if ((would_use == 0) && ((state == COMPARE_RESULTS_GONE) || (state == COMPARE_RESULTS_SAME) || (state == COMPARE_RESULTS_CHANGED))) { const char *level; /* Decrease the result count. */ level = result_iterator_level (results); (*orig_filtered_result_count)--; if (strcmp (level, "High") == 0) { (*orig_f_holes)--; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)--; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)--; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)--; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)--; } } /* Move on to the next. */ if (state == COMPARE_RESULTS_GONE) { /* "Used" just the 'results' result. */ if (used) { if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (results)); add_port (ports, results); } done = !next (results); } else if ((state == COMPARE_RESULTS_SAME) || (state == COMPARE_RESULTS_CHANGED)) { /* "Used" both results. */ if (used) { if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (results)); add_port (ports, results); } done = !next (results); delta_done = !next (delta_results); } else if (state == COMPARE_RESULTS_NEW) { if (would_use) { const char *level; /* Would have "used" just the 'delta_results' result, on * an earlier page. */ /* Increase the result count. */ level = result_iterator_level (delta_results); (*orig_filtered_result_count)++; if (strcmp (level, "High") == 0) { (*orig_f_holes)++; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)++; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)++; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)++; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)++; } } if (used) { const char *level; /* "Used" just the 'delta_results' result. */ /* Increase the result count. */ level = result_iterator_level (delta_results); (*filtered_result_count)++; if (strcmp (level, "High") == 0) { (*f_holes)++; } else if (strcmp (level, "Medium") == 0) { (*f_warnings)++; } else if (strcmp (level, "Low") == 0) { (*f_infos)++; } else if (strcmp (level, "Log") == 0) { (*f_logs)++; } else if (strcmp (level, "False Positive") == 0) { (*f_false_positives)++; } if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (delta_results)); add_port (ports, delta_results); } delta_done = !next (delta_results); } else assert (0); } /* Compare remaining results, for the filtered report counts. */ g_debug (" delta: %s: counting rest", __func__); while (1) { compare_results_t state; int used, would_use; if (done) { if (delta_done) break; if (new) /* Extra results in 'delta_results'. */ do { const char *level; g_debug (" delta: %s: extra from report 2: %s", __func__, result_iterator_nvt_oid (delta_results)); /* Increase the result count. */ level = result_iterator_level (delta_results); (*orig_filtered_result_count)++; if (strcmp (level, "High") == 0) { (*orig_f_holes)++; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)++; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)++; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)++; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)++; } } while (next (delta_results)); break; } if (delta_done) { /* Extra results in 'results'. */ if (gone) do { g_debug (" delta: %s: extra from report 1: %s", __func__, result_iterator_nvt_oid (results)); /* It's in the count already. */ } while (next (results)); else do { const char *level; /* Decrease the result count. */ level = result_iterator_level (results); (*orig_filtered_result_count)--; if (strcmp (level, "High") == 0) { (*orig_f_holes)--; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)--; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)--; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)--; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)--; } } while (next (results)); break; } /* Compare the two results. */ state = compare_and_buffer_results (NULL, results, delta_results, task, notes, notes_details, overrides, overrides_details, sort_order, sort_field, changed, gone, new, same, &max_results, &first_result, &used, &would_use); if (state == COMPARE_RESULTS_ERROR) { g_warning ("%s: compare_and_buffer_results failed", __func__); return -1; } if (state == COMPARE_RESULTS_NEW) { if (used) { const char *level; /* "Used" just the 'delta_results' result. */ /* Increase the result count. */ level = result_iterator_level (delta_results); (*orig_filtered_result_count)++; if (strcmp (level, "High") == 0) { (*orig_f_holes)++; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)++; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)++; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)++; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)++; } } } else if (used) { /* It's in the count already. */ } else { const char *level; /* Decrease the result count. */ level = result_iterator_level (results); (*orig_filtered_result_count)--; if (strcmp (level, "High") == 0) { (*orig_f_holes)--; } else if (strcmp (level, "Medium") == 0) { (*orig_f_warnings)--; } else if (strcmp (level, "Low") == 0) { (*orig_f_infos)--; } else if (strcmp (level, "Log") == 0) { (*orig_f_logs)--; } else if (strcmp (level, "False Positive") == 0) { (*orig_f_false_positives)--; } } /* Move on to the next. */ if (state == COMPARE_RESULTS_GONE) { /* "Used" just the 'results' result. */ done = !next (results); } else if ((state == COMPARE_RESULTS_SAME) || (state == COMPARE_RESULTS_CHANGED)) { /* "Used" both results. */ done = !next (results); delta_done = !next (delta_results); } else if (state == COMPARE_RESULTS_NEW) { /* "Used" just the 'delta_results' result. */ delta_done = !next (delta_results); } else assert (0); } msg = g_markup_printf_escaped (""); if (fprintf (out, "%s", msg) < 0) { g_free (msg); fclose (out); return -1; } g_free (msg); /* Write ports to file. */ msg = g_markup_printf_escaped ("", /* Add 1 for 1 indexing. */ first_result + 1, max_results); if (fprintf (out, "%s", msg) < 0) { g_free (msg); fclose (out); return -1; } g_free (msg); if (sort_field == NULL || strcmp (sort_field, "port")) { if (sort_order) g_tree_foreach (ports, print_host_ports_by_severity_asc, out); else g_tree_foreach (ports, print_host_ports_by_severity_desc, out); } else if (sort_order) g_tree_foreach (ports, print_host_ports, out); else g_tree_foreach (ports, print_host_ports_desc, out); g_tree_destroy (ports); msg = g_markup_printf_escaped (""); if (fprintf (out, "%s", msg) < 0) { g_free (msg); fclose (out); return -1; } g_free (msg); return 0; } /** * @brief Print the main XML content for a report to a file. * * @param[in] report The report. * @param[in] delta Report to compare with the report. * @param[in] task Task associated with report. * @param[in] xml_start File name. * @param[in] get GET command data. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides_details If overrides, Whether to include details. * @param[in] result_tags Whether to include tags in results. * @param[in] ignore_pagination Whether to ignore pagination data. * @param[in] lean Whether to return lean report. * @param[out] filter_term_return Filter term used in report. * @param[out] zone_return Actual timezone used in report. * @param[out] host_summary Summary of results per host. * * @return 0 on success, -1 error, 2 failed to find filter (before any printing). */ static int print_report_xml_start (report_t report, report_t delta, task_t task, gchar* xml_start, const get_data_t *get, int notes_details, int overrides_details, int result_tags, int ignore_pagination, int lean, gchar **filter_term_return, gchar **zone_return, gchar **host_summary) { int result_hosts_only; int notes, overrides; int first_result, max_results, sort_order; FILE *out; gchar *clean, *term, *sort_field, *levels, *search_phrase; gchar *min_qod; gchar *delta_states, *timestamp; int min_qod_int; char *uuid, *tsk_uuid = NULL, *start_time, *end_time; int total_result_count, filtered_result_count; array_t *result_hosts; int reuse_result_iterator; iterator_t results, delta_results; int holes, infos, logs, warnings, false_positives; int f_holes, f_infos, f_logs, f_warnings, f_false_positives; int orig_f_holes, orig_f_infos, orig_f_logs; int orig_f_warnings, orig_f_false_positives, orig_filtered_result_count; int search_phrase_exact, apply_overrides, count_filtered; double severity, f_severity; gchar *tz, *zone; char *old_tz_override; GString *filters_buffer, *filters_extra_buffer, *host_summary_buffer; gchar *term_value; GHashTable *f_host_ports; GHashTable *f_host_holes, *f_host_warnings, *f_host_infos; GHashTable *f_host_logs, *f_host_false_positives; task_status_t run_status; /* Init some vars to prevent warnings from older compilers. */ max_results = -1; levels = NULL; zone = NULL; delta_states = NULL; min_qod = NULL; search_phrase = NULL; total_result_count = filtered_result_count = 0; orig_filtered_result_count = 0; orig_f_false_positives = orig_f_warnings = orig_f_logs = orig_f_infos = 0; orig_f_holes = 0; f_host_ports = NULL; f_host_holes = NULL; f_host_warnings = NULL; f_host_infos = NULL; f_host_logs = NULL; f_host_false_positives = NULL; /** @todo Leaks on error in PRINT and PRINT_XML. The process normally exits * then anyway. */ /* run_status is set by report_scan_run_status when either of "delta" and * "report" are true. run_status is only used by run_status_name, only when * either of "delta" and "report" are true, and only after a * report_scan_run_status call. Still GCC 4.4.5 (Debian 4.4.5-8) gives a * "may be used uninitialized" warning, so init it here to quiet the * warning. */ run_status = TASK_STATUS_INTERRUPTED; if (report == 0) { assert (0); return -1; } out = fopen (xml_start, "w"); if (out == NULL) { g_warning ("%s: fopen failed: %s", __func__, strerror (errno)); return -1; } assert (get); if ((get->filt_id && strlen (get->filt_id) && strcmp (get->filt_id, FILT_ID_NONE)) || (get->filter && strlen (get->filter))) { term = NULL; if (get->filt_id && strlen (get->filt_id) && strcmp (get->filt_id, FILT_ID_NONE)) { term = filter_term (get->filt_id); if (term == NULL) { fclose (out); return 2; } } /* Set the filter parameters from the filter term. */ manage_report_filter_controls (term ? term : get->filter, &first_result, &max_results, &sort_field, &sort_order, &result_hosts_only, &min_qod, &levels, &delta_states, &search_phrase, &search_phrase_exact, ¬es, &overrides, &apply_overrides, &zone); } else { term = g_strdup (""); /* Set the filter parameters to defaults */ manage_report_filter_controls (term, &first_result, &max_results, &sort_field, &sort_order, &result_hosts_only, &min_qod, &levels, &delta_states, &search_phrase, &search_phrase_exact, ¬es, &overrides, &apply_overrides, &zone); } max_results = manage_max_rows (max_results); levels = levels ? levels : g_strdup ("hmlgd"); if (task && task_uuid (task, &tsk_uuid)) { fclose (out); g_free (term); g_free (levels); g_free (search_phrase); g_free (min_qod); g_free (delta_states); return -1; } if (zone && strlen (zone)) { gchar *quoted_zone; /* Store current TZ. */ tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL; if (setenv ("TZ", zone, 1) == -1) { g_warning ("%s: Failed to switch to timezone", __func__); if (tz != NULL) setenv ("TZ", tz, 1); g_free (tz); g_free (zone); return -1; } old_tz_override = sql_string ("SELECT current_setting" " ('gvmd.tz_override');"); quoted_zone = sql_insert (zone); sql ("SET SESSION \"gvmd.tz_override\" = %s;", quoted_zone); g_free (quoted_zone); tzset (); } else { /* Keep compiler quiet. */ tz = NULL; old_tz_override = NULL; } if (delta && report) { uuid = report_uuid (report); PRINT (out, "", uuid); free (uuid); } else { uuid = report_uuid (report); PRINT (out, "", uuid); free (uuid); } PRINT (out, "%s", GMP_VERSION); if (delta) { delta_states = delta_states ? delta_states : g_strdup ("cgns"); report_scan_run_status (delta, &run_status); uuid = report_uuid (delta); PRINT (out, "" "" "%s", uuid, run_status_name (run_status ? run_status : TASK_STATUS_INTERRUPTED)); if (report_timestamp (uuid, ×tamp)) { free (uuid); g_free (levels); g_free (search_phrase); g_free (min_qod); g_free (delta_states); tz_revert (zone, tz, old_tz_override); return -1; } PRINT (out, "%s", timestamp); g_free (timestamp); start_time = scan_start_time (delta); PRINT (out, "%s", start_time); free (start_time); end_time = scan_end_time (delta); PRINT (out, "%s", end_time); free (end_time); PRINT (out, "" ""); } count_filtered = (delta == 0 && ignore_pagination && get->details); if (report) { /* Get total counts of full results. */ if (delta == 0) { int total_holes, total_infos, total_logs; int total_warnings, total_false_positives; get_data_t *all_results_get; all_results_get = report_results_get_data (1, -1, 0, 0); report_counts_id (report, &total_holes, &total_infos, &total_logs, &total_warnings, &total_false_positives, NULL, all_results_get, NULL); total_result_count = total_holes + total_infos + total_logs + total_warnings + total_false_positives; get_data_reset (all_results_get); free (all_results_get); } /* Get total counts of filtered results. */ if (count_filtered) { /* We're getting all the filtered results, so we can count them as we * print them, to save time. */ filtered_result_count = 0; } else { /* Beware, we're using the full variables temporarily here, but * report_counts_id counts the filtered results. */ report_counts_id (report, &holes, &infos, &logs, &warnings, &false_positives, NULL, get, NULL); filtered_result_count = holes + infos + logs + warnings + false_positives; } /* Get report run status. */ report_scan_run_status (report, &run_status); } clean = manage_clean_filter (term ? term : (get->filter ? get->filter : "")); term_value = filter_term_value (clean, "min_qod"); if (term_value == NULL) { gchar *new_filter; new_filter = g_strdup_printf ("min_qod=%i %s", MIN_QOD_DEFAULT, clean); g_free (clean); clean = new_filter; } g_free (term_value); term_value = filter_term_value (clean, "apply_overrides"); if (term_value == NULL) { gchar *new_filter; new_filter = g_strdup_printf ("apply_overrides=%i %s", APPLY_OVERRIDES_DEFAULT, clean); g_free (clean); clean = new_filter; } g_free (term_value); g_free (term); term = clean; if (delta && sort_field /* These are all checked in result_cmp. */ && strcmp (sort_field, "name") && strcmp (sort_field, "vulnerability") && strcmp (sort_field, "host") && strcmp (sort_field, "port") && strcmp (sort_field, "location") && strcmp (sort_field, "severity") && strcmp (sort_field, "nvt") && strcmp (sort_field, "description") && strcmp (sort_field, "type") && strcmp (sort_field, "original_type")) { gchar *new_term; if ((strcmp (sort_field, "task") == 0) || (strcmp (sort_field, "task_id") == 0) || (strcmp (sort_field, "report_id") == 0)) { /* These don't affect delta report, so sort by vulnerability. */ g_free (sort_field); sort_field = g_strdup ("vulnerability"); } else { /* The remaining filterable fields for the result iterator, all of * which may be used as a sort field. These could be added to * result_cmp. For now sort by vulnerability. */ #if 0 "uuid", "comment", "created", "modified", "_owner", "cvss_base", "nvt_version", "original_severity", "date", "solution_type", "qod", "qod_type", "cve", "hostname", "path" #endif g_free (sort_field); sort_field = g_strdup ("vulnerability"); } /* Adjust "term" to match sort_field, because "term" will be used in the * REPORT XML FILTERS (sent by buffer_get_filter_xml below). */ new_term = g_strdup_printf ("sort=%s %s", sort_field, term); g_free (term); term = new_term; /* Similarly, the order will now be ascending. */ sort_order = 1; } if (filter_term_return) *filter_term_return = g_strdup (term); PRINT (out, "%s%s", sort_field ? sort_field : "type", sort_order ? "ascending" : "descending"); filters_extra_buffer = g_string_new (""); if (strchr (levels, 'h')) g_string_append (filters_extra_buffer, "High"); if (strchr (levels, 'm')) g_string_append (filters_extra_buffer, "Medium"); if (strchr (levels, 'l')) g_string_append (filters_extra_buffer, "Low"); if (strchr (levels, 'g')) g_string_append (filters_extra_buffer, "Log"); if (strchr (levels, 'f')) g_string_append (filters_extra_buffer, "False Positive"); if (delta) { gchar *escaped_delta_states = g_markup_escape_text (delta_states, -1); g_string_append_printf (filters_extra_buffer, "" "%s" "%i" "%i" "%i" "%i" "", escaped_delta_states, strchr (delta_states, 'c') != NULL, strchr (delta_states, 'g') != NULL, strchr (delta_states, 'n') != NULL, strchr (delta_states, 's') != NULL); g_free (escaped_delta_states); } filters_buffer = g_string_new (""); buffer_get_filter_xml (filters_buffer, "result", get, term, filters_extra_buffer->str); g_string_free (filters_extra_buffer, TRUE); PRINT_XML (out, filters_buffer->str); g_string_free (filters_buffer, TRUE); if (report) { int tag_count = resource_tag_count ("report", report, 1); if (tag_count) { PRINT (out, "" "%i", tag_count); if (get->details || get->id) { iterator_t tags; init_resource_tag_iterator (&tags, "report", report, 1, NULL, 1); while (next (&tags)) { PRINT (out, "" "%s" "%s" "%s" "", resource_tag_iterator_uuid (&tags), resource_tag_iterator_name (&tags), resource_tag_iterator_value (&tags), resource_tag_iterator_comment (&tags)); } cleanup_iterator (&tags); } PRINT (out, ""); } } if (report) { PRINT (out, "%s", run_status_name (run_status ? run_status : TASK_STATUS_INTERRUPTED)); PRINT (out, "%i", report_host_count (report)); PRINT (out, "%i", report_closed_cve_count (report)); PRINT (out, "%i", report_vuln_count (report)); PRINT (out, "%i", report_os_count (report)); PRINT (out, "%i", report_app_count (report)); PRINT (out, "%i", report_ssl_cert_count (report)); } if (task && tsk_uuid) { char *tsk_name, *task_target_uuid, *task_target_name; char *task_target_comment, *comment; target_t target; gchar *progress_xml; iterator_t tags; int task_tag_count = resource_tag_count ("task", task, 1); tsk_name = task_name (task); comment = task_comment (task); target = task_target (task); if (task_target_in_trash (task)) { task_target_uuid = trash_target_uuid (target); task_target_name = trash_target_name (target); task_target_comment = trash_target_comment (target); } else { task_target_uuid = target_uuid (target); task_target_name = target_name (target); task_target_comment = target_comment (target); } if ((target == 0) && (task_run_status (task) == TASK_STATUS_RUNNING)) progress_xml = g_strdup_printf ("%i", task_upload_progress (task)); else { int progress; progress = report_progress (report); progress_xml = g_strdup_printf ("%i", progress); } PRINT (out, "" "%s" "%s" "" "%i" "%s" "%s" "" "%s", tsk_uuid, tsk_name ? tsk_name : "", comment ? comment : "", task_target_uuid ? task_target_uuid : "", task_target_in_trash (task), task_target_name ? task_target_name : "", task_target_comment ? task_target_comment : "", progress_xml); g_free (progress_xml); free (comment); free (tsk_name); free (tsk_uuid); free (task_target_uuid); free (task_target_name); free (task_target_comment); if (task_tag_count) { PRINT (out, "" "%i", task_tag_count); init_resource_tag_iterator (&tags, "task", task, 1, NULL, 1); while (next (&tags)) { PRINT (out, "" "%s" "%s" "%s" "", resource_tag_iterator_uuid (&tags), resource_tag_iterator_name (&tags), resource_tag_iterator_value (&tags), resource_tag_iterator_comment (&tags)); } cleanup_iterator (&tags); PRINT (out, ""); } PRINT (out, ""); { char *source_iface; /* Info about the situation at the time of scan. */ PRINT (out, "" ""); source_iface = report_source_iface (report); if (source_iface) /* VALUE "" means preference was not set. Missing PREFERENCE means * we don't know. */ PRINT (out, "" "" "Network Source Interface" "source_iface" "%s" "" "", source_iface); free (source_iface); PRINT (out, "" ""); } } uuid = report_uuid (report); if (report_timestamp (uuid, ×tamp)) { free (uuid); g_free (term); tz_revert (zone, tz, old_tz_override); return -1; } free (uuid); PRINT (out, "%s", timestamp); g_free (timestamp); start_time = scan_start_time (report); PRINT (out, "%s", start_time); free (start_time); { time_t start_time_epoch; const char *abbrev; gchar *report_zone; start_time_epoch = scan_start_time_epoch (report); abbrev = NULL; if (zone && strlen (zone)) report_zone = g_strdup (zone); else report_zone = setting_timezone (); iso_time_tz (&start_time_epoch, report_zone, &abbrev); if (zone_return) *zone_return = g_strdup (report_zone ? report_zone : ""); PRINT (out, "%s" "%s", report_zone ? report_zone : "Coordinated Universal Time", abbrev ? abbrev : "UTC"); g_free (report_zone); } /* Port summary. */ f_host_ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); reuse_result_iterator = 0; if (get->details && (delta == 0)) { reuse_result_iterator = 1; if (print_report_port_xml (report, out, get, first_result, max_results, sort_order, sort_field, f_host_ports, &results)) { g_free (term); tz_revert (zone, tz, old_tz_override); g_hash_table_destroy (f_host_ports); return -1; } } /* Prepare result counts. */ if (count_filtered) { /* We're getting all the filtered results, so we can count them as we * print them, to save time. */ report_counts_id_full (report, &holes, &infos, &logs, &warnings, &false_positives, &severity, get, NULL, NULL, NULL, NULL, NULL, NULL, NULL); f_holes = f_infos = f_logs = f_warnings = 0; f_false_positives = f_severity = 0; } else report_counts_id_full (report, &holes, &infos, &logs, &warnings, &false_positives, &severity, get, NULL, &f_holes, &f_infos, &f_logs, &f_warnings, &f_false_positives, &f_severity); /* Results. */ if (min_qod == NULL || sscanf (min_qod, "%d", &min_qod_int) != 1) min_qod_int = MIN_QOD_DEFAULT; if (delta && get->details) { if (init_delta_iterators (report, &results, delta, &delta_results, get, term, sort_field)) { g_free (term); g_hash_table_destroy (f_host_ports); return -1; } g_free (term); } else if (get->details) { int res; g_free (term); if (reuse_result_iterator) iterator_rewind (&results); else { res = init_result_get_iterator (&results, get, report, NULL, NULL); if (res) { g_hash_table_destroy (f_host_ports); return -1; } } } else g_free (term); if (get->details) PRINT (out, "", /* Add 1 for 1 indexing. */ ignore_pagination ? 1 : first_result + 1, ignore_pagination ? -1 : max_results); if (get->details && result_hosts_only) result_hosts = make_array (); else /* Quiet erroneous compiler warning. */ result_hosts = NULL; f_host_holes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); f_host_warnings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); f_host_infos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); f_host_logs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); f_host_false_positives = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (delta && get->details) { if (print_report_delta_xml (out, &results, &delta_results, delta_states, ignore_pagination ? 1 : first_result, ignore_pagination ? -1 : max_results, task, notes, notes_details, overrides, overrides_details, sort_order, sort_field, result_hosts_only, &orig_filtered_result_count, &filtered_result_count, &orig_f_holes, &f_holes, &orig_f_infos, &f_infos, &orig_f_logs, &f_logs, &orig_f_warnings, &f_warnings, &orig_f_false_positives, &f_false_positives, result_hosts)) { fclose (out); g_free (sort_field); g_free (levels); g_free (search_phrase); g_free (min_qod); g_free (delta_states); cleanup_iterator (&results); cleanup_iterator (&delta_results); tz_revert (zone, tz, old_tz_override); g_hash_table_destroy (f_host_ports); g_hash_table_destroy (f_host_holes); g_hash_table_destroy (f_host_warnings); g_hash_table_destroy (f_host_infos); g_hash_table_destroy (f_host_logs); g_hash_table_destroy (f_host_false_positives); return -1; } } else if (get->details) { int cert_loaded; cert_loaded = manage_cert_loaded (); while (next (&results)) { const char* level; GHashTable *f_host_result_counts; GString *buffer = g_string_new (""); double result_severity; buffer_results_xml (buffer, &results, task, notes, notes_details, overrides, overrides_details, result_tags, 1, 0, NULL, NULL, 0, cert_loaded, lean); PRINT_XML (out, buffer->str); g_string_free (buffer, TRUE); if (result_hosts_only) array_add_new_string (result_hosts, result_iterator_host (&results)); result_severity = result_iterator_severity_double (&results); if (result_severity > f_severity) f_severity = result_severity; level = result_iterator_level (&results); if (strcasecmp (level, "log") == 0) { f_host_result_counts = f_host_logs; if (count_filtered) f_logs++; } else if (strcasecmp (level, "high") == 0) { f_host_result_counts = f_host_holes; if (count_filtered) f_holes++; } else if (strcasecmp (level, "medium") == 0) { f_host_result_counts = f_host_warnings; if (count_filtered) f_warnings++; } else if (strcasecmp (level, "low") == 0) { f_host_result_counts = f_host_infos; if (count_filtered) f_infos++; } else if (strcasecmp (level, "false positive") == 0) { f_host_result_counts = f_host_false_positives; if (count_filtered) f_false_positives++; } else f_host_result_counts = NULL; if (f_host_result_counts) { const char *result_host = result_iterator_host (&results); int result_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_result_counts, result_host)); g_hash_table_replace (f_host_result_counts, g_strdup (result_host), GINT_TO_POINTER (result_count + 1)); } } PRINT (out, ""); } if (get->details) cleanup_iterator (&results); if (delta && get->details) cleanup_iterator (&delta_results); /* Print result counts and severity. */ if (delta) /** @todo The f_holes, etc. vars are setup to give the page count. */ PRINT (out, "" "%i" "%i" "%i" "%i" "%i" "" "%i" "" "", orig_filtered_result_count, (strchr (levels, 'h') ? orig_f_holes : 0), (strchr (levels, 'l') ? orig_f_infos : 0), (strchr (levels, 'g') ? orig_f_logs : 0), (strchr (levels, 'm') ? orig_f_warnings : 0), (strchr (levels, 'f') ? orig_f_false_positives : 0)); else { if (count_filtered) filtered_result_count = f_holes + f_infos + f_logs + f_warnings + false_positives; PRINT (out, "" "%i" "%i" "%i" "%i%i" "%i%i" "%i%i" "%i%i" "" "%i" "%i" "" "", total_result_count, total_result_count, filtered_result_count, holes, (strchr (levels, 'h') ? f_holes : 0), infos, (strchr (levels, 'l') ? f_infos : 0), logs, (strchr (levels, 'g') ? f_logs : 0), warnings, (strchr (levels, 'm') ? f_warnings : 0), false_positives, (strchr (levels, 'f') ? f_false_positives : 0)); PRINT (out, "" "%1.1f" "%1.1f" "", severity, f_severity); } if (host_summary) { host_summary_buffer = g_string_new (""); g_string_append_printf (host_summary_buffer, " %-15s %-16s End\n", "Host", "Start"); } else host_summary_buffer = NULL; if (get->details && result_hosts_only) { gchar *result_host; int index = 0; array_terminate (result_hosts); while ((result_host = g_ptr_array_index (result_hosts, index++))) { gboolean present; iterator_t hosts; init_report_host_iterator (&hosts, report, result_host, 0); present = next (&hosts); if (delta && (present == FALSE)) { cleanup_iterator (&hosts); init_report_host_iterator (&hosts, delta, result_host, 0); present = next (&hosts); } if (present) { const char *current_host; int ports_count; int holes_count, warnings_count, infos_count; int logs_count, false_positives_count; current_host = host_iterator_host (&hosts); ports_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_ports, current_host)); holes_count = GPOINTER_TO_INT (g_hash_table_lookup ( f_host_holes, current_host)); warnings_count = GPOINTER_TO_INT (g_hash_table_lookup ( f_host_warnings, current_host)); infos_count = GPOINTER_TO_INT (g_hash_table_lookup ( f_host_infos, current_host)); logs_count = GPOINTER_TO_INT (g_hash_table_lookup ( f_host_logs, current_host)); false_positives_count = GPOINTER_TO_INT (g_hash_table_lookup ( f_host_false_positives, current_host)); host_summary_append (host_summary_buffer, result_host, host_iterator_start_time (&hosts), host_iterator_end_time (&hosts)); PRINT (out, "" "%s", result_host); if (host_iterator_asset_uuid (&hosts) && strlen (host_iterator_asset_uuid (&hosts))) PRINT (out, "", host_iterator_asset_uuid (&hosts)); else if (lean == 0) PRINT (out, ""); PRINT (out, "%s" "%s" "%d" "" "%d" "%d" "%d" "%d" "%d" "%d" "", host_iterator_start_time (&hosts), host_iterator_end_time (&hosts) ? host_iterator_end_time (&hosts) : "", ports_count, (holes_count + warnings_count + infos_count + logs_count + false_positives_count), holes_count, warnings_count, infos_count, logs_count, false_positives_count); if (print_report_host_details_xml (host_iterator_report_host (&hosts), out, lean)) { tz_revert (zone, tz, old_tz_override); if (host_summary_buffer) g_string_free (host_summary_buffer, TRUE); g_hash_table_destroy (f_host_ports); g_hash_table_destroy (f_host_holes); g_hash_table_destroy (f_host_warnings); g_hash_table_destroy (f_host_infos); g_hash_table_destroy (f_host_logs); g_hash_table_destroy (f_host_false_positives); return -1; } PRINT (out, ""); } cleanup_iterator (&hosts); } array_free (result_hosts); } else if (get->details) { iterator_t hosts; init_report_host_iterator (&hosts, report, NULL, 0); while (next (&hosts)) { const char *current_host; int ports_count; int holes_count, warnings_count, infos_count; int logs_count, false_positives_count; current_host = host_iterator_host (&hosts); ports_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_ports, current_host)); holes_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_holes, current_host)); warnings_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_warnings, current_host)); infos_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_infos, current_host)); logs_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_logs, current_host)); false_positives_count = GPOINTER_TO_INT (g_hash_table_lookup (f_host_false_positives, current_host)); host_summary_append (host_summary_buffer, host_iterator_host (&hosts), host_iterator_start_time (&hosts), host_iterator_end_time (&hosts)); PRINT (out, "" "%s", host_iterator_host (&hosts)); if (host_iterator_asset_uuid (&hosts) && strlen (host_iterator_asset_uuid (&hosts))) PRINT (out, "", host_iterator_asset_uuid (&hosts)); else if (lean == 0) PRINT (out, ""); PRINT (out, "%s" "%s" "%d" "" "%d" "%d" "%d" "%d" "%d" "%d" "", host_iterator_start_time (&hosts), host_iterator_end_time (&hosts) ? host_iterator_end_time (&hosts) : "", ports_count, (holes_count + warnings_count + infos_count + logs_count + false_positives_count), holes_count, warnings_count, infos_count, logs_count, false_positives_count); if (print_report_host_details_xml (host_iterator_report_host (&hosts), out, lean)) { tz_revert (zone, tz, old_tz_override); if (host_summary_buffer) g_string_free (host_summary_buffer, TRUE); g_hash_table_destroy (f_host_ports); g_hash_table_destroy (f_host_holes); g_hash_table_destroy (f_host_warnings); g_hash_table_destroy (f_host_infos); g_hash_table_destroy (f_host_logs); g_hash_table_destroy (f_host_false_positives); return -1; } PRINT (out, ""); } cleanup_iterator (&hosts); } g_hash_table_destroy (f_host_ports); g_hash_table_destroy (f_host_holes); g_hash_table_destroy (f_host_warnings); g_hash_table_destroy (f_host_infos); g_hash_table_destroy (f_host_logs); g_hash_table_destroy (f_host_false_positives); end_time = scan_end_time (report); PRINT (out, "%s", end_time); free (end_time); if (delta == 0 && print_report_errors_xml (report, out)) { tz_revert (zone, tz, old_tz_override); if (host_summary_buffer) g_string_free (host_summary_buffer, TRUE); return -1; } g_free (sort_field); g_free (levels); g_free (search_phrase); g_free (min_qod); g_free (delta_states); if (host_summary && host_summary_buffer) *host_summary = g_string_free (host_summary_buffer, FALSE); if (fclose (out)) { g_warning ("%s: fclose failed: %s", __func__, strerror (errno)); return -1; } return 0; } /** * @brief Generate a report. * * @param[in] report Report. * @param[in] delta_report Report to compare with. * @param[in] get GET data for report. * @param[in] report_format Report format. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides_details If overrides, Whether to include details. * @param[out] output_length NULL or location for length of return. * @param[out] extension NULL or location for report format extension. * Only defined on success. * @param[out] content_type NULL or location for report format content * type. Only defined on success. * @param[out] filter_term_return Filter term used in report. * @param[out] zone_return Actual timezone used in report. * @param[out] host_summary Summary of results per host. * * @return Contents of report on success, NULL on error. */ gchar * manage_report (report_t report, report_t delta_report, const get_data_t *get, const report_format_t report_format, int notes_details, int overrides_details, gsize *output_length, gchar **extension, gchar **content_type, gchar **filter_term_return, gchar **zone_return, gchar **host_summary) { task_t task; gchar *xml_start, *xml_file, *output_file; char xml_dir[] = "/tmp/gvmd_XXXXXX"; int ret; GList *used_rfps; GError *get_error; gchar *output; gsize output_len; used_rfps = NULL; /* Print the report as XML to a file. */ if (((report_format_predefined (report_format) == 0) && (report_format_trust (report_format) != TRUST_YES)) || (report_task (report, &task))) { return NULL; } if (mkdtemp (xml_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return NULL; } xml_start = g_strdup_printf ("%s/report-start.xml", xml_dir); ret = print_report_xml_start (report, delta_report, task, xml_start, get, notes_details, overrides_details, 1 /* result_tags */, 0 /* ignore_pagination */, 0 /* lean */, filter_term_return, zone_return, host_summary); if (ret) { g_free (xml_start); gvm_file_remove_recurse (xml_dir); return NULL; } xml_file = g_strdup_printf ("%s/report.xml", xml_dir); if (report_format > 0) { /* Apply report format(s) */ gchar* report_format_id = report_format_uuid (report_format); output_file = apply_report_format (report_format_id, xml_start, xml_file, xml_dir, &used_rfps); g_free (report_format_id); } else { print_report_xml_end(xml_start, xml_file, -1); output_file = g_strdup(xml_file); } if (output_file == NULL) { g_warning ("%s: No file returned for report format", __func__); } g_free (xml_file); g_free (xml_start); /* Read the script output from file. */ if (output_file == NULL) { gvm_file_remove_recurse (xml_dir); return NULL; } get_error = NULL; g_file_get_contents (output_file, &output, &output_len, &get_error); g_free (output_file); if (get_error) { g_warning ("%s: Failed to get output: %s", __func__, get_error->message); g_error_free (get_error); if (extension) g_free (*extension); if (content_type) g_free (*content_type); gvm_file_remove_recurse (xml_dir); return NULL; } /* Remove the directory. */ gvm_file_remove_recurse (xml_dir); /* Set convenience return parameters. */ if ((report_format > 0) && (extension || content_type)) { iterator_t formats; get_data_t report_format_get; memset (&report_format_get, '\0', sizeof (report_format_get)); report_format_get.id = report_format_uuid(report_format); init_report_format_iterator (&formats, &report_format_get); if (next (&formats) == FALSE) { g_free (report_format_get.id); cleanup_iterator (&formats); return NULL; } assert (report_format_iterator_extension (&formats)); assert (report_format_iterator_content_type (&formats)); if (extension) *extension = g_strdup (report_format_iterator_extension (&formats)); if (content_type) *content_type = g_strdup (report_format_iterator_content_type (&formats)); cleanup_iterator (&formats); g_free (report_format_get.id); } /* Return the output. */ if (output_length) *output_length = output_len; return output; } /** * @brief Size of base64 chunk in manage_send_report. */ #define MANAGE_SEND_REPORT_CHUNK64_SIZE 262144 /** * @brief Size of file chunk in manage_send_report. */ #define MANAGE_SEND_REPORT_CHUNK_SIZE (MANAGE_SEND_REPORT_CHUNK64_SIZE * 3 / 4) /** * @brief Generate a report. * * @param[in] report Report. * @param[in] delta_report Report to compare with. * @param[in] report_format Report format. * @param[in] get GET command data. * @param[in] notes_details If notes, Whether to include details. * @param[in] overrides_details If overrides, Whether to include details. * @param[in] result_tags Whether to include tags in results. * @param[in] ignore_pagination Whether to ignore pagination. * @param[in] lean Whether to send lean report. * @param[in] base64 Whether to base64 encode the report. * @param[in] send Function to write to client. * @param[in] send_data_1 Second argument to \p send. * @param[in] send_data_2 Third argument to \p send. * @param[in] alert_id ID of alert to escalate report with, * instead of getting report. NULL to get * report. * @param[in] prefix Text to send to client before the report. * * @return 0 success, -1 error, -2 failed to find alert report format, -3 error * during alert, -4 failed to find alert filter, 1 failed to find alert, * 2 failed to find filter (before anything sent to client). */ int manage_send_report (report_t report, report_t delta_report, report_format_t report_format, const get_data_t *get, int notes_details, int overrides_details, int result_tags, int ignore_pagination, int lean, int base64, gboolean (*send) (const char *, int (*) (const char *, void*), void*), int (*send_data_1) (const char *, void*), void *send_data_2, const char *alert_id, const gchar* prefix) { task_t task; gchar *xml_start, *xml_file; char xml_dir[] = "/tmp/gvmd_XXXXXX"; int ret; GList *used_rfps; gchar *output_file; char chunk[MANAGE_SEND_REPORT_CHUNK_SIZE + 1]; FILE *stream; used_rfps = NULL; if (report_task (report, &task)) return -1; /* Escalate instead, if requested. */ if (alert_id) { alert_t alert = 0; alert_condition_t condition; alert_method_t method; if (find_alert_with_permission (alert_id, &alert, "get_alerts")) return -1; if (alert == 0) return 1; condition = alert_condition (alert); method = alert_method (alert); ret = escalate_2 (alert, task, report, EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_DONE, method, condition, get, notes_details, overrides_details, NULL); if (ret == -3) return -4; if (ret == -1) return -3; if (ret) return -2; return 0; } /* Print the report as XML to a file. */ if ((report_format > 0) && (report_format_predefined (report_format) == 0) && (report_format_trust (report_format) != TRUST_YES)) return -1; if (mkdtemp (xml_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return -1; } xml_start = g_strdup_printf ("%s/report-start.xml", xml_dir); ret = print_report_xml_start (report, delta_report, task, xml_start, get, notes_details, overrides_details, result_tags, ignore_pagination, lean, NULL, NULL, NULL); if (ret) { g_free (xml_start); gvm_file_remove_recurse (xml_dir); if (ret == 2) return 2; return -1; } xml_file = g_strdup_printf ("%s/report.xml", xml_dir); if (report_format > 0) { /* Apply report format(s). */ gchar* report_format_id = report_format_uuid (report_format); output_file = apply_report_format (report_format_id, xml_start, xml_file, xml_dir, &used_rfps); g_free (report_format_id); } else { print_report_xml_end(xml_start, xml_file, -1); output_file = g_strdup(xml_file); } if (output_file == NULL) { g_warning ("%s: No file returned for report format", __func__); } /* Send the report. */ /* Read the script output from file in chunks, sending to client. */ stream = fopen (output_file, "r"); g_free (output_file); if (stream == NULL) { g_warning ("%s: %s", __func__, strerror (errno)); gvm_file_remove_recurse (xml_dir); return -1; } if (prefix && send (prefix, send_data_1, send_data_2)) { fclose (stream); g_warning ("%s: send prefix error", __func__); gvm_file_remove_recurse (xml_dir); return -1; } while (1) { int left; char *dest; /* Read a chunk. */ left = MANAGE_SEND_REPORT_CHUNK_SIZE; dest = chunk; while (1) { ret = fread (dest, 1, left, stream); if (ferror (stream)) { fclose (stream); g_warning ("%s: error after fread", __func__); gvm_file_remove_recurse (xml_dir); return -1; } left -= ret; if (left == 0) break; if (feof (stream)) break; dest += ret; } /* Send the chunk. */ if (left < MANAGE_SEND_REPORT_CHUNK_SIZE) { if (base64) { gchar *chunk64; chunk64 = g_base64_encode ((guchar*) chunk, MANAGE_SEND_REPORT_CHUNK_SIZE - left); if (send (chunk64, send_data_1, send_data_2)) { g_free (chunk64); fclose (stream); g_warning ("%s: send error", __func__); gvm_file_remove_recurse (xml_dir); return -1; } g_free (chunk64); } else { chunk[MANAGE_SEND_REPORT_CHUNK_SIZE - left] = '\0'; if (send (chunk, send_data_1, send_data_2)) { fclose (stream); g_warning ("%s: send error", __func__); gvm_file_remove_recurse (xml_dir); return -1; } } } /* Check if there's more. */ if (feof (stream)) break; } fclose (stream); /* Remove the directory. */ gvm_file_remove_recurse (xml_dir); /* Return the output. */ return 0; } /** * @brief Get the IP of a host, using the 'hostname' report host details. * * The most recent host detail takes preference. * * @param[in] host Host name or IP. * * @return Newly allocated UUID if available, else NULL. */ gchar* report_host_ip (const char *host) { gchar *quoted_host, *ret; quoted_host = sql_quote (host); ret = sql_string ("SELECT host FROM report_hosts" " WHERE id = (SELECT report_host FROM report_host_details" " WHERE name = 'hostname'" " AND value = '%s'" " ORDER BY id DESC LIMIT 1);", quoted_host); g_free (quoted_host); return ret; } /** * @brief Check if a report host is alive and has at least one result. * * @param[in] report Report. * @param[in] host Host name or IP. * * @return 0 if dead, else alive. */ int report_host_noticeable (report_t report, const gchar *host) { report_host_t report_host = 0; sql_int64 (&report_host, "SELECT id FROM report_hosts" " WHERE report = %llu AND host = '%s';", report, host); return report_host && report_host_dead (report_host) == 0 && report_host_result_count (report_host) > 0; } /** * @brief Parse an OSP report. * * @param[in] task Task. * @param[in] report Report. * @param[in] report_xml Report XML. */ void parse_osp_report (task_t task, report_t report, const char *report_xml) { entity_t entity, child; entities_t results; const char *str; char *defs_file = NULL; time_t start_time, end_time; gboolean has_results = FALSE; GArray *results_array; assert (task); assert (report); assert (report_xml); if (parse_entity (report_xml, &entity)) { g_warning ("Couldn't parse %s OSP scan report", report_xml); return; } sql_begin_immediate (); /* Set the report's start and end times. */ results_array = g_array_new (TRUE, TRUE, sizeof (result_t)); start_time = 0; str = entity_attribute (entity, "start_time"); if (str) { start_time = atoi (str); set_scan_start_time_epoch (report, start_time); } end_time = 0; str = entity_attribute (entity, "end_time"); if (str) { end_time = atoi (str); set_scan_end_time_epoch (report, end_time); } /* Insert results. */ child = entity_child (entity, "results"); if (!child) { g_warning ("Missing results element in OSP report %s", report_xml); goto end_parse_osp_report; } results = child->entities; if (results) has_results = TRUE; defs_file = task_definitions_file (task); while (results) { result_t result; const char *type, *name, *severity, *host, *hostname, *test_id, *port; const char *qod, *path; char *desc = NULL, *nvt_id = NULL, *severity_str = NULL; entity_t r_entity = results->data; int qod_int; if (strcmp (entity_name (r_entity), "result")) { g_warning ("Erroneous entry in OSP results %s", entity_name (r_entity)); results = next_entities (results); continue; } type = entity_attribute (r_entity, "type"); name = entity_attribute (r_entity, "name"); severity = entity_attribute (r_entity, "severity"); test_id = entity_attribute (r_entity, "test_id"); host = entity_attribute (r_entity, "host"); hostname = entity_attribute (r_entity, "hostname"); port = entity_attribute (r_entity, "port") ?: ""; qod = entity_attribute (r_entity, "qod") ?: ""; path = entity_attribute (r_entity, "uri") ?: ""; if (!name || !type || !severity || !test_id || !host) { GString *string = g_string_new (""); print_entity_to_string (r_entity, string); g_warning ("Erroneous attribute in OSP result %s", string->str); g_string_free (string, TRUE); results = next_entities (results); continue; } /* Add report host if it doesn't exist. */ manage_report_host_add (report, host, start_time, end_time); if (!strcmp (type, "Host Detail")) { insert_report_host_detail (report, host, "osp", "", "OSP Host Detail", name, entity_text (r_entity)); results = next_entities (results); continue; } else if (g_str_has_prefix (test_id, "1.3.6.1.4.1.25623.1.")) { nvt_id = g_strdup (test_id); severity_str = nvt_severity (test_id, type); desc = g_strdup (entity_text (r_entity)); } else if (g_str_has_prefix (test_id, "oval:")) { nvt_id = ovaldef_uuid (test_id, defs_file); severity_str = ovaldef_severity (nvt_id); } else { nvt_id = g_strdup (name); desc = g_strdup (entity_text (r_entity)); } qod_int = atoi (qod); if (qod_int <= 0 || qod_int > 100) qod_int = QOD_DEFAULT; if (port && strcmp (port, "general/Host_Details") == 0) { /* TODO: This should probably be handled by the "Host Detail" * result type with extra source info in OSP. */ if (manage_report_host_detail (report, host, desc)) g_warning ("%s: Failed to add report detail for host '%s': %s", __func__, host, desc); } else if (host && nvt_id && desc && (strcmp (nvt_id, "HOST_START") == 0)) { set_scan_host_start_time_ctime (report, host, desc); } else if (host && nvt_id && desc && (strcmp (nvt_id, "HOST_END") == 0)) { set_scan_host_end_time_ctime (report, host, desc); add_assets_from_host_in_report (report, host); } else { result = make_osp_result (task, host, hostname, nvt_id, type, desc, port ?: "", severity_str ?: severity, qod_int, path); g_array_append_val (results_array, result); } g_free (nvt_id); g_free (desc); g_free (severity_str); results = next_entities (results); } if (has_results) { report_add_results_array (report, results_array); } end_parse_osp_report: sql_commit (); g_array_free (results_array, TRUE); g_free (defs_file); free_entity (entity); } /* More task stuff. */ /** * @brief Return the trend of a task, given counts. * * @param[in] holes_a Number of holes on earlier report. * @param[in] warns_a Number of warnings on earlier report. * @param[in] infos_a Number of infos on earlier report. * @param[in] severity_a Severity of earlier report. * @param[in] holes_b Number of holes on later report. * @param[in] warns_b Number of warnings on later report. * @param[in] infos_b Number of infos on later report. * @param[in] severity_b Severity of later report. * * @return "up", "down", "more", "less", "same" or if too few reports "". */ static const char * task_trend_calc (int holes_a, int warns_a, int infos_a, double severity_a, int holes_b, int warns_b, int infos_b, double severity_b) { int threat_a, threat_b; /* Check if the severity score changed. */ if (severity_a > severity_b) return "up"; if (severity_a < severity_b) return "down"; /* Calculate trend. */ if (holes_a > 0) threat_a = 4; else if (warns_a > 0) threat_a = 3; else if (infos_a > 0) threat_a = 2; else threat_a = 1; if (holes_b > 0) threat_b = 4; else if (warns_b > 0) threat_b = 3; else if (infos_b > 0) threat_b = 2; else threat_b = 1; /* Check if the threat level changed. */ if (threat_a > threat_b) return "up"; if (threat_a < threat_b) return "down"; /* Check if the threat count changed in the highest level. */ if (holes_a) { if (holes_a > holes_b) return "more"; if (holes_a < holes_b) return "less"; return "same"; } if (warns_a) { if (warns_a > warns_b) return "more"; if (warns_a < warns_b) return "less"; return "same"; } if (infos_a) { if (infos_a > infos_b) return "more"; if (infos_a < infos_b) return "less"; return "same"; } return "same"; } /** * @brief Return the trend of a task, given counts. * * @param[in] iterator Task iterator. * @param[in] holes_a Number of holes on earlier report. * @param[in] warns_a Number of warnings on earlier report. * @param[in] infos_a Number of infos on earlier report. * @param[in] severity_a Severity score of earlier report. * @param[in] holes_b Number of holes on later report. * @param[in] warns_b Number of warnings on later report. * @param[in] infos_b Number of infos on later report. * @param[in] severity_b Severity score of later report. * * @return "up", "down", "more", "less", "same" or if too few reports "". */ const char * task_iterator_trend_counts (iterator_t *iterator, int holes_a, int warns_a, int infos_a, double severity_a, int holes_b, int warns_b, int infos_b, double severity_b) { /* Ensure there are enough reports. */ if (task_iterator_finished_reports (iterator) <= 1) return ""; /* Skip running tasks. */ if (task_iterator_run_status (iterator) == TASK_STATUS_RUNNING) return ""; return task_trend_calc (holes_a, warns_a, infos_a, severity_a, holes_b, warns_b, infos_b, severity_b); } /** * @brief Make a task. * * The char* parameters name and comment are used directly and freed * when the task is freed. * * @param[in] name The name of the task. * @param[in] comment A comment associated the task. * @param[in] in_assets Whether task must be considered for assets. * @param[in] event Whether to be generate event and event log. * * @return A pointer to the new task. */ task_t make_task (char* name, char* comment, int in_assets, int event) { task_t task; char* uuid = gvm_uuid_make (); gchar *quoted_name, *quoted_comment; if (uuid == NULL) abort (); quoted_name = name ? sql_quote ((gchar*) name) : NULL; quoted_comment = comment ? sql_quote ((gchar*) comment) : NULL; sql ("INSERT into tasks" " (owner, uuid, name, hidden, comment, schedule," " schedule_next_time, config_location, target, target_location," " scanner_location, schedule_location, alterable," " creation_time, modification_time, usage_type)" " VALUES ((SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '%s', 0, '%s', 0, 0, 0, 0, 0, 0, 0, 0, m_now ()," " m_now (), 'scan');", current_credentials.uuid, uuid, quoted_name ? quoted_name : "", quoted_comment ? quoted_comment : ""); task = sql_last_insert_id (); if (event) set_task_run_status (task, TASK_STATUS_NEW); else set_task_run_status_internal (task, TASK_STATUS_NEW); sql ("INSERT INTO task_preferences (task, name, value)" " VALUES (%llu, 'in_assets', '%s')", task, in_assets ? "yes" : "no"); sql ("INSERT INTO task_preferences (task, name, value)" " VALUES (%llu, 'assets_apply_overrides', 'yes')", task); sql ("INSERT INTO task_preferences (task, name, value)" " VALUES (%llu, 'assets_min_qod', %d)", task, MIN_QOD_DEFAULT); free (uuid); free (name); free (comment); g_free (quoted_name); g_free (quoted_comment); return task; } /** * @brief Complete the creation of a task. * * @param[in] task The task. */ void make_task_complete (task_t task) { assert (task); cache_permissions_for_resource ("task", task, NULL); event (EVENT_TASK_RUN_STATUS_CHANGED, (void*) TASK_STATUS_NEW, task, 0); } /** * @brief Set the name of a task. * * @param[in] task A task. * @param[in] name New name. */ void set_task_name (task_t task, const char *name) { gchar *quoted_name; quoted_name = sql_quote (name ? name : ""); sql ("UPDATE tasks SET name = '%s', modification_time = m_now ()" " WHERE id = %llu;", quoted_name, task); g_free (quoted_name); } /** * @brief Set the comment of a task. * * @param[in] task A task. * @param[in] comment New comment. */ static void set_task_comment (task_t task, const char *comment) { gchar *quoted_comment; assert (comment); quoted_comment = sql_quote (comment ? comment : ""); sql ("UPDATE tasks SET comment = '%s', modification_time = m_now ()" " WHERE id = %llu;", quoted_comment, task); g_free (quoted_comment); } /** * @brief Create a task from an existing task. * * @param[in] name Name of new task. NULL to copy from existing. * @param[in] comment Comment on new task. NULL to copy from existing. * @param[in] task_id UUID of existing task. * @param[in] alterable Whether the new task will be alterable. < 0 to * to copy from existing. * @param[out] new_task New task. * * @return 0 success, 2 failed to find existing task, 99 permission denied, * -1 error. */ int copy_task (const char* name, const char* comment, const char *task_id, int alterable, task_t* new_task) { task_t new, old; int ret; assert (current_credentials.uuid); if (task_id == NULL) return -1; sql_begin_immediate (); ret = copy_resource_lock ("task", name, comment, task_id, "config, target, schedule, schedule_periods," " scanner, schedule_next_time," " config_location, target_location," " schedule_location, scanner_location," " hosts_ordering, usage_type, alterable", 1, &new, &old); if (ret) { sql_rollback (); return ret; } if (alterable >= 0) sql ("UPDATE tasks SET alterable = %i, hidden = 0 WHERE id = %llu;", alterable, new); else sql ("UPDATE tasks SET hidden = 0 WHERE id = %llu;", new); set_task_run_status (new, TASK_STATUS_NEW); sql ("INSERT INTO task_preferences (task, name, value)" " SELECT %llu, name, value FROM task_preferences" " WHERE task = %llu;", new, old); sql ("INSERT INTO task_alerts (task, alert, alert_location)" " SELECT %llu, alert, alert_location FROM task_alerts" " WHERE task = %llu;", new, old); sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource, resource_uuid," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " SELECT make_uuid (), (SELECT owner FROM tasks WHERE id = %llu)," " name, comment, resource_type, %llu," " (SELECT uuid FROM tasks WHERE id = %llu)," " resource_location, subject_type, subject, subject_location," " m_now (), m_now ()" " FROM permissions" " WHERE owner = (SELECT owner FROM tasks WHERE id = %llu)" " AND resource_type = 'task'" " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) " AND resource = %llu;", new, new, new, old, old); if (ret) { sql_rollback (); return ret; } cache_permissions_for_resource ("task", new, NULL); sql_commit (); if (new_task) *new_task = new; return 0; } /** * @brief Complete deletion of a task. * * This sets up a transaction around the delete. * * @param[in] task The task. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 on success, 1 if task is hidden, -1 on error. */ static int delete_task_lock (task_t task, int ultimate) { int ret, lock_ret, lock_retries; g_debug (" delete task %llu", task); sql_begin_immediate (); /* This prevents other processes (for example a START_TASK) from getting * a reference to a report ID or the task ID, and then using that * reference to try access the deleted report or task. * * If the task is already active then delete_report (via delete_task) * will fail and rollback. */ lock_retries = LOCK_RETRIES; lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); while ((lock_ret == 0) && (lock_retries > 0)) { sleep(LOCK_RETRY_DELAY); lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); lock_retries--; } if (lock_ret == 0) { sql_rollback (); return -1; } if (sql_int ("SELECT hidden FROM tasks WHERE id = %llu;", task)) { sql_rollback (); return -1; } ret = delete_task (task, ultimate); if (ret) sql_rollback (); else sql_commit (); return ret; } /** * @brief Request deletion of a task. * * Stop the task beforehand with \ref stop_task_internal, if it is running. * * Used only for CREATE_TASK in gmp.c. Always ultimate. * * @param[in] task_pointer A pointer to the task. * * @return 0 if deleted, 1 if delete requested, 2 if task is hidden, * -1 if error, -5 if scanner is down. */ int request_delete_task (task_t* task_pointer) { task_t task = *task_pointer; int hidden; g_debug (" request delete task %llu", task); hidden = sql_int ("SELECT hidden from tasks WHERE id = %llu;", *task_pointer); /* Technically the task could be in the trashcan, if someone gets the UUID * with GET_TASKS before the CREATE_TASK finishes, and removes the task. * Pretend it was deleted. There'll be half a task in the trashcan. */ if (hidden == 2) return 0; if (current_credentials.uuid == NULL) return -1; switch (stop_task_internal (task)) { case 0: /* Stopped. */ return delete_task_lock (task, 1); case 1: /* Stop requested. */ set_task_run_status (task, TASK_STATUS_DELETE_ULTIMATE_REQUESTED); return 1; default: /* Programming error. */ assert (0); case -1: /* Error. */ return -1; break; case -5: /* Scanner down. */ return -5; break; } return 0; } /** * @brief Request deletion of a task. * * Stop the task beforehand with \ref stop_task_internal, if it is running. * * This is only used for DELETE_TASK in gmp.c. * * @param[in] task_id UUID of task. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 deleted, 1 delete requested, 2 task is hidden, 3 failed to find * task, 99 permission denied, -1 error, -5 scanner is down, -7 no CA * cert. */ int request_delete_task_uuid (const char *task_id, int ultimate) { task_t task = 0; int lock_ret, lock_retries; /* Tasks have special handling for the trashcan. Other resources have trash * tables, like targets_trash. Tasks are marked as trash in the tasks table * by giving the "hidden" field a value of 2. This means that the results can * stay in the results table and will still refer to the correct task. This * should all work because there is already handling of the hidden flag * everywhere else. */ g_debug (" request delete task %s", task_id); sql_begin_immediate (); if (acl_user_may ("delete_task") == 0) { sql_rollback (); return 99; } if (find_task_with_permission (task_id, &task, "delete_task")) { sql_rollback (); return -1; } if (task == 0) { if (find_trash_task (task_id, &task)) { sql_rollback (); return -1; } if (task == 0) { sql_rollback (); return 3; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } if (delete_reports (task)) { sql_rollback (); return -1; } permissions_set_orphans ("task", task, LOCATION_TRASH); tags_remove_resource ("task", task, LOCATION_TRASH); tickets_remove_task (task); sql ("DELETE FROM results WHERE task = %llu;", task); sql ("DELETE FROM task_alerts WHERE task = %llu;", task); sql ("DELETE FROM task_files WHERE task = %llu;", task); sql ("DELETE FROM task_preferences WHERE task = %llu;", task); sql ("DELETE FROM tasks WHERE id = %llu;", task); sql_commit (); return 0; } if (current_credentials.uuid == NULL) { sql_rollback (); return -1; } switch (stop_task_internal (task)) { case 0: /* Stopped. */ { int ret; if (ultimate) { /* This prevents other processes (for example a START_TASK) from * getting a reference to a report ID or the task ID, and then * using that reference to try access the deleted report or task. * * If the task is running already then delete_task will lead to * ROLLBACK. */ lock_retries = LOCK_RETRIES; lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); while ((lock_ret == 0) && (lock_retries > 0)) { sleep(LOCK_RETRY_DELAY); lock_ret = sql_int ("SELECT try_exclusive_lock('reports');"); lock_retries--; } if (lock_ret == 0) { sql_rollback (); return -1; } } ret = delete_task (task, ultimate); if (ret) sql_rollback (); else sql_commit (); return ret; } case 1: /* Stop requested. */ if (ultimate) set_task_run_status (task, TASK_STATUS_DELETE_ULTIMATE_REQUESTED); else set_task_run_status (task, TASK_STATUS_DELETE_REQUESTED); sql_commit (); return 1; default: /* Programming error. */ assert (0); case -1: /* Error. */ sql_rollback (); return -1; break; case -5: /* Scanner down. */ sql_rollback (); return -5; break; case -7: /* No CA cert. */ sql_rollback (); return -5; break; } sql_commit (); return 0; } /** * @brief Complete deletion of a task. * * The caller must do the locking, and must do the hidden check. * * The caller must handle the case where the task is already in the trashcan. * * @param[in] task The task. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 on success, -1 on error. */ int delete_task (task_t task, int ultimate) { g_debug (" delete task %llu", task); assert (current_credentials.uuid); if (ultimate) { if (delete_reports (task)) return -1; permissions_set_orphans ("task", task, task_in_trash (task) ? LOCATION_TRASH : LOCATION_TABLE); tags_remove_resource ("task", task, task_in_trash (task) ? LOCATION_TRASH : LOCATION_TABLE); tickets_remove_task (task); sql ("DELETE FROM results_trash WHERE task = %llu;", task); sql ("DELETE FROM results WHERE task = %llu;", task); sql ("DELETE FROM task_alerts WHERE task = %llu;", task); sql ("DELETE FROM task_files WHERE task = %llu;", task); sql ("DELETE FROM task_preferences WHERE task = %llu;", task); sql ("DELETE FROM tasks WHERE id = %llu;", task); } else { permissions_set_locations ("task", task, task, LOCATION_TRASH); tags_set_locations ("task", task, task, LOCATION_TRASH); sql ("UPDATE tag_resources" " SET resource_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE resource_type = 'report'" " AND resource IN (SELECT id FROM reports" " WHERE reports.task = %llu);", task); sql ("UPDATE tag_resources" " SET resource_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE resource_type = 'result'" " AND resource IN (SELECT id FROM results" " WHERE results.task = %llu);", task); sql ("UPDATE tag_resources_trash" " SET resource_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE resource_type = 'report'" " AND resource IN (SELECT id FROM reports" " WHERE reports.task = %llu);", task); sql ("UPDATE tag_resources_trash" " SET resource_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE resource_type = 'result'" " AND resource IN (SELECT id FROM results" " WHERE results.task = %llu);", task); sql ("INSERT INTO results_trash" " (uuid, task, host, port, nvt, result_nvt, type, description," " report, nvt_version, severity, qod, qod_type, owner, date," " hostname, path)" " SELECT uuid, task, host, port, nvt, result_nvt, type," " description, report, nvt_version, severity, qod," " qod_type, owner, date, hostname, path" " FROM results" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", task); tickets_trash_task (task); sql ("DELETE FROM results" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", task); sql ("DELETE FROM report_counts" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", task); sql ("UPDATE tasks SET hidden = 2 WHERE id = %llu;", task); } delete_permissions_cache_for_resource ("task", task); return 0; } /** * @brief Delete all trash tasks. * * The caller must do the transaction. * * @return 0 on success, -1 on error. */ static int delete_trash_tasks () { iterator_t tasks; init_user_task_iterator (&tasks, 1, 1); while (next (&tasks)) { task_t task; task = get_iterator_resource (&tasks); if (delete_reports (task)) { cleanup_iterator (&tasks); return -1; } tickets_remove_task (task); sql ("DELETE FROM results WHERE task = %llu;", task); sql ("DELETE FROM task_alerts WHERE task = %llu;", task); sql ("DELETE FROM task_files WHERE task = %llu;", task); sql ("DELETE FROM task_preferences WHERE task = %llu;", task); sql ("DELETE FROM tasks WHERE id = %llu;", task); } cleanup_iterator (&tasks); return 0; } /** * @brief Fixes the DST offset in schedule_next_time of tasks. * * @return changes The number of tasks updated. */ static int cleanup_schedule_times () { sql ("UPDATE tasks" " SET schedule_next_time" " = (SELECT next_time_ical (icalendar, timezone)" " FROM schedules" " WHERE schedules.id = tasks.schedule)" " WHERE schedule_next_time != 0" " AND schedule_next_time" " = (SELECT next_time_ical (icalendar, timezone)" " FROM schedules" " WHERE schedules.id = tasks.schedule)" " AND schedule_next_time" " != (SELECT next_time_ical (icalendar, timezone)" " FROM schedules" " WHERE schedules.id = tasks.schedule);"); return sql_changes(); } /** * @brief Append text to the comment associated with a task. * * @param[in] task A pointer to the task. * @param[in] text The text to append. * @param[in] length Length of the text. */ void append_to_task_comment (task_t task, const char* text, /* unused */ int length) { append_to_task_string (task, "comment", text); } /** * @brief Set the ports for a particular host in a scan. * * @param[in] report Report associated with scan. * @param[in] host Host. * @param[in] current New value for port currently being scanned. * @param[in] max New value for last port to be scanned. */ void set_scan_ports (report_t report, const char* host, unsigned int current, unsigned int max) { sql ("UPDATE report_hosts SET current_port = %i, max_port = %i" " WHERE host = '%s' AND report = %llu;", current, max, host, report); } /** * @brief Find a task for a specific permission, given a UUID. * * @param[in] uuid UUID of task. * @param[out] task Task return, 0 if successfully failed to find task. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find task), TRUE on error. */ gboolean find_task_with_permission (const char* uuid, task_t* task, const char *permission) { return find_resource_with_permission ("task", uuid, task, permission, 0); } /** * @brief Find a task in the trashcan for a specific permission, given a UUID. * * @param[in] uuid UUID of task. * @param[out] task Task return, 0 if successfully failed to find task. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find task), TRUE on error. */ gboolean find_trash_task_with_permission (const char* uuid, task_t* task, const char *permission) { return find_resource_with_permission ("task", uuid, task, permission, 1); } /** * @brief Find a task in the trashcan, given an identifier. * * @param[in] uuid A task identifier. * @param[out] task Task return, 0 if successfully failed to find task. * * @return FALSE on success (including if failed to find task), TRUE on error. */ static gboolean find_trash_task (const char* uuid, task_t* task) { if (acl_user_owns_uuid ("task", uuid, 1) == 0) { *task = 0; return FALSE; } switch (sql_int64 (task, "SELECT id FROM tasks WHERE uuid = '%s'" " AND hidden = 2;", uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ *task = 0; break; default: /* Programming error. */ assert (0); case -1: return TRUE; break; } return FALSE; } /** * @brief Find a report for a specific permission, given a UUID. * * @param[in] uuid UUID of report. * @param[out] report Report return, 0 if successfully failed to find * report. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find report), TRUE on error. */ gboolean find_report_with_permission (const char* uuid, report_t* report, const char *permission) { return find_resource_with_permission ("report", uuid, report, permission, 0); } /** * @brief Find a report in the trashcan for a specific permission, given a UUID. * * @param[in] uuid UUID of report. * @param[out] report Report return, 0 if successfully failed to find * report. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find report), TRUE on error. */ static gboolean find_trash_report_with_permission (const char *uuid, report_t *report, const char *permission) { return find_resource_with_permission ("report", uuid, report, permission, 1); } /** * @brief Reset all running information for a task. * * @param[in] task Task. */ void reset_task (task_t task) { sql ("UPDATE tasks SET" " start_time = 0," " end_time = 0" " WHERE id = %llu;", task); } /** * @brief Add a file to a task, or update the file on the task. * * @param[in] task_id Task. * @param[in] name Name of file. * @param[in] content Content for file in base64 encoding. * * @return 0 success, 1 failed to find task, -1 error. */ int manage_task_update_file (const gchar *task_id, const char *name, const void *content) { gchar* quoted_name = sql_quote (name); gchar* quoted_content = sql_quote (content); task_t task; /** @todo Probably better to save ASCII instead of base64. */ task = 0; if (find_task_with_permission (task_id, &task, "modify_task")) return -1; else if (task == 0) return 1; if (sql_int ("SELECT count(*) FROM task_files" " WHERE task = %llu AND name = '%s';", task, quoted_name)) { /* Update the existing file. */ sql ("UPDATE task_files SET content = '%s'" " WHERE task = %llu AND name = '%s';", quoted_content, task, quoted_name); } else { /* Insert the file. */ sql ("INSERT INTO task_files (task, name, content)" " VALUES (%llu, '%s', '%s');", task, quoted_name, quoted_content); } sql ("UPDATE tasks SET modification_time = m_now () WHERE id = %llu;", task); g_free (quoted_name); g_free (quoted_content); return 0; } /** * @brief Remove a file on a task. * * @param[in] task_id Task. * @param[in] name Name of file. * * @return 0 success, 1 failed to find task, -1 error. */ int manage_task_remove_file (const gchar *task_id, const char *name) { task_t task; task = 0; if (find_task_with_permission (task_id, &task, "modify_task")) return -1; else if (task == 0) return 1; if (sql_int ("SELECT count(*) FROM task_files" " WHERE task = %llu AND name = '%s';", task)) { gchar* quoted_name = sql_quote (name); sql ("DELETE FROM task_files WHERE task = %llu AND name = '%s';", task, quoted_name); sql ("UPDATE tasks SET modification_time = m_now () WHERE id = %llu;", task); g_free (quoted_name); return 0; } return -1; } /** * @brief Initialise a task file iterator. * * @param[in] iterator Iterator. * @param[in] task Task. * @param[in] file File name, NULL for all files. */ void init_task_file_iterator (iterator_t* iterator, task_t task, const char* file) { gchar* sql; if (file) { gchar *quoted_file = sql_nquote (file, strlen (file)); sql = g_strdup_printf ("SELECT name, content, length(content)" " FROM task_files" " WHERE task = %llu" " AND name = '%s';", task, quoted_file); g_free (quoted_file); } else sql = g_strdup_printf ("SELECT name, content, length(content)" " FROM task_files" " WHERE task = %llu;", task); init_iterator (iterator, "%s", sql); g_free (sql); } /** * @brief Get the name of the file from a task file iterator. * * @param[in] iterator Iterator. * * @return Name of the file or NULL if iteration is complete. */ DEF_ACCESS (task_file_iterator_name, 0); /** * @brief Get the content of the file from a task file iterator. * * @param[in] iterator Iterator. * * @return Content of the file or NULL if iteration is complete. */ DEF_ACCESS (task_file_iterator_content, 1); /** * @brief Modify a task. * * @param[in] task_id Task. * @param[in] name Name of file. * @param[in] comment Comment. * @param[in] scanner_id Scanner. * @param[in] target_id Target. * @param[in] config_id Config. * @param[in] observers Observers. * @param[in] alerts Alerts. * @param[in] alterable Alterable. * @param[in] groups Groups. * @param[in] schedule_id Schedule. * @param[in] schedule_periods Period of schedule. * @param[in] preferences Preferences. * @param[in] hosts_ordering Host scan order. * @param[out] fail_alert_id Alert when failed to find alert. * @param[out] fail_group_id Group when failed to find group. * * @return 0 success, 1 failed to find task, 2 status must be new to edit * scanner, 3 failed to find scanner, 4 failed to find config, 5 status * must be new to edit config, 6 user name validation failed, 7 failed * to find user, 8 failed to find alert, 9 task must be new to modify * alterable state, 10 failed to find group, 11 failed to find schedule, * 12 failed to find target, 13 invalid auto_delete value, 14 auto * delete count out of range, 15 config and scanner types mismatch, * 16 status must be new to edit target, 17 for container tasks only * certain fields may be edited, -1 error. */ int modify_task (const gchar *task_id, const gchar *name, const gchar *comment, const gchar *scanner_id, const gchar *target_id, const gchar *config_id, const gchar *observers, array_t *alerts, const gchar *alterable, array_t *groups, const gchar *schedule_id, const gchar *schedule_periods, array_t *preferences, const gchar *hosts_ordering, gchar **fail_alert_id, gchar **fail_group_id) { task_t task; int type_of_scanner; scanner_t scanner; /* @todo Probably better to rollback on error. */ task = 0; if (find_task_with_permission (task_id, &task, "modify_task")) return -1; if (task == 0) return 1; if ((task_target (task) == 0) && (alerts->len || schedule_id)) return 17; switch (modify_task_check_config_scanner (task, config_id, scanner_id)) { case 0: break; case 1: return 15; case 2: return 4; case 3: return 3; default: assert (0); /* fallthrough */ case -1: return -1; } if (name) set_task_name (task, name); if (comment) set_task_comment (task, comment); scanner = 0; if (scanner_id) { if (strcmp (scanner_id, "0") == 0) { /* Leave it as is. */ } else if ((task_run_status (task) != TASK_STATUS_NEW) && (task_alterable (task) == 0)) return 2; else if (find_scanner_with_permission (scanner_id, &scanner, "get_scanners")) return -1; else if (scanner == 0) return 3; else set_task_scanner (task, scanner); } if (scanner == 0) type_of_scanner = scanner_type (task_scanner (task)); else type_of_scanner = scanner_type (scanner); if (config_id && (type_of_scanner != SCANNER_TYPE_CVE)) { config_t config; config = 0; if (strcmp (config_id, "0") == 0) { /* Leave it as it is. */ } else if ((task_run_status (task) != TASK_STATUS_NEW) && (task_alterable (task) == 0)) return 5; else if (find_config_with_permission (config_id, &config, "get_configs")) return -1; else if (config == 0) return 4; else set_task_config (task, config); } if (observers) { switch (set_task_observers (task, observers)) { case 0: break; case 1: return 6; case 2: return 7; break; case -1: default: return -1; } } if (alerts->len) { switch (set_task_alerts (task, alerts, fail_alert_id)) { case 0: break; case 1: return 8; case -1: default: return -1; } } if (alterable && (task_alterable (task) != atoi (alterable))) { if (task_run_status (task) != TASK_STATUS_NEW) return 9; set_task_alterable (task, strcmp (alterable, "0")); } if (groups->len) { switch (set_task_groups (task, groups, fail_group_id)) { case 0: break; case 1: return 10; case -1: default: return -1; } } if (schedule_id) { schedule_t schedule = 0; int periods; periods = schedule_periods ? atoi (schedule_periods) : 0; if (strcmp (schedule_id, "0") == 0) set_task_schedule (task, 0, periods); else if (find_schedule_with_permission (schedule_id, &schedule, "get_schedules")) return -1; else if (schedule == 0) return 11; else if (set_task_schedule (task, schedule, periods)) return -1; } else if (schedule_periods && strlen (schedule_periods)) set_task_schedule_periods (task_id, atoi (schedule_periods)); if (target_id) { target_t target; target = 0; if (strcmp (target_id, "0") == 0) { /* Leave it as it is. */ } else if ((task_run_status (task) != TASK_STATUS_NEW) && (task_alterable (task) == 0)) return 16; else if (find_target_with_permission (target_id, &target, "get_targets")) return -1; else if (target == 0) return 12; else set_task_target (task, target); } if (preferences) switch (set_task_preferences (task, preferences)) { case 0: break; case 1: return 13; case 2: return 14; default: return -1; } if (hosts_ordering) set_task_hosts_ordering (task, hosts_ordering); return 0; } /* Targets. */ /** * @brief Get the maximum allowed number of hosts per target. * * @return Maximum. */ int manage_max_hosts () { return max_hosts; } /** * @brief Set the maximum allowed number of hosts per target. * * @param[in] new_max New max_hosts value. */ static void manage_set_max_hosts (int new_max) { max_hosts = new_max; } /** * @brief Find a target for a specific permission, given a UUID. * * @param[in] uuid UUID of target. * @param[out] target Target return, 0 if successfully failed to find target. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find target), TRUE on error. */ gboolean find_target_with_permission (const char* uuid, target_t* target, const char *permission) { return find_resource_with_permission ("target", uuid, target, permission, 0); } /** * @brief Return number of hosts described by a hosts string. * * @param[in] given_hosts String describing hosts. * @param[in] exclude_hosts String describing hosts excluded from given set. * * @return Number of hosts, or -1 on error. */ int manage_count_hosts (const char *given_hosts, const char *exclude_hosts) { return manage_count_hosts_max (given_hosts, exclude_hosts, manage_max_hosts ()); } /** * @brief Trim leading and trailing space from a hosts string. * * @param[in] string String. May be modified. * * @return Either string or some address within string. */ static gchar * trim_hosts (gchar *string) { gchar *host, *end; /* Trim leading and trailing space. */ host = string; while ((*host == ' ') || (*host == '\t')) host++; end = host; while (*end) { if ((*end == ' ') || (*end == '\t')) { *end = '\0'; break; } end++; } return host; } /** * @brief Clean a hosts string. * * @param[in] given_hosts String describing hosts. * @param[out] max Max number of hosts, adjusted for duplicates. * * @return Freshly allocated new hosts string, or NULL on error. */ gchar* clean_hosts (const char *given_hosts, int *max) { array_t *clean_array; GString *clean; gchar **split, **point, *hosts, *hosts_start, *host; guint index; /* Treat newlines like commas. */ hosts = hosts_start = g_strdup (given_hosts); while (*hosts) { if (*hosts == '\n') *hosts = ','; hosts++; } split = g_strsplit (hosts_start, ",", 0); g_free (hosts_start); point = split; if ((point == NULL) || (*point == NULL)) { g_strfreev (split); return g_strdup (""); } clean_array = make_array (); while (*point) { host = trim_hosts (*point); if (*host) { /* Prevent simple duplicates. */ if (array_find_string (clean_array, host) == NULL) array_add (clean_array, host); else if (max) (*max)--; } point += 1; } clean = g_string_new (""); host = (gchar*) g_ptr_array_index (clean_array, 0); if (host) g_string_append_printf (clean, "%s", host); for (index = 1; index < clean_array->len; index++) { host = (gchar*) g_ptr_array_index (clean_array, index); if (host) g_string_append_printf (clean, ", %s", host); } return g_string_free (clean, FALSE); } /** * @brief Start a new IMMEDIATE transaction. */ void manage_transaction_start () { if (!in_transaction) { sql_begin_immediate (); in_transaction = TRUE; } gettimeofday (&last_msg, NULL); } /** * @brief Commit the current transaction, if any. * * The algorithm is extremely naive (time elapsed since the last message * was received) but delivers good enough performances when facing * bursts of messages. * * @param[in] force_commit Force committing the pending transaction. */ void manage_transaction_stop (gboolean force_commit) { struct timeval now; if (!in_transaction) return; gettimeofday (&now, NULL); if (force_commit || TIMEVAL_SUBTRACT_MS (now, last_msg) >= 500) { sql_commit (); in_transaction = FALSE; } } /** * @brief Validate a single port. * * @param[in] port A port. * * @return 0 success, 1 failed. */ static int validate_port (const char *port) { const char *first; while (*port && isblank (*port)) port++; if (*port == '\0') return 1; first = port; while (*first && isdigit (*first)) first++; if (first == port) return 1; while (*first && isblank (*first)) first++; if (*first == '\0') { long int number; number = strtol (port, NULL, 10); if (number <= 0) return 1; if (number > 65535) return 1; return 0; } return 1; } /** * @brief Validate a single port, for use in override or note. * * @param[in] port A port. * * @return 0 success, 1 failed. */ static int validate_results_port (const char *port) { long int num; char *end; if (!port) return 1; /* "cpe:abc", "general/tcp", "20/udp" * * We keep the "general/tcp" case pretty open because it is not clearly * restricted anywhere, and is already used with non-alphanumerics in * "general/Host_Details". We exclude whitespace, ',' and ';' to prevent * users from entering lists of ports. * * Similarly, the CPE case forbids whitespace, but allows ',' and ';' as * these may occur in valid CPEs. */ if (g_regex_match_simple ("^(cpe:[^\\s]+|general/[^\\s,;]+|[0-9]+/[[:alnum:]]+)$", port, 0, 0) == FALSE) return 1; if (g_str_has_prefix (port, "cpe:") || g_str_has_prefix (port, "general/")) return 0; num = strtol (port, &end, 10); if (*end != '/') return 1; if (num > 0 && num <= 65535) return 0; return 1; } /** * @brief Convert alive test name to alive test bitfield. * * @param[in] alive_tests Name of alive test. * * @return Alive test, or -1 on error. */ static int alive_test_from_string (const char* alive_tests) { alive_test_t alive_test; if (alive_tests == NULL || strcmp (alive_tests, "") == 0 || strcmp (alive_tests, "Scan Config Default") == 0) alive_test = 0; else if (strcmp (alive_tests, "ICMP, TCP-ACK Service & ARP Ping") == 0) alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ICMP | ALIVE_TEST_ARP; else if (strcmp (alive_tests, "TCP-ACK Service & ARP Ping") == 0) alive_test = ALIVE_TEST_TCP_ACK_SERVICE | ALIVE_TEST_ARP; else if (strcmp (alive_tests, "ICMP & ARP Ping") == 0) alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_ARP; else if (strcmp (alive_tests, "ICMP & TCP-ACK Service Ping") == 0) alive_test = ALIVE_TEST_ICMP | ALIVE_TEST_TCP_ACK_SERVICE; else if (strcmp (alive_tests, "ARP Ping") == 0) alive_test = ALIVE_TEST_ARP; else if (strcmp (alive_tests, "TCP-ACK Service Ping") == 0) alive_test = ALIVE_TEST_TCP_ACK_SERVICE; else if (strcmp (alive_tests, "TCP-SYN Service Ping") == 0) alive_test = ALIVE_TEST_TCP_SYN_SERVICE; else if (strcmp (alive_tests, "ICMP Ping") == 0) alive_test = ALIVE_TEST_ICMP; else if (strcmp (alive_tests, "Consider Alive") == 0) alive_test = ALIVE_TEST_CONSIDER_ALIVE; else return -1; return alive_test; } /** * @brief Set login data for a target. * * @param[in] target The target. * @param[in] type The credential type (e.g. "ssh" or "smb"). * @param[in] credential The credential or 0 to remove. * @param[in] port The port to authenticate at with credential. * * @return 0 on success, -1 on error, 1 target not found, 99 permission denied. */ static int set_target_login_data (target_t target, const char* type, credential_t credential, int port) { gchar *quoted_type; if (current_credentials.uuid && (acl_user_may ("modify_target") == 0)) return 99; if (type == NULL) return -1; if (target == 0) return 1; quoted_type = sql_quote (type); if (sql_int ("SELECT count (*) FROM targets_login_data" " WHERE target = %llu AND type = '%s';", target, quoted_type)) { if (credential == 0) { sql ("DELETE FROM targets_login_data" " WHERE target = '%llu' AND type = '%s';", target, quoted_type); } else { sql ("UPDATE targets_login_data" " SET credential = %llu, port = %d" " WHERE target = %llu AND type = '%s';", credential, port, target, quoted_type); } } else if (credential) { sql ("INSERT INTO targets_login_data (target, type, credential, port)" " VALUES (%llu, '%s', %llu, %i)", target, quoted_type, credential, port); } g_free (quoted_type); return 0; } /** * @brief Get a credential from a target. * * @param[in] target The target. * @param[in] type The credential type (e.g. "ssh" or "smb"). * * @return 0 on success, -1 on error, 1 credential not found, 99 permission * denied. */ credential_t target_credential (target_t target, const char* type) { gchar *quoted_type; credential_t credential; if (target == 0 || type == NULL) return 0; quoted_type = sql_quote (type); if (sql_int ("SELECT NOT EXISTS" " (SELECT * FROM targets_login_data" " WHERE target = %llu and type = '%s');", target, quoted_type)) { g_free (quoted_type); return 0; } sql_int64 (&credential, "SELECT credential FROM targets_login_data" " WHERE target = %llu AND type = '%s';", target, quoted_type); g_free (quoted_type); return credential; } /** * @brief Get a login port from a target. * * @param[in] target The target. * @param[in] type The credential type (e.g. "ssh" or "smb"). * * @return 0 on success, -1 on error, 1 credential not found, 99 permission * denied. */ int target_login_port (target_t target, const char* type) { gchar *quoted_type; int port; if (target == 0 || type == NULL) return 0; quoted_type = sql_quote (type); if (sql_int ("SELECT NOT EXISTS" " (SELECT * FROM targets_login_data" " WHERE target = %llu and type = '%s');", target, quoted_type)) { g_free (quoted_type); return 0; } port = sql_int ("SELECT port FROM targets_login_data" " WHERE target = %llu AND type = '%s';", target, quoted_type); g_free (quoted_type); return port; } /** * @brief Create a target. * * @param[in] name Name of target. * @param[in] asset_hosts_filter Asset host filter to select hosts. * Overrides \p hosts and \p exclude_hosts. * @param[in] hosts Host list of target. * @param[in] exclude_hosts List of hosts to exclude from \p hosts. * @param[in] comment Comment on target. * @param[in] port_list_id Port list of target (overrides \p port_range). * @param[in] port_range Port range of target. * @param[in] ssh_credential SSH credential. * @param[in] ssh_elevate_credential SSH previlige escalation credential. * @param[in] ssh_port Port for SSH login. * @param[in] smb_credential SMB credential. * @param[in] esxi_credential ESXi credential. * @param[in] snmp_credential SNMP credential. * @param[in] reverse_lookup_only Scanner preference reverse_lookup_only. * @param[in] reverse_lookup_unify Scanner preference reverse_lookup_unify. * @param[in] alive_tests Alive tests. * @param[in] allow_simultaneous_ips Scanner preference allow_simultaneous_ips. * @param[out] target Created target. * * @return 0 success, 1 target exists already, 2 error in host specification, * 3 too many hosts, 4 error in port range, 5 error in SSH port, * 6 failed to find port list, 7 error in alive tests, * 8 invalid SSH credential type, 9 invalid SSH elevate credential type, * 10 invalid SMB credential type, 11 invalid ESXi credential type, * 12 invalid SNMP credential type, 13 port range or port list required, * 14 SSH elevate credential without an SSH credential, * 99 permission denied, -1 error. */ int create_target (const char* name, const char* asset_hosts_filter, const char* hosts, const char* exclude_hosts, const char* comment, const char* port_list_id, const char* port_range, credential_t ssh_credential, credential_t ssh_elevate_credential, const char* ssh_port, credential_t smb_credential, credential_t esxi_credential, credential_t snmp_credential, const char *reverse_lookup_only, const char *reverse_lookup_unify, const char *alive_tests, const char *allow_simultaneous_ips, target_t* target) { gchar *quoted_name, *quoted_hosts, *quoted_exclude_hosts, *quoted_comment; gchar *port_list_comment, *quoted_ssh_port, *clean, *clean_exclude; gchar *chosen_hosts; port_list_t port_list; int ret, alive_test, max; target_t new_target; assert (current_credentials.uuid); if (port_range && validate_port_range (port_range)) return 4; if (ssh_port && validate_port (ssh_port)) return 5; alive_test = alive_test_from_string (alive_tests); if (alive_test <= -1) return 7; if (ssh_elevate_credential && (!ssh_credential)) return 14; if (ssh_credential && (ssh_elevate_credential == ssh_credential)) return 15; sql_begin_immediate (); if (acl_user_may ("create_target") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (name, "target", 0)) { sql_rollback (); return 1; } if (port_list_id) { if (find_port_list_with_permission (port_list_id, &port_list, "get_port_lists") || (port_list == 0)) { sql_rollback (); return 6; } } else if (port_range == NULL) { sql_rollback (); return 13; } else { port_list_comment = g_strdup_printf ("Autogenerated for target %s.", name); ret = create_port_list_unique (name, port_list_comment, port_range, &port_list); g_free (port_list_comment); if (ret) { sql_rollback (); return ret; } } if (asset_hosts_filter) { iterator_t asset_hosts; int previous; get_data_t get; GString *buffer; memset (&get, 0, sizeof (get)); get.filter = g_strdup (asset_hosts_filter); init_asset_host_iterator (&asset_hosts, &get); g_free (get.filter); previous = 0; buffer = g_string_new (""); while (next (&asset_hosts)) { g_string_append_printf (buffer, "%s%s", previous ? ", " : "", get_iterator_name (&asset_hosts)); previous = 1; } cleanup_iterator (&asset_hosts); chosen_hosts = g_string_free (buffer, FALSE); g_debug ("asset chosen_hosts: %s", chosen_hosts); } else { chosen_hosts = g_strdup (hosts); g_debug ("manual chosen_hosts: %s", chosen_hosts); } clean = clean_hosts (chosen_hosts, &max); g_free (chosen_hosts); if (exclude_hosts) clean_exclude = clean_hosts (exclude_hosts, NULL); else clean_exclude = g_strdup (""); max = manage_count_hosts (clean, clean_exclude); if (max <= 0) { g_free (clean); g_free (clean_exclude); sql_rollback (); return 2; } if (max > max_hosts) { g_free (clean); g_free (clean_exclude); sql_rollback (); return 3; } quoted_hosts = sql_quote (clean); quoted_exclude_hosts = sql_quote (clean_exclude); g_free (clean); g_free (clean_exclude); if (ssh_credential) quoted_ssh_port = sql_insert (ssh_port ? ssh_port : "22"); else quoted_ssh_port = g_strdup ("NULL"); if (reverse_lookup_only == NULL || strcmp (reverse_lookup_only, "0") == 0) reverse_lookup_only = "0"; else reverse_lookup_only = "1"; if (reverse_lookup_unify == NULL || strcmp (reverse_lookup_unify, "0") == 0) reverse_lookup_unify = "0"; else reverse_lookup_unify = "1"; if (allow_simultaneous_ips && strcmp (allow_simultaneous_ips, "0") == 0) allow_simultaneous_ips = "0"; else allow_simultaneous_ips = "1"; quoted_name = sql_quote (name ?: ""); if (comment) quoted_comment = sql_quote (comment); else quoted_comment = sql_quote (""); sql ("INSERT INTO targets" " (uuid, name, owner, hosts, exclude_hosts, comment, " " port_list, reverse_lookup_only, reverse_lookup_unify, alive_test," " allow_simultaneous_ips," " creation_time, modification_time)" " VALUES (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '%s', '%s', %llu, '%s', '%s', %i," " %s," " m_now (), m_now ());", quoted_name, current_credentials.uuid, quoted_hosts, quoted_exclude_hosts, quoted_comment, port_list, reverse_lookup_only, reverse_lookup_unify, alive_test, allow_simultaneous_ips); new_target = sql_last_insert_id (); if (target) *target = new_target; g_free (quoted_comment); g_free (quoted_name); g_free (quoted_hosts); g_free (quoted_exclude_hosts); if (ssh_credential) { gchar *type = credential_type (ssh_credential); if (strcmp (type, "usk") && strcmp (type, "up")) { sql_rollback (); g_free (quoted_ssh_port); return 8; } g_free (type); sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " VALUES (%llu, 'ssh', %llu, %s);", new_target, ssh_credential, quoted_ssh_port); } g_free (quoted_ssh_port); if (ssh_elevate_credential) { gchar *type = credential_type (ssh_elevate_credential); if (strcmp (type, "up")) { sql_rollback (); return 9; } g_free (type); sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " VALUES (%llu, 'elevate', %llu, %s);", new_target, ssh_elevate_credential, "0"); } if (smb_credential) { gchar *type = credential_type (smb_credential); if (strcmp (type, "up")) { sql_rollback (); return 10; } g_free (type); sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " VALUES (%llu, 'smb', %llu, %s);", new_target, smb_credential, "0"); } if (esxi_credential) { gchar *type = credential_type (esxi_credential); if (strcmp (type, "up")) { sql_rollback (); return 11; } g_free (type); sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " VALUES (%llu, 'esxi', %llu, %s);", new_target, esxi_credential, "0"); } if (snmp_credential) { gchar *type = credential_type (snmp_credential); if (strcmp (type, "snmp")) { sql_rollback (); return 12; } g_free (type); sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " VALUES (%llu, 'snmp', %llu, %s);", new_target, snmp_credential, "0"); } sql_commit (); return 0; } /** * @brief Create a target from an existing target. * * @param[in] name Name of new target. NULL to copy from existing. * @param[in] comment Comment on new target. NULL to copy from existing. * @param[in] target_id UUID of existing target. * @param[out] new_target New target. * * @return 0 success, 1 target exists already, 2 failed to find existing * target, 99 permission denied, -1 error. */ int copy_target (const char* name, const char* comment, const char *target_id, target_t* new_target) { int ret; target_t old_target; assert (new_target); ret = copy_resource ("target", name, comment, target_id, "hosts, exclude_hosts, port_list, reverse_lookup_only," " reverse_lookup_unify, alive_test," " allow_simultaneous_ips", 1, new_target, &old_target); if (ret) return ret; sql ("INSERT INTO targets_login_data (target, type, credential, port)" " SELECT %llu, type, credential, port" " FROM targets_login_data" " WHERE target = %llu;", *new_target, old_target); return 0; } /** * @brief Delete a target. * * @param[in] target_id UUID of target. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a task refers to the target, 2 failed * to find target, 99 permission denied, -1 error. */ int delete_target (const char *target_id, int ultimate) { target_t target = 0; target_t trash_target; sql_begin_immediate (); if (acl_user_may ("delete_target") == 0) { sql_rollback (); return 99; } if (find_target_with_permission (target_id, &target, "delete_target")) { sql_rollback (); return -1; } if (target == 0) { if (find_trash ("target", target_id, &target)) { sql_rollback (); return -1; } if (target == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by a task in the trashcan. */ if (sql_int ("SELECT count(*) FROM tasks" " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TRASH) ";", target)) { sql_rollback (); return 1; } permissions_set_orphans ("target", target, LOCATION_TRASH); tags_remove_resource ("target", target, LOCATION_TRASH); sql ("DELETE FROM targets_trash_login_data WHERE target = %llu;", target); sql ("DELETE FROM targets_trash WHERE id = %llu;", target); sql_commit (); return 0; } if (ultimate == 0) { if (sql_int ("SELECT count(*) FROM tasks" " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TABLE) " AND hidden = 0;", target)) { sql_rollback (); return 1; } sql ("INSERT INTO targets_trash" " (uuid, owner, name, hosts, exclude_hosts, comment," " port_list, port_list_location," " reverse_lookup_only, reverse_lookup_unify, alive_test," " allow_simultaneous_ips," " creation_time, modification_time)" " SELECT uuid, owner, name, hosts, exclude_hosts, comment," " port_list, " G_STRINGIFY (LOCATION_TABLE) "," " reverse_lookup_only, reverse_lookup_unify, alive_test," " allow_simultaneous_ips," " creation_time, modification_time" " FROM targets WHERE id = %llu;", target); trash_target = sql_last_insert_id (); /* Copy login data */ sql ("INSERT INTO targets_trash_login_data" " (target, type, credential, port, credential_location)" " SELECT %llu, type, credential, port, " G_STRINGIFY (LOCATION_TABLE) " FROM targets_login_data WHERE target = %llu;", trash_target, target); /* Update the location of the target in any trashcan tasks. */ sql ("UPDATE tasks" " SET target = %llu," " target_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TABLE) ";", sql_last_insert_id (), target); permissions_set_locations ("target", target, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("target", target, sql_last_insert_id (), LOCATION_TRASH); } else if (sql_int ("SELECT count(*) FROM tasks" " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TABLE), target)) { sql_rollback (); return 1; } else { permissions_set_orphans ("target", target, LOCATION_TABLE); tags_remove_resource ("target", target, LOCATION_TABLE); } sql ("DELETE FROM targets_login_data WHERE target = %llu;", target); sql ("DELETE FROM targets WHERE id = %llu;", target); sql_commit (); return 0; } /** * @brief Modify a target. * * @param[in] target_id UUID of target. * @param[in] name Name of target. * @param[in] hosts Host list of target. * @param[in] exclude_hosts List of hosts to exclude from \p hosts. * @param[in] comment Comment on target. * @param[in] port_list_id Port list of target (overrides \p port_range). * @param[in] ssh_credential_id SSH credential. * @param[in] ssh_elevate_credential_id SSH previlige escalation credential. * @param[in] ssh_port Port for SSH login. * @param[in] smb_credential_id SMB credential. * @param[in] esxi_credential_id ESXi credential. * @param[in] snmp_credential_id SNMP credential. * @param[in] reverse_lookup_only Scanner preference reverse_lookup_only. * @param[in] reverse_lookup_unify Scanner preference reverse_lookup_unify. * @param[in] alive_tests Alive tests. * @param[in] allow_simultaneous_ips Scanner preference allow_simultaneous_ips. * * @return 0 success, 1 target exists already, 2 error in host specification, * 3 too many hosts, 4 error in port range, 5 error in SSH port, * 6 failed to find port list, 7 failed to find SSH cred, 8 failed to * find SMB cred, 9 failed to find target, 10 error in alive tests, * 11 zero length name, 12 exclude hosts requires hosts * 13 hosts requires exclude hosts, * 14 hosts must be at least one character, 15 target is in use, * 16 failed to find ESXi cred, 17 failed to find SNMP cred, * 18 invalid SSH credential type, 19 invalid SMB credential type, * 20 invalid ESXi credential type, 21 invalid SNMP credential type, * 22 failed to find SSH elevate cred, 23 invalid SSH elevate * credential type, 24 SSH elevate credential without SSH credential, * 25 SSH elevate credential equals SSH credential, * 99 permission denied, -1 error. */ int modify_target (const char *target_id, const char *name, const char *hosts, const char *exclude_hosts, const char *comment, const char *port_list_id, const char *ssh_credential_id, const char *ssh_elevate_credential_id, const char *ssh_port, const char *smb_credential_id, const char *esxi_credential_id, const char* snmp_credential_id, const char *reverse_lookup_only, const char *reverse_lookup_unify, const char *alive_tests, const char *allow_simultaneous_ips) { target_t target; credential_t ssh_credential = 0; credential_t ssh_elevate_credential = 0; assert (target_id); sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_target") == 0) { sql_rollback (); return 99; } if (hosts && (exclude_hosts == NULL)) { sql_rollback (); return 13; } target = 0; if (find_target_with_permission (target_id, &target, "modify_target")) { sql_rollback (); return -1; } if (target == 0) { sql_rollback (); return 9; } if (name) { gchar *quoted_name; if (strlen (name) == 0) { sql_rollback (); return 11; } if (resource_with_name_exists (name, "target", target)) { sql_rollback (); return 1; } quoted_name = sql_quote (name); sql ("UPDATE targets SET" " name = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, target); g_free (quoted_name); } if (comment) { gchar *quoted_comment; quoted_comment = sql_quote (comment); sql ("UPDATE targets SET" " comment = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_comment, target); g_free (quoted_comment); } if (allow_simultaneous_ips) { if (target_in_use (target)) { sql_rollback (); return 15; } sql ("UPDATE targets SET" " allow_simultaneous_ips = '%i'," " modification_time = m_now ()" " WHERE id = %llu;", strcmp (allow_simultaneous_ips, "0") ? 1 : 0, target); } if (alive_tests) { int alive_test; alive_test = alive_test_from_string (alive_tests); if (alive_test <= -1) { sql_rollback (); return 10; } sql ("UPDATE targets SET" " alive_test = '%i'," " modification_time = m_now ()" " WHERE id = %llu;", alive_test, target); } if (port_list_id) { port_list_t port_list; if (target_in_use (target)) { sql_rollback (); return 15; } port_list = 0; if (find_port_list_with_permission (port_list_id, &port_list, "get_port_lists")) { sql_rollback (); return -1; } if (port_list == 0) { sql_rollback (); return 6; } sql ("UPDATE targets SET" " port_list = %llu," " modification_time = m_now ()" " WHERE id = %llu;", port_list, target); } if (ssh_credential_id) { if (target_in_use (target)) { sql_rollback (); return 15; } ssh_credential = 0; if (strcmp (ssh_credential_id, "0")) { int port_int; gchar *type; if (find_credential_with_permission (ssh_credential_id, &ssh_credential, "get_credentials")) { sql_rollback (); return -1; } if (ssh_credential == 0) { sql_rollback (); return 7; } if (ssh_port && strcmp (ssh_port, "0") && strcmp (ssh_port, "")) { if (validate_port (ssh_port)) { sql_rollback (); return 5; } port_int = atoi (ssh_port); } else port_int = 22; type = credential_type (ssh_credential); if (strcmp (type, "up") && strcmp (type, "usk")) { sql_rollback (); return 18; } g_free (type); set_target_login_data (target, "ssh", ssh_credential, port_int); } else set_target_login_data (target, "ssh", 0, 0); } if (ssh_elevate_credential_id) { if (target_in_use (target)) { sql_rollback (); return 15; } ssh_elevate_credential = 0; if (strcmp (ssh_elevate_credential_id, "0")) { gchar *type; if (find_credential_with_permission (ssh_elevate_credential_id, &ssh_elevate_credential, "get_credentials")) { sql_rollback (); return -1; } if (ssh_elevate_credential == 0) { sql_rollback (); return 22; } type = credential_type (ssh_elevate_credential); if (strcmp (type, "up")) { sql_rollback (); return 23; } g_free (type); set_target_login_data (target, "elevate", ssh_elevate_credential, 0); } else set_target_login_data (target, "elevate", 0, 0); } if (smb_credential_id) { credential_t smb_credential; if (target_in_use (target)) { sql_rollback (); return 15; } smb_credential = 0; if (strcmp (smb_credential_id, "0")) { gchar *type; if (find_credential_with_permission (smb_credential_id, &smb_credential, "get_credentials")) { sql_rollback (); return -1; } if (smb_credential == 0) { sql_rollback (); return 7; } type = credential_type (smb_credential); if (strcmp (type, "up")) { sql_rollback (); return 19; } g_free (type); set_target_login_data (target, "smb", smb_credential, 0); } else set_target_login_data (target, "smb", 0, 0); } if (esxi_credential_id) { credential_t esxi_credential; if (target_in_use (target)) { sql_rollback (); return 15; } esxi_credential = 0; if (strcmp (esxi_credential_id, "0")) { gchar *type; if (find_credential_with_permission (esxi_credential_id, &esxi_credential, "get_credentials")) { sql_rollback (); return -1; } if (esxi_credential == 0) { sql_rollback (); return 16; } type = credential_type (esxi_credential); if (strcmp (type, "up")) { sql_rollback (); return 20; } g_free (type); set_target_login_data (target, "esxi", esxi_credential, 0); } else set_target_login_data (target, "esxi", 0, 0); } if (snmp_credential_id) { credential_t snmp_credential; if (target_in_use (target)) { sql_rollback (); return 15; } snmp_credential = 0; if (strcmp (snmp_credential_id, "0")) { gchar *type; if (find_credential_with_permission (snmp_credential_id, &snmp_credential, "get_credentials")) { sql_rollback (); return -1; } if (snmp_credential == 0) { sql_rollback (); return 17; } type = credential_type (snmp_credential); if (strcmp (type, "snmp")) { sql_rollback (); return 21; } g_free (type); set_target_login_data (target, "snmp", snmp_credential, 0); } else set_target_login_data (target, "snmp", 0, 0); } if (ssh_credential_id || ssh_elevate_credential_id) { if (!ssh_credential_id) ssh_credential = target_ssh_credential (target); if (!ssh_elevate_credential_id) ssh_elevate_credential = target_ssh_elevate_credential (target); if (ssh_elevate_credential && !ssh_credential) { sql_rollback (); return 24; } if (ssh_credential && (ssh_credential == ssh_elevate_credential)) { sql_rollback (); return 25; } } if (exclude_hosts) { gchar *quoted_exclude_hosts, *quoted_hosts, *clean, *clean_exclude; int max; if (target_in_use (target)) { sql_rollback (); return 15; } if (hosts == NULL) { sql_rollback (); return 12; } if (strlen (hosts) == 0) { sql_rollback (); return 14; } clean = clean_hosts (hosts, &max); clean_exclude = clean_hosts (exclude_hosts, NULL); max = manage_count_hosts (clean, clean_exclude); if (max <= 0) { g_free (clean); g_free (clean_exclude); sql_rollback (); return 2; } if (max > max_hosts) { g_free (clean); g_free (clean_exclude); sql_rollback (); return 3; } quoted_hosts = sql_quote (clean); quoted_exclude_hosts = sql_quote (clean_exclude); g_free (clean); g_free (clean_exclude); sql ("UPDATE targets SET" " hosts = '%s'," " exclude_hosts = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_hosts, quoted_exclude_hosts, target); g_free (quoted_hosts); g_free (quoted_exclude_hosts); } if (reverse_lookup_only) { if (target_in_use (target)) { sql_rollback (); return 15; } sql ("UPDATE targets SET" " reverse_lookup_only = '%i'," " modification_time = m_now ()" " WHERE id = %llu;", strcmp (reverse_lookup_only, "0") ? 1 : 0, target); } if (reverse_lookup_unify) { if (target_in_use (target)) { sql_rollback (); return 15; } sql ("UPDATE targets SET" " reverse_lookup_unify = '%i'," " modification_time = m_now ()" " WHERE id = %llu;", strcmp (reverse_lookup_unify, "0") ? 1 : 0, target); } sql_commit (); return 0; } /** * @brief Filter columns for target iterator. */ #define TARGET_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "hosts", "exclude_hosts", "ips", "port_list", \ "ssh_credential", "smb_credential", "esxi_credential", "snmp_credential", \ "ssh_elevate_credential", NULL } /** * @brief Target iterator columns. */ #define TARGET_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (targets), \ { "hosts", NULL, KEYWORD_TYPE_STRING }, \ { "target_credential (id, 0, CAST ('ssh' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "target_login_port (id, 0, CAST ('ssh' AS text))", \ "ssh_port", \ KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 0, CAST ('smb' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "port_list", NULL, KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { \ "(SELECT uuid FROM port_lists" \ " WHERE port_lists.id = port_list)", \ NULL, \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM port_lists" \ " WHERE port_lists.id = port_list)", \ "port_list", \ KEYWORD_TYPE_STRING \ }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "exclude_hosts", NULL, KEYWORD_TYPE_STRING }, \ { "reverse_lookup_only", NULL, KEYWORD_TYPE_INTEGER }, \ { "reverse_lookup_unify", NULL, KEYWORD_TYPE_INTEGER }, \ { "alive_test", NULL, KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 0, CAST ('esxi' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 0, CAST ('snmp' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 0, CAST ('elevate' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "allow_simultaneous_ips", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { \ "(SELECT name FROM credentials" \ " WHERE credentials.id" \ " = target_credential (targets.id, 0," \ " CAST ('ssh' AS text)))", \ "ssh_credential", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM credentials" \ " WHERE credentials.id" \ " = target_credential (targets.id, 0," \ " CAST ('smb' AS text)))", \ "smb_credential", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM credentials" \ " WHERE credentials.id" \ " = target_credential (targets.id, 0," \ " CAST ('esxi' AS text)))", \ "esxi_credential", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM credentials" \ " WHERE credentials.id" \ " = target_credential (targets.id, 0," \ " CAST ('snmp' AS text)))", \ "snmp_credential", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM credentials" \ " WHERE credentials.id" \ " = target_credential (targets.id, 0," \ " CAST ('elevate' AS text)))", \ "ssh_elevate_credential", \ KEYWORD_TYPE_STRING \ }, \ { "hosts", NULL, KEYWORD_TYPE_STRING }, \ { "max_hosts (hosts, exclude_hosts)", \ "ips", \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Target iterator columns for trash case. */ #define TARGET_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (targets_trash), \ { "hosts", NULL, KEYWORD_TYPE_STRING }, \ { "target_credential (id, 1, CAST ('ssh' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "target_login_port (id, 1, CAST ('ssh' AS text))", \ "ssh_port", \ KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 1, CAST ('smb' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "port_list", NULL, KEYWORD_TYPE_INTEGER }, \ { "trash_target_credential_location (id, CAST ('ssh' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "trash_target_credential_location (id, CAST ('smb' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { \ "(CASE" \ " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT uuid FROM port_lists_trash" \ " WHERE port_lists_trash.id = port_list)" \ " ELSE (SELECT uuid FROM port_lists" \ " WHERE port_lists.id = port_list)" \ " END)", \ NULL, \ KEYWORD_TYPE_STRING \ }, \ { \ "(CASE" \ " WHEN port_list_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT name FROM port_lists_trash" \ " WHERE port_lists_trash.id = port_list)" \ " ELSE (SELECT name FROM port_lists" \ " WHERE port_lists.id = port_list)" \ " END)", \ NULL, \ KEYWORD_TYPE_STRING \ }, \ { "port_list_location = " G_STRINGIFY (LOCATION_TRASH), \ NULL, \ KEYWORD_TYPE_STRING }, \ { "exclude_hosts", NULL, KEYWORD_TYPE_STRING }, \ { "reverse_lookup_only", NULL, KEYWORD_TYPE_INTEGER }, \ { "reverse_lookup_unify", NULL, KEYWORD_TYPE_INTEGER }, \ { "alive_test", NULL, KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 1, CAST ('esxi' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "trash_target_credential_location (id, CAST ('esxi' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 1, CAST ('snmp' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "trash_target_credential_location (id, CAST ('snmp' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "target_credential (id, 1, CAST ('elevate' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "trash_target_credential_location (id, CAST ('elevate' AS text))", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "allow_simultaneous_ips", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of targets. * * @param[in] get GET params. * * @return Total number of targets in filtered set. */ int target_count (const get_data_t *get) { static const char *extra_columns[] = TARGET_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TARGET_ITERATOR_COLUMNS; static column_t trash_columns[] = TARGET_ITERATOR_TRASH_COLUMNS; return count ("target", get, columns, trash_columns, extra_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a target iterator, given a single target. * * @param[in] iterator Iterator. * @param[in] target Single target to iterate. */ void init_target_iterator_one (iterator_t* iterator, target_t target) { get_data_t get; assert (target); memset (&get, '\0', sizeof (get)); get.id = target_uuid (target); get.filter = "owner=any permission=get_targets"; /* We could pass the return up to the caller, but we don't pass in * a filter id and the callers are all in situations where the * target cannot disappear, so it's safe to ignore the return. */ init_target_iterator (iterator, &get); } /** * @brief Initialise a target iterator, including observed targets. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find target, 2 failed to find filter, * -1 error. */ int init_target_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = TARGET_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TARGET_ITERATOR_COLUMNS; static column_t trash_columns[] = TARGET_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "target", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the hosts of the target from a target iterator. * * @param[in] iterator Iterator. * * @return Hosts of the target or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_hosts, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the SSH LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return SSH LSC credential. */ int target_iterator_ssh_credential (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); return ret; } /** * @brief Get the SSH LSC port of the target from a target iterator. * * @param[in] iterator Iterator. * * @return SSH LSC port of the target or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_ssh_port, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the SMB LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return SMB LSC credential. */ int target_iterator_smb_credential (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3); return ret; } /** * @brief Get the location of the SSH LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return 0 in table, 1 in trash */ int target_iterator_ssh_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 5); return ret; } /** * @brief Get the location of the SMB LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return 0 in table, 1 in trash */ int target_iterator_smb_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6); return ret; } /** * @brief Get the port list uuid of the target from a target iterator. * * @param[in] iterator Iterator. * * @return UUID of the target port list or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_port_list_uuid, GET_ITERATOR_COLUMN_COUNT + 7); /** * @brief Get the port list name of the target from a target iterator. * * @param[in] iterator Iterator. * * @return Name of the target port list or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_port_list_name, GET_ITERATOR_COLUMN_COUNT + 8); /** * @brief Get the location of the port list from a target iterator. * * @param[in] iterator Iterator. * * @return 0 in table, 1 in trash. */ int target_iterator_port_list_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 9); return ret; } /** * @brief Get the excluded hosts of the target from a target iterator. * * @param[in] iterator Iterator. * * @return Excluded hosts of the target or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_exclude_hosts, GET_ITERATOR_COLUMN_COUNT + 10); /** * @brief Get the reverse lookup only value from a target iterator. * * @param[in] iterator Iterator. * * @return Reverse lookup only of the target or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_reverse_lookup_only, GET_ITERATOR_COLUMN_COUNT + 11); /** * @brief Get the reverse lookup unify value from a target iterator. * * @param[in] iterator Iterator. * * @return Reverse lookup unify of the target or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_reverse_lookup_unify, GET_ITERATOR_COLUMN_COUNT + 12); /** * @brief Get the alive test description from a target iterator. * * @param[in] iterator Iterator. * * @return Reverse lookup unify of the target or NULL if iteration is complete. */ const char* target_iterator_alive_tests (iterator_t* iterator) { int tests; if (iterator->done) return ""; tests = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 13); if ((tests & ALIVE_TEST_TCP_ACK_SERVICE) && (tests & ALIVE_TEST_ICMP) && (tests & ALIVE_TEST_ARP)) return "ICMP, TCP-ACK Service & ARP Ping"; if ((tests & ALIVE_TEST_TCP_ACK_SERVICE) && (tests & ALIVE_TEST_ARP)) return "TCP-ACK Service & ARP Ping"; if ((tests & ALIVE_TEST_ICMP) && (tests & ALIVE_TEST_ARP)) return "ICMP & ARP Ping"; if ((tests & ALIVE_TEST_ICMP) && (tests & ALIVE_TEST_TCP_ACK_SERVICE)) return "ICMP & TCP-ACK Service Ping"; if (tests & ALIVE_TEST_ARP) return "ARP Ping"; if (tests & ALIVE_TEST_TCP_ACK_SERVICE) return "TCP-ACK Service Ping"; if (tests & ALIVE_TEST_TCP_SYN_SERVICE) return "TCP-SYN Service Ping"; if (tests & ALIVE_TEST_ICMP) return "ICMP Ping"; if (tests & ALIVE_TEST_CONSIDER_ALIVE) return "Consider Alive"; return "Scan Config Default"; } /** * @brief Get the ESXi LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return ESXi LSC credential. */ int target_iterator_esxi_credential (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 14); return ret; } /** * @brief Get the ESXi LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return ESXi LSC credential. */ int target_iterator_esxi_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 15); return ret; } /** * @brief Get the SNMP LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return ESXi LSC credential. */ int target_iterator_snmp_credential (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 16); return ret; } /** * @brief Get the SNMP LSC credential location from a target iterator. * * @param[in] iterator Iterator. * * @return ESXi LSC credential. */ int target_iterator_snmp_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 17); return ret; } /** * @brief Get the ELEVATE LSC credential from a target iterator. * * @param[in] iterator Iterator. * * @return ELEVATE LSC credential. */ int target_iterator_ssh_elevate_credential (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 18); return ret; } /** * @brief Get the ELEVATE LSC credential location from a target iterator. * * @param[in] iterator Iterator. * * @return ELEVATE LSC credential. */ int target_iterator_ssh_elevate_trash (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 19); return ret; } /** * @brief Get the allow_simultaneous_ips value from a target iterator. * * @param[in] iterator Iterator. * * @return allow_simult_ips_same_host or NULL if iteration is complete. */ DEF_ACCESS (target_iterator_allow_simultaneous_ips, GET_ITERATOR_COLUMN_COUNT + 20); /** * @brief Return the UUID of a tag. * * @param[in] tag Tag. * * @return Newly allocated UUID if available, else NULL. */ char* tag_uuid (tag_t tag) { return sql_string ("SELECT uuid FROM tags WHERE id = %llu;", tag); } /** * @brief Return the UUID of a target. * * @param[in] target Target. * * @return Newly allocated UUID if available, else NULL. */ char* target_uuid (target_t target) { return sql_string ("SELECT uuid FROM targets WHERE id = %llu;", target); } /** * @brief Return the UUID of a trashcan target. * * @param[in] target Target. * * @return Newly allocated UUID if available, else NULL. */ char* trash_target_uuid (target_t target) { return sql_string ("SELECT uuid FROM targets_trash WHERE id = %llu;", target); } /** * @brief Return the name of a target. * * @param[in] target Target. * * @return Newly allocated name if available, else NULL. */ char* target_name (target_t target) { return sql_string ("SELECT name FROM targets WHERE id = %llu;", target); } /** * @brief Return the name of a trashcan target. * * @param[in] target Target. * * @return Newly allocated name if available, else NULL. */ char* trash_target_name (target_t target) { return sql_string ("SELECT name FROM targets_trash WHERE id = %llu;", target); } /** * @brief Return the comment of a target. * * @param[in] target Target. * * @return Newly allocated name if available, else NULL. */ static char* target_comment (target_t target) { return sql_string ("SELECT comment FROM targets WHERE id = %llu;", target); } /** * @brief Return the comment of a trashcan target. * * @param[in] target Target. * * @return Newly allocated name if available, else NULL. */ static char* trash_target_comment (target_t target) { return sql_string ("SELECT comment FROM targets_trash WHERE id = %llu;", target); } /** * @brief Return whether a trashcan target is readable. * * @param[in] target Target. * * @return 1 if readable, else 0. */ int trash_target_readable (target_t target) { char *uuid; target_t found = 0; if (target == 0) return 0; uuid = target_uuid (target); if (find_trash ("target", uuid, &found)) { g_free (uuid); return 0; } g_free (uuid); return found > 0; } /** * @brief Return the hosts associated with a target. * * @param[in] target Target. * * @return Newly allocated comma separated list of hosts if available, * else NULL. */ char* target_hosts (target_t target) { return sql_string ("SELECT hosts FROM targets WHERE id = %llu;", target); } /** * @brief Return the excluded hosts associated with a target. * * @param[in] target Target. * * @return Newly allocated comma separated list of excluded hosts if available, * else NULL. */ char* target_exclude_hosts (target_t target) { return sql_string ("SELECT exclude_hosts FROM targets WHERE id = %llu;", target); } /** * @brief Return the reverse_lookup_only value of a target. * * @param[in] target Target. * * @return Reverse lookup only value if available, else NULL. */ char* target_reverse_lookup_only (target_t target) { return sql_string ("SELECT reverse_lookup_only FROM targets" " WHERE id = %llu;", target); } /** * @brief Return the reverse_lookup_unify value of a target. * * @param[in] target Target. * * @return Reverse lookup unify value if available, else NULL. */ char* target_reverse_lookup_unify (target_t target) { return sql_string ("SELECT reverse_lookup_unify FROM targets" " WHERE id = %llu;", target); } /** * @brief Return the allow_simultaneous_ips value of a target. * * @param[in] target Target. * * @return The allow_simultaneous_ips value if available, else NULL. */ char* target_allow_simultaneous_ips (target_t target) { return sql_string ("SELECT allow_simultaneous_ips FROM targets" " WHERE id = %llu;", target); } /** * @brief Return the SSH LSC port of a target. * * @param[in] target Target. * * @return Newly allocated port if available, else NULL. */ char* target_ssh_port (target_t target) { int port = target_login_port (target, "ssh"); return port ? g_strdup_printf ("%d", port) : NULL; } /** * @brief Return the SSH credential associated with a target, if any. * * @param[in] target Target. * * @return SSH credential if any, else 0. */ credential_t target_ssh_credential (target_t target) { return target_credential (target, "ssh"); } /** * @brief Return the SMB credential associated with a target, if any. * * @param[in] target Target. * * @return SMB credential if any, else 0. */ credential_t target_smb_credential (target_t target) { return target_credential (target, "smb"); } /** * @brief Return the ESXi credential associated with a target, if any. * * @param[in] target Target. * * @return ESXi credential if any, else 0. */ credential_t target_esxi_credential (target_t target) { return target_credential (target, "esxi"); } /** * @brief Return the ELEVATE credential associated with a target, if any. * * @param[in] target Target. * * @return ELEVATE credential if any, else 0. */ credential_t target_ssh_elevate_credential (target_t target) { return target_credential (target, "elevate"); } /** * @brief Return the port list associated with a target, if any. * * @param[in] target Target. * * @return Port list */ port_list_t target_port_list (target_t target) { port_list_t port_list; switch (sql_int64 (&port_list, "SELECT port_list FROM targets" " WHERE id = %llu;", target)) { case 0: break; case 1: /* Too few rows in result of query. */ return 0; break; default: /* Programming error. */ assert (0); case -1: /** @todo Move return to arg; return -1. */ return 0; break; } return port_list; } /** * @brief Return the port range of a target, in GMP port range list format. * * For "OpenVAS Default", return the explicit port ranges instead of "default". * * @param[in] target Target. * * @return Newly allocated port range if available, else NULL. */ char* target_port_range (target_t target) { GString *range; iterator_t ranges; range = g_string_new (""); init_port_range_iterator (&ranges, target_port_list (target), 0, 1, "type, CAST (start AS INTEGER)"); if (next (&ranges)) { const char *start, *end; int type; start = port_range_iterator_start (&ranges); end = port_range_iterator_end (&ranges); type = port_range_iterator_type_int (&ranges); /* Scanner can only handle: T:1-3,5-6,9,U:1-2 */ if (end && strcmp (end, "0") && strcmp (end, start)) g_string_append_printf (range, "%s%s-%s", (type == PORT_PROTOCOL_UDP ? "U:" : "T:"), start, end); else g_string_append_printf (range, "%s%s", (type == PORT_PROTOCOL_UDP ? "U:" : "T:"), start); while (next (&ranges)) { int tcp; start = port_range_iterator_start (&ranges); end = port_range_iterator_end (&ranges); tcp = (type == PORT_PROTOCOL_TCP); type = port_range_iterator_type_int (&ranges); if (end && strcmp (end, "0") && strcmp (end, start)) g_string_append_printf (range, ",%s%s-%s", (tcp && type == PORT_PROTOCOL_UDP ? "U:" : ""), start, end); else g_string_append_printf (range, ",%s%s", (tcp && type == PORT_PROTOCOL_UDP ? "U:" : ""), start); } } cleanup_iterator (&ranges); return g_string_free (range, FALSE); } /** * @brief Return a target's alive tests. * * @param[in] target Target. * * @return Alive test bitfield. */ alive_test_t target_alive_tests (target_t target) { return sql_int ("SELECT alive_test FROM targets WHERE id = %llu;", target); } /** * @brief Return whether a target is in use by a task. * * @param[in] target Target. * * @return 1 if in use, else 0. */ int target_in_use (target_t target) { return !!sql_int ("SELECT count(*) FROM tasks" " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TABLE) " AND hidden = 0;", target); } /** * @brief Return whether a trashcan target is referenced by a task. * * @param[in] target Target. * * @return 1 if in use, else 0. */ int trash_target_in_use (target_t target) { return !!sql_int ("SELECT count(*) FROM tasks" " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TRASH), target); } /** * @brief Return whether a target is writable. * * @param[in] target Target. * * @return 1 if writable, else 0. */ int target_writable (target_t target) { return 1; } /** * @brief Return whether a trashcan target is writable. * * @param[in] target Target. * * @return 1 if writable, else 0. */ int trash_target_writable (target_t target) { return trash_target_in_use (target) == 0; } /** * @brief Initialise a target task iterator. * * Iterates over all tasks that use the target. * * @param[in] iterator Iterator. * @param[in] target Target. */ void init_target_task_iterator (iterator_t* iterator, target_t target) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (target); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_tasks")); available = acl_where_owned ("task", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT name, uuid, %s FROM tasks" " WHERE target = %llu" " AND hidden = 0" " ORDER BY name ASC;", with_clause ? with_clause : "", available, target); g_free (with_clause); g_free (available); } /** * @brief Get the name from a target_task iterator. * * @param[in] iterator Iterator. * * @return The name of the host, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (target_task_iterator_name, 0); /** * @brief Get the uuid from a target_task iterator. * * @param[in] iterator Iterator. * * @return The uuid of the host, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (target_task_iterator_uuid, 1); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int target_task_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 2); } /* SecInfo Alerts. */ /** * @brief Check for new SCAP SecInfo after an update. */ static void check_for_new_scap () { if (manage_scap_loaded ()) { if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.cves" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_NEW_SECINFO, "cve", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.cpes" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_NEW_SECINFO, "cpe", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.ovaldefs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_NEW_SECINFO, "ovaldef", 0, 0); } } /** * @brief Check for new CERT SecInfo after an update. */ static void check_for_new_cert () { if (manage_cert_loaded ()) { if (sql_int ("SELECT EXISTS" " (SELECT * FROM cert.cert_bund_advs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0));")) event (EVENT_NEW_SECINFO, "cert_bund_adv", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM cert.dfn_cert_advs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0));")) event (EVENT_NEW_SECINFO, "dfn_cert_adv", 0, 0); } } /** * @brief Print an URL for a New NVTs alert. * * @param[in] url Format string for url. * @param[in] oid SecInfo ID. * @param[in] type SecInfo Type. * * @return Freshly allocated url. */ static gchar * alert_url_print (const gchar *url, const gchar *oid, const gchar *type) { int formatting; const gchar *point, *end; GString *new_url; assert (url); new_url = g_string_new (""); for (formatting = 0, point = url, end = (url + strlen (url)); point < end; point++) if (formatting) { switch (*point) { case '$': g_string_append_c (new_url, '$'); break; case 'o': { g_string_append (new_url, oid); break; } case 't': { g_string_append (new_url, type); break; } default: g_string_append_c (new_url, '$'); g_string_append_c (new_url, *point); break; } formatting = 0; } else if (*point == '$') formatting = 1; else g_string_append_c (new_url, *point); return g_string_free (new_url, FALSE); } /** * @brief Create list for New NVTs event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated list. */ static gchar * new_nvts_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; time_t feed_version_epoch; feed_version_epoch = nvts_feed_version_epoch(); details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; if (details_url && strlen (details_url)) buffer = g_string_new (NEW_NVTS_HEADER); else buffer = g_string_new (NEW_NVTS_HEADER_OID); count = 0; // TODO This should use an iterator provided by manage_sql_nvts.c. if (example) init_iterator (&rows, "SELECT oid, name, solution_type, cvss_base, qod FROM nvts" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT oid, name, solution_type, cvss_base, qod FROM nvts" " WHERE creation_time > %ld" " ORDER BY creation_time DESC;", feed_version_epoch); else init_iterator (&rows, "SELECT oid, name, solution_type, cvss_base, qod FROM nvts" " WHERE modification_time > %ld" " AND creation_time <= %ld" " ORDER BY modification_time DESC;", feed_version_epoch, feed_version_epoch); while (next (&rows)) { gchar *url; const char *name; name = iterator_string (&rows, 1); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; g_string_append_printf (buffer, "%-57.57s%-3s %13s %8s %3s%%%s%s%s", name, strlen (name) > 60 ? "..." : (strlen (name) > 57 ? name + 57 : " "), iterator_string (&rows, 2), iterator_string (&rows, 3), iterator_string (&rows, 4), url ? "\n " : " ", url ? url : iterator_string (&rows, 0), url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create list for New CVEs event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated message. */ static gchar * new_cves_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; buffer = g_string_new (NEW_CVES_HEADER); count = 0; if (example) init_iterator (&rows, "SELECT uuid, name, cvss, description FROM cves" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT uuid, name, cvss, description FROM cves" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY creation_time DESC;"); else init_iterator (&rows, "SELECT uuid, name, cvss, description FROM cves" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY modification_time DESC;"); while (next (&rows)) { gchar *url; const char *name, *desc; name = iterator_string (&rows, 1); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; desc = iterator_string (&rows, 3); g_string_append_printf (buffer, "%-15.15s %8s %50.50s%s%s%s%s", name, iterator_string (&rows, 2), desc, strlen (desc) > 53 ? "..." : (strlen (desc) > 50 ? desc + 50 : " "), url ? "\n " : "", url ? url : "", url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create list for New CPEs event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated list. */ static gchar * new_cpes_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; buffer = g_string_new (NEW_CPES_HEADER); count = 0; if (example) init_iterator (&rows, "SELECT uuid, name, title FROM cpes" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT uuid, name, title FROM cpes" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY creation_time DESC;"); else init_iterator (&rows, "SELECT uuid, name, title FROM cpes" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY modification_time DESC;"); while (next (&rows)) { gchar *url; const char *name, *title; name = iterator_string (&rows, 1); title = iterator_string (&rows, 2); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; g_string_append_printf (buffer, "%-57.57s%-3s %-s%s%s%s", name, strlen (name) > 60 ? "..." : (strlen (name) > 57 ? name + 57 : " "), title, url ? "\n " : "", url ? url : "", url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create list for "New CERT-Bund Advisories" event message. * * @param[in] event Event. * @param[in] event_data Event data. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated string. */ static gchar * new_cert_bunds_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; buffer = g_string_new (NEW_CERT_BUNDS_HEADER); count = 0; if (example) init_iterator (&rows, "SELECT uuid, name, title FROM cert_bund_advs" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT uuid, name, title FROM cert_bund_advs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " ORDER BY creation_time DESC;"); else init_iterator (&rows, "SELECT uuid, name, title FROM cert_bund_advs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " ORDER BY modification_time DESC;"); while (next (&rows)) { gchar *url; const char *name, *title; name = iterator_string (&rows, 1); title = iterator_string (&rows, 2); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; g_string_append_printf (buffer, "%-11s %-s%s%s%s", name, title, url ? "\n " : "", url ? url : "", url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create list for "New DFN-CERT Advisories" event message. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated string. */ static gchar * new_dfn_certs_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; buffer = g_string_new (NEW_DFN_CERTS_HEADER); count = 0; if (example) init_iterator (&rows, "SELECT uuid, name, title FROM dfn_cert_advs" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT uuid, name, title FROM dfn_cert_advs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " ORDER BY creation_time DESC;"); else init_iterator (&rows, "SELECT uuid, name, title FROM dfn_cert_advs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " ORDER BY modification_time DESC;"); while (next (&rows)) { gchar *url; const char *name, *title; name = iterator_string (&rows, 1); title = iterator_string (&rows, 2); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; g_string_append_printf (buffer, "%-18s %-s%s%s%s", name, title, url ? "\n " : "", url ? url : "", url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create list for "New OVAL Definitions" event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * @param[in] example Whether the message is an example only. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated list. */ static gchar * new_oval_defs_list (event_t event, const void* event_data, alert_t alert, int example, int *count_return) { iterator_t rows; GString *buffer; int count; char *details_url; const gchar *type; details_url = alert_data (alert, "method", "details_url"); type = (gchar*) event_data; buffer = g_string_new (NEW_OVAL_DEFS_HEADER); count = 0; if (example) init_iterator (&rows, "SELECT uuid, name, title FROM ovaldefs" " LIMIT 4;"); else if (event == EVENT_NEW_SECINFO) init_iterator (&rows, "SELECT uuid, name, title FROM ovaldefs" " WHERE creation_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY creation_time DESC;"); else init_iterator (&rows, "SELECT uuid, name, title FROM ovaldefs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " ORDER BY modification_time DESC;"); while (next (&rows)) { gchar *url; const char *name, *title; name = iterator_string (&rows, 1); title = iterator_string (&rows, 2); if (details_url && strlen (details_url)) url = alert_url_print (details_url, iterator_string (&rows, 0), type); else url = NULL; g_string_append_printf (buffer, "%-30s %-s%s%s%s", name, title, url ? "\n " : "", url ? url : "", url ? "\n\n" : "\n"); g_free (url); count++; } cleanup_iterator (&rows); if (count_return) *count_return = count; return g_string_free (buffer, FALSE); } /** * @brief Create message for New NVTs event. * * @param[in] event Event. * @param[in] event_data Event data. * @param[in] alert Alert. * @param[out] count_return NULL, or address for row count. * * @return Freshly allocated list. */ static gchar * new_secinfo_list (event_t event, const void* event_data, alert_t alert, int *count_return) { g_debug ("%s: event_data: %s", __func__, (gchar*) event_data); if (strcasecmp (event_data, "nvt_example") == 0) return new_nvts_list (event, "nvt", alert, 1, count_return); if (strcasecmp (event_data, "nvt") == 0) return new_nvts_list (event, "nvt", alert, 0, count_return); if (strcasecmp (event_data, "cve_example") == 0) return new_cves_list (event, "cve", alert, 1, count_return); if (strcasecmp (event_data, "cve") == 0) return new_cves_list (event, "cve", alert, 0, count_return); if (strcasecmp (event_data, "cpe_example") == 0) return new_cpes_list (event, "cpe", alert, 1, count_return); if (strcasecmp (event_data, "cpe") == 0) return new_cpes_list (event, "cpe", alert, 0, count_return); if (strcasecmp (event_data, "cert_bund_adv_example") == 0) return new_cert_bunds_list (event, "cert_bund_adv", alert, 1, count_return); if (strcasecmp (event_data, "cert_bund_adv") == 0) return new_cert_bunds_list (event, "cert_bund_adv", alert, 0, count_return); if (strcasecmp (event_data, "dfn_cert_adv_example") == 0) return new_dfn_certs_list (event, "dfn_cert_adv", alert, 1, count_return); if (strcasecmp (event_data, "dfn_cert_adv") == 0) return new_dfn_certs_list (event, "dfn_cert_adv", alert, 0, count_return); if (strcasecmp (event_data, "ovaldef_example") == 0) return new_oval_defs_list (event, "ovaldef", alert, 1, count_return); if (strcasecmp (event_data, "ovaldef") == 0) return new_oval_defs_list (event, "ovaldef", alert, 0, count_return); if (count_return) { g_warning ("%s: Type error: %s", __func__, (char *) event_data); *count_return = 0; } return g_strdup ("ERROR generating list"); } /** * @brief Create message for New NVTs event. * * @param[in] event Event. * @param[in] event_data Event type specific details. * @param[in] alert Alert. * * @return Freshly allocated message. */ static gchar * new_secinfo_message (event_t event, const void* event_data, alert_t alert) { gchar *type, *list, *message, *name, *point; int count, example; list = new_secinfo_list (event, event_data, alert, &count); assert (count > 0); type = g_strdup (event_data); if (type && (point = strstr (type, "_example"))) { example = 1; point[0] = '\0'; } else example = 0; name = alert_name (alert); message = g_strdup_printf ("%s%i%s%s%s%s, according to the\nalert \"%s\":\n\n%s", example ? "Warning: This is an example alert only.\n\n" : "", count, event == EVENT_NEW_SECINFO ? " new " : " ", count == 1 ? type_name (type) : type_name_plural (type), event == EVENT_NEW_SECINFO ? "" : (count == 1 ? " was" : " were"), event == EVENT_NEW_SECINFO ? " appeared in the feed" : " updated in the feed", name, list); free (name); g_free (list); return message; } /** * @brief Check for updated SCAP SecInfo after an update. */ static void check_for_updated_scap () { if (manage_scap_loaded ()) { if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.cves" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_UPDATED_SECINFO, "cve", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.cpes" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_UPDATED_SECINFO, "cpe", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM scap.ovaldefs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'scap_check_time')" " AS INTEGER)," " 0));")) event (EVENT_UPDATED_SECINFO, "ovaldef", 0, 0); } } /** * @brief Check for updated CERT SecInfo after an update. */ static void check_for_updated_cert () { if (manage_cert_loaded ()) { if (sql_int ("SELECT EXISTS" " (SELECT * FROM cert.cert_bund_advs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0));")) event (EVENT_UPDATED_SECINFO, "cert_bund_adv", 0, 0); if (sql_int ("SELECT EXISTS" " (SELECT * FROM cert.dfn_cert_advs" " WHERE modification_time" " > coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0)" " AND creation_time" " <= coalesce (CAST ((SELECT value FROM meta" " WHERE name" " = 'cert_check_time')" " AS INTEGER)," " 0));")) event (EVENT_UPDATED_SECINFO, "dfn_cert_adv", 0, 0); } } /* Credentials. */ /** * @brief Ensure that there is an encryption key. * * This prevents contention problems that can happen when the key is * created on the fly during a GMP operation. * * Up to caller to create transaction. * * @return 0 success, -1 error. */ static int check_db_encryption_key () { lsc_crypt_ctx_t crypt_ctx; gchar *secret; crypt_ctx = lsc_crypt_new (); /* The encryption layer creates the key if it does not exist. */ secret = lsc_crypt_encrypt (crypt_ctx, "dummy", "dummy", NULL); lsc_crypt_release (crypt_ctx); if (secret == NULL) return -1; g_free (secret); return 0; } /** * @brief Check that a string represents a valid Private Key. * * @param[in] key_str Private Key string. * @param[in] key_phrase Private Key passphrase. * * @return 0 if valid, 1 otherwise. */ int check_private_key (const char *key_str, const char *key_phrase) { gnutls_x509_privkey_t key; gnutls_datum_t data; int ret; assert (key_str); if (gnutls_x509_privkey_init (&key)) return 1; data.size = strlen (key_str); data.data = (void *) g_strdup (key_str); ret = gnutls_x509_privkey_import2 (key, &data, GNUTLS_X509_FMT_PEM, key_phrase, 0); if (ret) { gchar *public_key; public_key = gvm_ssh_public_from_private (key_str, key_phrase); if (public_key == NULL) { gnutls_x509_privkey_deinit (key); g_free (data.data); g_message ("%s: import failed: %s", __func__, gnutls_strerror (ret)); return 1; } g_free (public_key); } g_free (data.data); gnutls_x509_privkey_deinit (key); return 0; } /** * @brief Find a credential for a specific permission, given a UUID. * * @param[in] uuid UUID of credential. * @param[out] credential Credential return, 0 if successfully failed * to find Credential. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find credential), TRUE * on error. */ gboolean find_credential_with_permission (const char* uuid, credential_t* credential, const char *permission) { return find_resource_with_permission ("credential", uuid, credential, permission, 0); } /** * @brief Set data for a credential. * * @param[in] credential The credential. * @param[in] type The data type (e.g. "username" or "secret"). * @param[in] value The value to set or NULL to remove data entry. * * @return 0 on success, -1 on error, 1 credential not found, 99 permission * denied. */ static int set_credential_data (credential_t credential, const char* type, const char* value) { gchar *quoted_type; if (current_credentials.uuid && (acl_user_may ("modify_credential") == 0)) return 99; if (type == NULL) return -1; if (credential == 0) return 1; quoted_type = sql_quote (type); if (sql_int ("SELECT count (*) FROM credentials_data" " WHERE credential = '%llu' AND type = '%s';", credential, quoted_type)) { if (value == NULL) { sql ("DELETE FROM credentials_data" " WHERE credential = '%llu' AND type = '%s';", credential, quoted_type); } else { gchar *quoted_value; quoted_value = sql_quote (value); sql ("UPDATE credentials_data SET value = '%s'" " WHERE credential = %llu AND type = '%s';", quoted_value, credential, quoted_type); g_free (quoted_value); } } else if (value != NULL) { gchar *quoted_value; quoted_value = sql_quote (value); sql ("INSERT INTO credentials_data (credential, type, value)" " VALUES (%llu, '%s', '%s')", credential, quoted_type, quoted_value); g_free (quoted_value); } g_free (quoted_type); return 0; } /** * @brief Test if a username is valid to use in a credential. * * Valid usernames may only contain alphanumeric characters and a few * special ones to avoid problems with installer package generation. * * @param[in] username The username string to test. * * @return Whether the username is valid. */ static int validate_credential_username (const gchar *username) { const char *s; s = username; while (*s) if (isalnum (*s) || strchr ("-_\\.@", *s)) s++; else return 0; return 1; } /** * @brief Test if a username is valid for a credential export format. * * @param[in] username The username string to test. * @param[in] format The credential format to validate for. * * @return Whether the username is valid. */ static gboolean validate_credential_username_for_format (const gchar *username, credential_format_t format) { const char *s, *name_characters; name_characters = NULL; switch (format) { case CREDENTIAL_FORMAT_NONE: case CREDENTIAL_FORMAT_KEY: case CREDENTIAL_FORMAT_PEM: // No validation required break; case CREDENTIAL_FORMAT_RPM: case CREDENTIAL_FORMAT_DEB: name_characters = "-_"; break; case CREDENTIAL_FORMAT_EXE: name_characters = "-_\\.@"; break; case CREDENTIAL_FORMAT_ERROR: return 0; } if (name_characters) { s = username; while (*s) if (isalnum (*s) || strchr (name_characters, *s)) s++; else return FALSE; } return TRUE; } /** * @brief Length of password generated in create_credential. */ #define PASSWORD_LENGTH 10 /** * @brief Create a Credential. * * @param[in] name Name of LSC credential. Must be at least one * character long. * @param[in] comment Comment on LSC credential. * @param[in] login Name of LSC credential user. Must be at least * one character long. * @param[in] given_password Password for password-only credential, NULL to * generate credentials. * @param[in] key_private Private key, or NULL. * @param[in] key_public Public key, or NULL. * @param[in] certificate Certificate, or NULL. * @param[in] community SNMP community string, or NULL. * @param[in] auth_algorithm SNMP authentication algorithm, or NULL. * @param[in] privacy_password SNMP privacy password. * @param[in] privacy_algorithm SNMP privacy algorithm. * @param[in] given_type Credential type or NULL. * @param[in] allow_insecure Whether to allow insecure uses. * @param[out] credential Created Credential. * * @return 0 success, 1 LSC credential exists already, 2 invalid username, * 3 Failed to create public key from private key/password, * 4 Invalid credential type, 5 login username missing, * 6 password missing, 7 private key missing, 8 certificate missing, * 9 public key missing, 10 autogenerate not supported, * 11 community missing, 12 auth algorithm missing, * 14 privacy algorithm missing, * 15 invalid auth algorithm, 16 invalid privacy algorithm, * 17 invalid certificate, 99 permission denied, -1 error. */ int create_credential (const char* name, const char* comment, const char* login, const char* given_password, const char* key_private, const char* key_public, const char* certificate, const char* community, const char* auth_algorithm, const char* privacy_password, const char* privacy_algorithm, const char* given_type, const char* allow_insecure, credential_t *credential) { gchar *quoted_name, *quoted_comment, *quoted_type; int i; GRand *rand; gchar generated_password[PASSWORD_LENGTH]; gchar *generated_private_key; credential_t new_credential; int auto_generate, allow_insecure_int; int using_snmp_v3; int ret; assert (name && strlen (name) > 0); assert (current_credentials.uuid); assert (comment); sql_begin_immediate (); if (acl_user_may ("create_credential") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (name, "credential", 0)) { sql_rollback (); return 1; } if (allow_insecure && strcmp (allow_insecure, "") && strcmp (allow_insecure, "0")) allow_insecure_int = 1; else allow_insecure_int = 0; if (given_type && strcmp (given_type, "")) { if (strcmp (given_type, "cc") && strcmp (given_type, "pgp") && strcmp (given_type, "pw") && strcmp (given_type, "snmp") && strcmp (given_type, "smime") && strcmp (given_type, "up") && strcmp (given_type, "usk")) { sql_rollback (); return 4; } else quoted_type = g_strdup (given_type); } else if (community || (login && given_password && auth_algorithm)) quoted_type = g_strdup ("snmp"); else if (certificate && key_private) quoted_type = g_strdup ("cc"); else if (login && key_private) quoted_type = g_strdup ("usk"); else if (login && given_password) quoted_type = g_strdup ("up"); else if (login && key_private == NULL && given_password == NULL) quoted_type = g_strdup ("usk"); /* auto-generate */ else { g_warning ("%s: Cannot determine type of new credential", __func__); return -1; } /* Validate credential data */ auto_generate = ((given_password == NULL) && (key_private == NULL) && (key_public == NULL) && (certificate == NULL) && (community == NULL)); ret = 0; if (auto_generate && (strcmp (quoted_type, "cc") == 0 || strcmp (quoted_type, "pgp") == 0 || strcmp (quoted_type, "smime") == 0 || strcmp (quoted_type, "snmp") == 0)) ret = 10; // Type does not support autogenerate using_snmp_v3 = 0; if (login == NULL && strcmp (quoted_type, "cc") && strcmp (quoted_type, "pgp") && strcmp (quoted_type, "pw") && strcmp (quoted_type, "smime") && strcmp (quoted_type, "snmp")) ret = 5; else if (given_password == NULL && auto_generate == 0 && (strcmp (quoted_type, "up") == 0 || strcmp (quoted_type, "pw") == 0)) // (username) password requires a password ret = 6; else if (key_private == NULL && auto_generate == 0 && (strcmp (quoted_type, "cc") == 0 || strcmp (quoted_type, "usk") == 0)) ret = 7; else if (certificate == NULL && auto_generate == 0 && (strcmp (quoted_type, "cc") == 0 || strcmp (quoted_type, "smime") == 0)) ret = 8; else if (key_public == NULL && auto_generate == 0 && strcmp (quoted_type, "pgp") == 0) ret = 9; else if (strcmp (quoted_type, "snmp") == 0) { if (login || given_password || auth_algorithm || privacy_password || privacy_algorithm) using_snmp_v3 = 1; if (community == NULL) { if (login == NULL || given_password == NULL) ret = 11; } else if (auth_algorithm == NULL && using_snmp_v3) ret = 12; else if ((privacy_algorithm == NULL || strcmp (privacy_algorithm, "") == 0) && privacy_password && strcmp (privacy_password, "")) ret = 14; else if (auth_algorithm && strcmp (auth_algorithm, "md5") && strcmp (auth_algorithm, "sha1")) ret = 15; else if (privacy_algorithm && strcmp (privacy_algorithm, "") && strcmp (privacy_algorithm, "aes") && strcmp (privacy_algorithm, "des")) ret = 16; } if (ret) { g_free (quoted_type); sql_rollback (); return ret; } /* Create base credential */ quoted_name = sql_quote (name); quoted_comment = sql_quote (comment ? comment : ""); sql ("INSERT INTO credentials" " (uuid, name, owner, comment, creation_time, modification_time," " type, allow_insecure)" " VALUES" " (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', m_now (), m_now (), '%s', %d);", quoted_name, current_credentials.uuid, quoted_comment, quoted_type, allow_insecure_int); g_free (quoted_name); g_free (quoted_comment); new_credential = sql_last_insert_id (); /* Add non-secret data */ if (login) { /* * Ensure the login does not contain characters that cause problems * with package generation. */ if (validate_credential_username (login) == 0) { sql_rollback (); return 2; } set_credential_data (new_credential, "username", login); } if (key_public) set_credential_data (new_credential, "public_key", key_public); if (certificate) { gchar *certificate_truncated; certificate_truncated = truncate_certificate (certificate); if (certificate_truncated) set_credential_data (new_credential, "certificate", certificate_truncated); else { sql_rollback(); return 17; } g_free (certificate_truncated); } if (auth_algorithm) set_credential_data (new_credential, "auth_algorithm", auth_algorithm); if (privacy_algorithm) set_credential_data (new_credential, "privacy_algorithm", privacy_algorithm); g_free (quoted_type); /* Add secret data like passwords and private keys */ /* Private key with optional passphrase */ if (key_private) { lsc_crypt_ctx_t crypt_ctx; gchar *key_private_truncated, *generated_key_public; /* Try truncate the private key, but if that fails try get the public * key anyway, in case it's a key type that truncate_private_key does * not understand. */ if (key_private) key_private_truncated = truncate_private_key (key_private); else return 3; generated_key_public = gvm_ssh_public_from_private (key_private_truncated ? key_private_truncated : key_private, given_password); if (generated_key_public == NULL) { g_free (key_private_truncated); return 3; } g_free (generated_key_public); /* Encrypt password and private key. Note that we do not need to call sql_quote because the result of the encryption is base64 encoded and does not contain apostrophes. */ if (!disable_encrypted_credentials) { gchar *secret; crypt_ctx = lsc_crypt_new (); secret = lsc_crypt_encrypt (crypt_ctx, "password", given_password, "private_key", key_private, NULL); if (!secret) { lsc_crypt_release (crypt_ctx); sql_rollback (); return -1; } set_credential_data (new_credential, "secret", secret); g_free (secret); } else { crypt_ctx = NULL; set_credential_data (new_credential, "password", given_password); set_credential_data (new_credential, "private_key", key_private); } lsc_crypt_release (crypt_ctx); if (credential) *credential = new_credential; sql_commit (); return 0; } /* SNMP passwords */ if (community || using_snmp_v3) { lsc_crypt_ctx_t crypt_ctx; /* Encrypt passwords. Note that we do not need to call sql_quote because the result of the encryption is base64 encoded and does not contain apostrophes. */ if (!disable_encrypted_credentials) { gchar *secret; crypt_ctx = lsc_crypt_new (); secret = lsc_crypt_encrypt (crypt_ctx, "community", community, "password", given_password, "privacy_password", privacy_password ? privacy_password : "", NULL); if (!secret) { lsc_crypt_release (crypt_ctx); sql_rollback (); return -1; } set_credential_data (new_credential, "secret", secret); g_free (secret); } else { crypt_ctx = NULL; set_credential_data (new_credential, "community", community); set_credential_data (new_credential, "password", given_password); set_credential_data (new_credential, "privacy_password", privacy_password ? privacy_password : ""); } lsc_crypt_release (crypt_ctx); if (credential) *credential = new_credential; sql_commit (); return 0; } /* Password only */ if (given_password) { lsc_crypt_ctx_t crypt_ctx; /* Password-only credential. */ if (!disable_encrypted_credentials) { crypt_ctx = lsc_crypt_new (); gchar *secret = lsc_crypt_encrypt (crypt_ctx, "password", given_password, NULL); if (!secret) { lsc_crypt_release (crypt_ctx); sql_rollback (); return -1; } set_credential_data (new_credential, "secret", secret); g_free (secret); } else { crypt_ctx = NULL; set_credential_data (new_credential, "password", given_password); } if (credential) *credential = new_credential; sql_commit (); return 0; } /* * Auto-generate credential */ /* Create the keys and packages. */ rand = g_rand_new (); for (i = 0; i < PASSWORD_LENGTH - 1; i++) { generated_password[i] = (gchar) g_rand_int_range (rand, '0', 'z'); if (generated_password[i] == '\\') generated_password[i] = '{'; } generated_password[PASSWORD_LENGTH - 1] = '\0'; g_rand_free (rand); if (given_type == NULL || strcmp (given_type, "usk") == 0) { if (lsc_user_keys_create (generated_password, &generated_private_key)) { sql_rollback (); return -1; } } else generated_private_key = NULL; { lsc_crypt_ctx_t crypt_ctx; /* Generated key credential. */ if (!disable_encrypted_credentials) { gchar *secret; crypt_ctx = lsc_crypt_new (); if (generated_private_key) secret = lsc_crypt_encrypt (crypt_ctx, "password", generated_password, "private_key", generated_private_key, NULL); else secret = lsc_crypt_encrypt (crypt_ctx, "password", generated_password, NULL); if (!secret) { lsc_crypt_release (crypt_ctx); sql_rollback (); return -1; } set_credential_data (new_credential, "secret", secret); g_free (secret); } else { set_credential_data (new_credential, "password", generated_password); if (generated_private_key) set_credential_data (new_credential, "private_key", generated_private_key); crypt_ctx = NULL; } lsc_crypt_release (crypt_ctx); g_free (generated_private_key); } if (credential) *credential = new_credential; sql_commit (); return 0; } /** * @brief Create an LSC Credential from an existing one. * * @param[in] name Name of new Credential. NULL to copy * from existing. * @param[in] comment Comment on new Credential. NULL to copy * from existing. * @param[in] credential_id UUID of existing Credential. * @param[out] new_credential New Credential. * * @return 0 success, 1 Credential exists already, 2 failed to find * existing Credential, -1 error. */ int copy_credential (const char* name, const char* comment, const char *credential_id, credential_t* new_credential) { int ret; credential_t credential; assert (new_credential); ret = copy_resource ("credential", name, comment, credential_id, "type", 1, new_credential, &credential); if (ret) return ret; sql ("INSERT INTO credentials_data (credential, type, value)" " SELECT %llu, type, value FROM credentials_data" " WHERE credential = %llu;", *new_credential, credential); return 0; } /** * @brief Modify a Credential. * * @param[in] credential_id UUID of Credential. * @param[in] name Name of Credential. * @param[in] comment Comment on Credential. * @param[in] login Login of Credential. * @param[in] password Password or passphrase of Credential. * @param[in] key_private Private key of Credential. * @param[in] key_public Public key of Credential. * @param[in] certificate Certificate of Credential. * @param[in] community SNMP Community of Credential. * @param[in] auth_algorithm Authentication algorithm of Credential. * @param[in] privacy_password Privacy password of Credential. * @param[in] privacy_algorithm Privacy algorithm of Credential. * @param[in] allow_insecure Whether to allow insecure use. * * @return 0 success, 1 failed to find credential, 2 credential with new name * exists, 3 credential_id required, 4 invalid login name, * 5 invalid certificate, 6 invalid auth_algorithm, * 7 invalid privacy_algorithm, 8 invalid private key, * 9 invalid public key, * 10 privacy password must be empty if algorithm is empty * 99 permission denied, * -1 internal error. */ int modify_credential (const char *credential_id, const char *name, const char *comment, const char *login, const char* password, const char* key_private, const char* key_public, const char* certificate, const char* community, const char* auth_algorithm, const char* privacy_password, const char* privacy_algorithm, const char* allow_insecure) { credential_t credential; iterator_t iterator; int ret; if (credential_id == NULL) return 3; sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_credential") == 0) { sql_rollback (); return 99; } credential = 0; if (find_credential_with_permission (credential_id, &credential, "modify_credential")) { sql_rollback (); return -1; } if (credential == 0) { sql_rollback (); return 1; } /* Check whether a credential with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "credential", credential)) { sql_rollback (); return 2; } } /* Update values */ if (name) set_credential_name (credential, name); if (comment) set_credential_comment (credential, comment); if (allow_insecure) { if (strcmp (allow_insecure, "") && strcmp (allow_insecure, "0")) sql ("UPDATE credentials SET allow_insecure = 1 WHERE id = %llu;", credential); else sql ("UPDATE credentials SET allow_insecure = 0 WHERE id = %llu;", credential); } ret = 0; if (login && ret == 0) { /* * Ensure the login is not empty and does not contain characters that * cause problems with package generation. */ if (strcmp (login, "") == 0 || validate_credential_username (login) == 0) ret = 4; if (ret == 0) set_credential_login (credential, login); } if (certificate && ret == 0) { // Truncate certificate which also validates it. gchar *certificate_truncated; certificate_truncated = truncate_certificate (certificate); if (certificate_truncated) { set_credential_certificate (credential, certificate_truncated); g_free (certificate_truncated); } else ret = 5; } if (auth_algorithm && ret == 0) { if (strcmp (auth_algorithm, "md5") && strcmp (auth_algorithm, "sha1")) ret = 6; else set_credential_auth_algorithm (credential, auth_algorithm); } if (privacy_algorithm && ret == 0) { if (strcmp (privacy_algorithm, "aes") && strcmp (privacy_algorithm, "des") && strcmp (privacy_algorithm, "")) ret = 7; else { iterator_t credential_iterator; const char *used_password; init_credential_iterator_one (&credential_iterator, credential); if (privacy_password) used_password = privacy_password; else { next (&credential_iterator); used_password = credential_iterator_privacy_password (&credential_iterator); if (used_password == NULL) used_password = ""; } // Privacy password must be empty if algorithm is empty if (strcmp (privacy_algorithm, "") == 0 && strcmp (used_password, "")) ret = 10; else set_credential_privacy_algorithm (credential, privacy_algorithm); cleanup_iterator (&credential_iterator); } } if (key_public && ret == 0) { set_credential_public_key (credential, key_public); } if (ret) { sql_rollback (); return ret; } init_credential_iterator_one (&iterator, credential); if (next (&iterator)) { gchar *key_private_truncated = NULL; const char *key_private_to_use; const char *type = credential_iterator_type (&iterator); if (key_private) { char *generated_key_public = NULL; /* Try truncate the private key, but if that fails try get the * public key anyway, in case it's a key type that * truncate_private_key does not understand. */ key_private_truncated = truncate_private_key (key_private); key_private_to_use = key_private_truncated ? key_private_truncated : key_private; if (strcmp (type, "cc") == 0) { generated_key_public = gvm_ssh_public_from_private (key_private_to_use, NULL); } else if (strcmp (type, "usk") == 0) { generated_key_public = gvm_ssh_public_from_private (key_private_to_use, password ? password : credential_iterator_password (&iterator)); } if (generated_key_public == NULL) { sql_rollback (); cleanup_iterator (&iterator); g_free (generated_key_public); return 8; } g_free (generated_key_public); } else key_private_to_use = NULL; if (strcmp (type, "cc") == 0) { if (key_private_to_use) set_credential_private_key (credential, key_private_to_use, NULL); } else if (strcmp (type, "up") == 0 || strcmp (type, "pw") == 0) { if (password) set_credential_password (credential, password); } else if (strcmp (type, "usk") == 0) { if (key_private_to_use || password) { if (check_private_key (key_private_truncated ? key_private_to_use : credential_iterator_private_key (&iterator), password ? password : credential_iterator_password (&iterator))) { sql_rollback (); cleanup_iterator (&iterator); return 8; } set_credential_private_key (credential, key_private_truncated ? key_private_to_use : credential_iterator_private_key (&iterator), password ? password : credential_iterator_password (&iterator)); } } else if (strcmp (type, "snmp") == 0) { if (privacy_password) { const char *used_algorithm; // Privacy_algorithm should be set above if given used_algorithm = credential_iterator_privacy_algorithm (&iterator); if (used_algorithm == NULL) used_algorithm = ""; // privacy password must be empty if algorithm is empty if (strcmp (used_algorithm, "") == 0 && strcmp (privacy_password, "")) { sql_rollback (); cleanup_iterator (&iterator); return 10; } } if (community || password || privacy_password) { set_credential_snmp_secret (credential, community ? community : credential_iterator_community (&iterator), password ? password : credential_iterator_password (&iterator), privacy_password ? privacy_password : credential_iterator_privacy_password (&iterator)); } } else if (strcmp (type, "pgp") == 0 || strcmp (type, "smime") == 0) { set_credential_data (credential, "secret", ""); } else { g_warning ("%s: Unknown credential type: %s", __func__, type); sql_rollback (); cleanup_iterator (&iterator); g_free (key_private_truncated); return -1; } g_free (key_private_truncated); } else { g_warning ("%s: credential iterator next() failed", __func__); sql_rollback (); cleanup_iterator (&iterator); return -1; } cleanup_iterator (&iterator); sql_commit (); return 0; } /** * @brief Delete a Credential. * * @param[in] credential_id UUID of Credential. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because the credential is in use, * 2 failed to find credential, 99 permission denied, -1 error. */ int delete_credential (const char *credential_id, int ultimate) { credential_t credential = 0; sql_begin_immediate (); if (acl_user_may ("delete_credential") == 0) { sql_rollback (); return 99; } if (find_credential_with_permission (credential_id, &credential, "delete_credential")) { sql_rollback (); return -1; } if (credential == 0) { if (find_trash ("credential", credential_id, &credential)) { sql_rollback (); return -1; } if (credential == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by another resource in the trashcan. */ if (trash_credential_in_use (credential)) { sql_rollback (); return 1; } permissions_set_orphans ("credential", credential, LOCATION_TRASH); tags_remove_resource ("credential", credential, LOCATION_TRASH); sql ("DELETE FROM credentials_trash_data WHERE credential = %llu;", credential); sql ("DELETE FROM credentials_trash WHERE id = %llu;", credential); sql_commit (); return 0; } /* Check if it's in use by another resource. */ if (credential_in_use (credential)) { sql_rollback (); return 1; } if (ultimate == 0) { credential_t trash_credential; sql ("INSERT INTO credentials_trash" " (uuid, owner, name, comment, creation_time," " modification_time, type)" " SELECT uuid, owner, name, comment, creation_time," " modification_time, type" " FROM credentials WHERE id = %llu;", credential); trash_credential = sql_last_insert_id (); sql ("INSERT INTO credentials_trash_data" " (credential, type, value)" " SELECT %llu, type, value" " FROM credentials_data" " WHERE credential = %llu", trash_credential, credential); /* Update the credential references in any trashcan targets or scanners. * This situation is possible if the user deletes the credential when * the target or scanner is in the trashcan. */ sql ("UPDATE targets_trash_login_data" " SET credential_location = " G_STRINGIFY (LOCATION_TRASH) "," " credential = %llu" " WHERE credential = %llu" " AND credential_location = " G_STRINGIFY (LOCATION_TABLE) ";", trash_credential, credential); sql ("UPDATE scanners_trash" " SET credential_location = " G_STRINGIFY (LOCATION_TRASH) "," " credential = %llu" " WHERE credential = %llu" " AND credential_location = " G_STRINGIFY (LOCATION_TABLE) ";", trash_credential, credential); permissions_set_locations ("credential", credential, trash_credential, LOCATION_TRASH); tags_set_locations ("credential", credential, trash_credential, LOCATION_TRASH); } else { permissions_set_orphans ("credential", credential, LOCATION_TABLE); tags_remove_resource ("credential", credential, LOCATION_TABLE); } sql ("DELETE FROM credentials_data WHERE credential = %llu;", credential); sql ("DELETE FROM credentials WHERE id = %llu;", credential); sql_commit (); return 0; } /** * @brief Filter columns for LSC Credential iterator. */ #define CREDENTIAL_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "login", "type", "allow_insecure", NULL } /** * @brief LSC Credential iterator columns. */ #define CREDENTIAL_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (credentials), \ /* public generic data */ \ { "type", NULL, KEYWORD_TYPE_STRING }, \ { "allow_insecure", NULL, KEYWORD_TYPE_INTEGER }, \ /* public type specific data */ \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'username')", \ "login", \ KEYWORD_TYPE_STRING \ }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'certificate')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'auth_algorithm')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'privacy_algorithm')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'public_key')", \ NULL, \ KEYWORD_TYPE_STRING }, \ /* private data */ \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'secret')", \ "secret", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'password')", \ "password", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'private_key')", \ "private_key", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'community')", \ "community", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials.id AND type = 'privacy_password')", \ "privacy_password", \ KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief LSC Credential iterator columns for trash case. */ #define CREDENTIAL_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (credentials_trash), \ /* public generic data */ \ { "type", NULL, KEYWORD_TYPE_STRING }, \ { "allow_insecure", NULL, KEYWORD_TYPE_INTEGER }, \ /* public type specific data */ \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'username')", \ "login", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'certificate')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id" \ " AND type = 'auth_algorithm')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id" \ " AND type = 'privacy_algorithm')", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_data" \ " WHERE credential = credentials_trash.id AND type = 'public_key')", \ NULL, \ KEYWORD_TYPE_STRING }, \ /* private data */ \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'secret')", \ "secret", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'password')", \ "password", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'private_key')", \ "private_key", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id AND type = 'community')", \ "community", \ KEYWORD_TYPE_STRING }, \ { "(SELECT value FROM credentials_trash_data" \ " WHERE credential = credentials_trash.id" \ " AND type = 'privacy_password')", \ "privacy_password", \ KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of LSC Credentials. * * @param[in] get GET params. * * @return Total number of LSC Credentials in filtered set. */ int credential_count (const get_data_t *get) { static const char *filter_columns[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS; static column_t columns[] = CREDENTIAL_ITERATOR_COLUMNS; static column_t trash_columns[] = CREDENTIAL_ITERATOR_TRASH_COLUMNS; return count ("credential", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Check whether a Credential is in use. * * @param[in] credential Credential. * * @return 1 yes, 0 no. */ int credential_in_use (credential_t credential) { int ret; char *uuid = credential_uuid (credential); ret = !!(sql_int ("SELECT count (*) FROM targets_login_data" " WHERE credential = %llu;", credential) || sql_int ("SELECT count (*) FROM scanners" " WHERE credential = %llu;", credential) || sql_int ("SELECT count (*) FROM alert_method_data" " WHERE (name = 'recipient_credential'" " OR name = 'scp_credential'" " OR name = 'smb_credential'" " OR name = 'tp_sms_credential'" " OR name = 'verinice_server_credential'" " OR name = 'pkcs12_credential')" " AND data = '%s'", uuid)); free (uuid); return ret; } /** * @brief Check whether a trashcan Credential is in use. * * @param[in] credential Credential. * * @return 1 yes, 0 no. */ int trash_credential_in_use (credential_t credential) { int ret; char *uuid = trash_credential_uuid (credential); ret = !!(sql_int ("SELECT count (*) FROM targets_trash_login_data" " WHERE credential = %llu" " AND credential_location" " = " G_STRINGIFY (LOCATION_TRASH) ";", credential) || sql_int ("SELECT count (*) FROM scanners_trash" " WHERE credential = %llu" " AND credential_location" " = " G_STRINGIFY (LOCATION_TRASH) ";", credential) || sql_int ("SELECT count (*) FROM alert_method_data_trash" " WHERE (name = 'recipient_credential'" " OR name = 'scp_credential'" " OR name = 'smb_credential'" " OR name = 'tp_sms_credential'" " OR name = 'verinice_server_credential'" " OR name = 'pkcs12_credential')" " AND data = '%s'", uuid)); free (uuid); return ret; } /** * @brief Check whether a Credential is writable. * * @param[in] credential Credential. * * @return 1 yes, 0 no. */ int credential_writable (credential_t credential) { return 1; } /** * @brief Check whether a trashcan Credential is writable. * * @param[in] credential Credential. * * @return 1 yes, 0 no. */ int trash_credential_writable (credential_t credential) { return 1; } /** * @brief Get a value from a credential. * * @param[in] credential The Credential. * @param[in] value_name Name of the value. * * @return Value. */ gchar * credential_value (credential_t credential, const char* value_name) { if (credential == 0) return NULL; return sql_string ("SELECT value FROM credentials_data" " WHERE credential = %llu" " AND type = '%s';", credential, value_name); } /** * @brief Get a possibly encrypted credential value in decrypted form. * * @param[in] credential The Credential. * @param[in] value_name Name of the value. * * @return Value. */ gchar * credential_encrypted_value (credential_t credential, const char* value_name) { gchar *value; value = sql_string ("SELECT value FROM credentials_data" " WHERE credential = %llu" " AND type = '%s';", credential, value_name); if (value == NULL) { gchar *secret; const char* decrypted; lsc_crypt_ctx_t crypt_ctx; crypt_ctx = lsc_crypt_new (); secret = sql_string ("SELECT value FROM credentials_data" " WHERE credential = %llu" " AND type = 'secret';", credential); decrypted = lsc_crypt_decrypt (crypt_ctx, secret, value_name); if (decrypted) value = g_strdup (decrypted); lsc_crypt_release (crypt_ctx); g_free (secret); } return value; } /** * @brief Set the name of a Credential. * * @param[in] credential The Credential. * @param[in] name Name. */ static void set_credential_name (credential_t credential, const char *name) { gchar *quoted_name = sql_quote (name); sql ("UPDATE credentials SET name = '%s', modification_time = m_now ()" " WHERE id = %llu;", quoted_name, credential); g_free (quoted_name); } /** * @brief Set the comment of a Credential. * * @param[in] credential The Credential. * @param[in] comment Comment. */ static void set_credential_comment (credential_t credential, const char *comment) { gchar *quoted_comment = sql_quote (comment); sql ("UPDATE credentials SET comment = '%s', modification_time = m_now ()" " WHERE id = %llu;", quoted_comment, credential); g_free (quoted_comment); } /** * @brief Set the login of a Credential. * * @param[in] credential The Credential. * @param[in] login Login. */ static void set_credential_login (credential_t credential, const char *login) { set_credential_data (credential, "username", login); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", credential); } /** * @brief Set the certificate of a Credential. * * @param[in] credential The Credential. * @param[in] certificate Certificate. */ static void set_credential_certificate (credential_t credential, const char *certificate) { set_credential_data (credential, "certificate", certificate); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", credential); } /** * @brief Set the auth_algorithm of a Credential. * * @param[in] credential The Credential. * @param[in] algorithm Authentication algorithm. */ static void set_credential_auth_algorithm (credential_t credential, const char *algorithm) { set_credential_data (credential, "auth_algorithm", algorithm); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", credential); } /** * @brief Set the privacy_algorithm of a Credential. * * @param[in] credential The Credential. * @param[in] algorithm Privacy algorithm. */ void set_credential_privacy_algorithm (credential_t credential, const char *algorithm) { set_credential_data (credential, "privacy_algorithm", algorithm); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", credential); } /** * @brief Set the password of a Credential. * * @param[in] credential The Credential. * @param[in] password Password. */ static void set_credential_password (credential_t credential, const char *password) { lsc_crypt_ctx_t crypt_ctx; if (!disable_encrypted_credentials) { gchar *encrypted_blob; crypt_ctx = lsc_crypt_new (); encrypted_blob = lsc_crypt_encrypt (crypt_ctx, "password", password, NULL); if (!encrypted_blob) { g_critical ("%s: encryption failed", G_STRFUNC); lsc_crypt_release (crypt_ctx); return; } set_credential_data (credential, "secret", encrypted_blob); set_credential_data (credential, "password", NULL); set_credential_data (credential, "private_key", NULL); g_free (encrypted_blob); } else { crypt_ctx = NULL; set_credential_data (credential, "secret", NULL); set_credential_data (credential, "password", password); set_credential_data (credential, "private_key", NULL); } sql ("UPDATE credentials SET modification_time = m_now ()" " WHERE id = %llu;", credential); lsc_crypt_release (crypt_ctx); } /** * @brief Set the private key and passphrase of a Credential. * * @param[in] credential The Credential. * @param[in] private_key Private key. * @param[in] passphrase Passphrase. */ static void set_credential_private_key (credential_t credential, const char *private_key, const char *passphrase) { lsc_crypt_ctx_t crypt_ctx; if (!disable_encrypted_credentials) { gchar *encrypted_blob; crypt_ctx = lsc_crypt_new (); encrypted_blob = lsc_crypt_encrypt (crypt_ctx, "private_key", private_key, "password", passphrase, NULL); if (!encrypted_blob) { g_critical ("%s: encryption failed", G_STRFUNC); lsc_crypt_release (crypt_ctx); return; } set_credential_data (credential, "secret", encrypted_blob); set_credential_data (credential, "password", NULL); set_credential_data (credential, "private_key", NULL); g_free (encrypted_blob); } else { crypt_ctx = NULL; set_credential_data (credential, "secret", NULL); set_credential_data (credential, "password", passphrase); set_credential_data (credential, "private_key", private_key); } sql ("UPDATE credentials SET modification_time = m_now ()" " WHERE id = %llu;", credential); lsc_crypt_release (crypt_ctx); } /** * @brief Set the public key of a Credential. * * @param[in] credential The Credential. * @param[in] public_key Public key. */ void set_credential_public_key (credential_t credential, const char *public_key) { set_credential_data (credential, "public_key", public_key); sql ("UPDATE credentials SET" " modification_time = m_now ()" " WHERE id = %llu;", credential); } /** * @brief Set the community, password and privacy password of a Credential. * * @param[in] credential The Credential. * @param[in] community SNMP community. * @param[in] password Authentication password. * @param[in] privacy_password Privacy password. */ static void set_credential_snmp_secret (credential_t credential, const char* community, const char *password, const char *privacy_password) { lsc_crypt_ctx_t crypt_ctx; if (!disable_encrypted_credentials) { gchar *encrypted_blob; crypt_ctx = lsc_crypt_new (); encrypted_blob = lsc_crypt_encrypt (crypt_ctx, "community", community, "password", password, "privacy_password", privacy_password, NULL); if (!encrypted_blob) { g_critical ("%s: encryption failed", G_STRFUNC); lsc_crypt_release (crypt_ctx); return; } set_credential_data (credential, "secret", encrypted_blob); set_credential_data (credential, "community", NULL); set_credential_data (credential, "password", NULL); set_credential_data (credential, "privacy_password", NULL); g_free (encrypted_blob); } else { crypt_ctx = NULL; set_credential_data (credential, "secret", NULL); set_credential_data (credential, "community", community); set_credential_data (credential, "password", password); set_credential_data (credential, "privacy_password", privacy_password); } sql ("UPDATE credentials SET modification_time = m_now ()" " WHERE id = %llu;", credential); lsc_crypt_release (crypt_ctx); } /** * @brief Initialise a Credential iterator, given a single Credential. * * @param[in] iterator Iterator. * @param[in] credential Single Credential to iterate. */ void init_credential_iterator_one (iterator_t* iterator, credential_t credential) { get_data_t get; assert (credential); memset (&get, '\0', sizeof (get)); get.id = credential_uuid (credential); get.filter = "owner=any permission=get_credentials"; /* We could pass the return up to the caller, but we don't pass in * a filter id and the callers are all in situations where the * credential cannot disappear, so it's safe to ignore the return. */ init_credential_iterator (iterator, &get); } /** * @brief Initialise a Credential iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find filter, 2 failed to find * filter (filt_id), -1 error. */ int init_credential_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS; static column_t columns[] = CREDENTIAL_ITERATOR_COLUMNS; static column_t trash_columns[] = CREDENTIAL_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "credential", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get possibly encrypted data from credentials. * * @param[in] iterator Iterator. * @param[in] type Type of data. * * @return Data. */ static const char* credential_iterator_encrypted_data (iterator_t* iterator, const char* type) { const char *secret, *unencrypted; if (iterator->done) return NULL; secret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 7); if (type == NULL) { g_warning ("%s: NULL data type given", __func__); return NULL; } else if (strcmp (type, "password") == 0) unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8); else if (strcmp (type, "private_key") == 0) unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9); else if (strcmp (type, "community") == 0) unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 10); else if (strcmp (type, "privacy_password") == 0) unencrypted = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 11); else { g_warning ("%s: unknown data type \"%s\"", __func__, type); return NULL; } /* If we do not have a private key, there is no encrypted data. Return the password as is or NULL. */ if (secret) { /* This is an encrypted credential. */ if (!iterator->crypt_ctx) iterator->crypt_ctx = lsc_crypt_new (); return lsc_crypt_decrypt (iterator->crypt_ctx, secret, type); } else { return unencrypted; } } /** * @brief Get the login from a Credential iterator. * * @param[in] iterator Iterator. * * @return Login, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_type, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the login from a Credential iterator. * * @param[in] iterator Iterator. * * @return Login, or NULL if iteration is complete. Freed by * cleanup_iterator. */ int credential_iterator_allow_insecure (iterator_t* iterator) { return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); } /** * @brief Get the credential type abbreviation from an LSC credential iterator. * * @param[in] iterator Iterator. * * @return Credential type, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_login, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the certificate from a Credential iterator. * * @param[in] iterator Iterator. * * @return Certificate, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_certificate, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the authentication algorithm from an LSC credential iterator. * * @param[in] iterator Iterator. * * @return Auth algorithm, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_auth_algorithm, GET_ITERATOR_COLUMN_COUNT + 4); /** * @brief Get the authentication algorithm from an LSC credential iterator. * * @param[in] iterator Iterator. * * @return Auth algorithm, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_privacy_algorithm, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the public key from an LSC credential iterator. * * @param[in] iterator Iterator. * * @return Public key, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_iterator_public_key, GET_ITERATOR_COLUMN_COUNT + 6); /** * @brief Get the password from a Credential iterator. * * @param[in] iterator Iterator. * * @return Password, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* credential_iterator_password (iterator_t* iterator) { return credential_iterator_encrypted_data (iterator, "password"); } /** * @brief Get the private_key from a Credential iterator. * * @param[in] iterator Iterator. * * @return Private_key, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* credential_iterator_private_key (iterator_t* iterator) { return credential_iterator_encrypted_data (iterator, "private_key"); } /** * @brief Get the SNMP community from a Credential iterator. * * @param[in] iterator Iterator. * * @return SNMP community, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* credential_iterator_community (iterator_t* iterator) { return credential_iterator_encrypted_data (iterator, "community"); } /** * @brief Get the privacy password from a Credential iterator. * * @param[in] iterator Iterator. * * @return SNMP community, or NULL if iteration is complete. Freed by * cleanup_iterator. */ const char* credential_iterator_privacy_password (iterator_t* iterator) { return credential_iterator_encrypted_data (iterator, "privacy_password"); } /** * @brief Get the rpm from a Credential iterator. * * @param[in] iterator Iterator. * * @return Rpm, or NULL if iteration is complete. Free with g_free(). */ char* credential_iterator_rpm (iterator_t *iterator) { const char *private_key, *login, *pass; void *rpm; char *public_key; gsize rpm_size; gchar *rpm64; if (iterator->done) return NULL; private_key = credential_iterator_private_key (iterator); pass = credential_iterator_password (iterator); public_key = gvm_ssh_public_from_private (private_key, pass); if (!public_key) return NULL; login = credential_iterator_login (iterator); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_RPM) == FALSE) { g_free (public_key); return NULL; } else if (lsc_user_rpm_recreate (login, public_key, &rpm, &rpm_size)) { g_warning ("%s: Failed to create RPM", __func__); g_free (public_key); return NULL; } g_free (public_key); rpm64 = (rpm && rpm_size) ? g_base64_encode (rpm, rpm_size) : g_strdup (""); free (rpm); return rpm64; } /** * @brief Get the deb from a Credential iterator. * * @param[in] iterator Iterator. * * @return Deb, or NULL if iteration is complete. Free with g_free(). */ char* credential_iterator_deb (iterator_t *iterator) { const char *login, *private_key, *pass; char *public_key, *maintainer; void *deb; gsize deb_size; gchar *deb64; if (iterator->done) return NULL; maintainer = NULL; setting_value (SETTING_UUID_LSC_DEB_MAINTAINER, &maintainer); private_key = credential_iterator_private_key (iterator); pass = credential_iterator_password (iterator); public_key = gvm_ssh_public_from_private (private_key, pass); if (!public_key) return NULL; login = credential_iterator_login (iterator); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_DEB) == FALSE) { g_free (public_key); free (maintainer); return NULL; } else if (lsc_user_deb_recreate (login, public_key, maintainer ? maintainer : "", &deb, &deb_size)) { g_warning ("%s: Failed to create DEB", __func__); g_free (public_key); free (maintainer); return NULL; } g_free (public_key); free (maintainer); deb64 = (deb && deb_size) ? g_base64_encode (deb, deb_size) : g_strdup (""); free (deb); return deb64; } /** * @brief Get the exe from a Credential iterator. * * @param[in] iterator Iterator. * * @return Exe, or NULL if iteration is complete. Free with g_free(). */ char* credential_iterator_exe (iterator_t *iterator) { const char *login, *password; void *exe; gsize exe_size; gchar *exe64; if (iterator->done) return NULL; login = credential_iterator_login (iterator); password = credential_iterator_password (iterator); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_EXE) == FALSE) return NULL; else if (lsc_user_exe_recreate (login, password, &exe, &exe_size)) { g_warning ("%s: Failed to create EXE", __func__); return NULL; } exe64 = (exe && exe_size) ? g_base64_encode (exe, exe_size) : g_strdup (""); free (exe); return exe64; } /** * @brief Test if a credential format is available for an iterator. * * @param[in] iterator Iterator. * @param[in] format The format to test availability of. * * @return Whether format is available for the current credential of iterator. */ gboolean credential_iterator_format_available (iterator_t* iterator, credential_format_t format) { const char *type, *login, *private_key; type = credential_iterator_type (iterator); login = credential_iterator_login (iterator); private_key = credential_iterator_private_key (iterator); switch (format) { case CREDENTIAL_FORMAT_NONE: return TRUE; case CREDENTIAL_FORMAT_KEY: case CREDENTIAL_FORMAT_RPM: case CREDENTIAL_FORMAT_DEB: if (strcasecmp (type, "usk") == 0 && private_key) return validate_credential_username_for_format (login, format); else return FALSE; case CREDENTIAL_FORMAT_EXE: if (strcasecmp (type, "up") == 0) return validate_credential_username_for_format (login, format); else return FALSE; case CREDENTIAL_FORMAT_PEM: if (strcasecmp (type, "cc") == 0) return validate_credential_username_for_format (login, format); else return FALSE; case CREDENTIAL_FORMAT_ERROR: return FALSE; } return FALSE; } /** * @brief Get XML of available formats for a credential iterator. * * @param[in] iterator Iterator. * * @return Newly allocated XML string. */ gchar * credential_iterator_formats_xml (iterator_t* iterator) { GString *xml; xml = g_string_new (""); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_KEY)) g_string_append (xml, "key"); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_RPM)) g_string_append (xml, "rpm"); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_DEB)) g_string_append (xml, "deb"); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_EXE)) g_string_append (xml, "exe"); if (credential_iterator_format_available (iterator, CREDENTIAL_FORMAT_PEM)) g_string_append (xml, "pem"); g_string_append (xml, ""); return g_string_free (xml, FALSE); } /** * @brief Get the UUID of a Credential. * * @param[in] credential Credential. * * @return UUID. */ char* credential_uuid (credential_t credential) { return sql_string ("SELECT uuid FROM credentials WHERE id = %llu;", credential); } /** * @brief Get the UUID of a Credential in the trashcan. * * @param[in] credential Credential. * * @return UUID. */ char* trash_credential_uuid (credential_t credential) { return sql_string ("SELECT uuid FROM credentials_trash" " WHERE id = %llu;", credential); } /** * @brief Get the name of an LSC credential. * * @param[in] credential Credential. * * @return Name. */ char* credential_name (credential_t credential) { return sql_string ("SELECT name FROM credentials WHERE id = %llu;", credential); } /** * @brief Get the name of an LSC credential in the trashcan. * * @param[in] credential Credential. * * @return Name. */ char* trash_credential_name (credential_t credential) { return sql_string ("SELECT name FROM credentials_trash" " WHERE id = %llu;", credential); } /** * @brief Get the type of a Credential. * * @param[in] credential Credential. * * @return Credential type. */ char* credential_type (credential_t credential) { return sql_string ("SELECT type FROM credentials WHERE id = %llu;", credential); } /** * @brief Return whether a trashcan credential is readable. * * @param[in] credential Credential. * * @return 1 if readable, else 0. */ int trash_credential_readable (credential_t credential) { char *uuid; credential_t found = 0; if (credential == 0) return 0; uuid = credential_uuid (credential); if (find_trash ("credential", uuid, &found)) { g_free (uuid); return 0; } g_free (uuid); return found > 0; } /** * @brief Initialise a Credential target iterator. * * Iterates over all targets that use the credential. * * @param[in] iterator Iterator. * @param[in] credential Name of credential. * @param[in] ascending Whether to sort ascending or descending. */ void init_credential_target_iterator (iterator_t* iterator, credential_t credential, int ascending) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (credential); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_targets")); available = acl_where_owned ("target", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT uuid, name, %s FROM targets" " WHERE id IN" " (SELECT target FROM targets_login_data" " WHERE credential = %llu)" " ORDER BY name %s;", with_clause ? with_clause : "", available, credential, ascending ? "ASC" : "DESC"); g_free (with_clause); g_free (available); } /** * @brief Get the uuid from an Credential Target iterator. * * @param[in] iterator Iterator. * * @return Uuid, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_target_iterator_uuid, 0); /** * @brief Get the name from an Credential Target iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_target_iterator_name, 1); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int credential_target_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 2); } /** * @brief Initialise a Credential scanner iterator. * * Iterates over all scanners that use the credential. * * @param[in] iterator Iterator. * @param[in] credential Name of credential. * @param[in] ascending Whether to sort ascending or descending. */ void init_credential_scanner_iterator (iterator_t* iterator, credential_t credential, int ascending) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (credential); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_scanners")); available = acl_where_owned ("scanner", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT uuid, name, %s FROM scanners" " WHERE credential = %llu" " ORDER BY name %s;", with_clause ? with_clause : "", available, credential, ascending ? "ASC" : "DESC"); g_free (with_clause); g_free (available); } /** * @brief Get the uuid from an Credential Scanner iterator. * * @param[in] iterator Iterator. * * @return Uuid, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_scanner_iterator_uuid, 0); /** * @brief Get the name from an Credential Scanner iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (credential_scanner_iterator_name, 1); /** * @brief Get the read permission status from a Credential Scanner iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int credential_scanner_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 2); } /* Notes. */ /** * @brief Find a note for a specific permission, given a UUID. * * @param[in] uuid UUID of note. * @param[out] note Note return, 0 if successfully failed to find note. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find note), TRUE on error. */ gboolean find_note_with_permission (const char* uuid, note_t* note, const char *permission) { return find_resource_with_permission ("note", uuid, note, permission, 0); } /** * @brief Check if an NVT exists. * * @param[in] nvt NVT OID. * * @return 1 if exists, else 0. */ static gboolean nvt_exists (const char* nvt) { gchar *quoted_nvt; quoted_nvt = sql_quote (nvt); if (g_str_has_prefix (nvt, "CVE-")) { if (sql_int ("SELECT count (*) FROM cves WHERE uuid = '%s'", quoted_nvt) != 0) { g_free (quoted_nvt); return 1; } } else if (strcmp (nvt, "0") && (sql_int ("SELECT count (*) FROM nvts WHERE oid = '%s'", quoted_nvt) != 0)) { g_free (quoted_nvt); return 1; } g_free (quoted_nvt); return 0; } /** * @brief Create a note. * * @param[in] active NULL or -1 on, 0 off, n on for n days. * @param[in] nvt OID of noted NVT. * @param[in] text Note text. * @param[in] hosts Hosts to apply note to, NULL for any host. * @param[in] port Port to apply note to, NULL for any port. * @param[in] severity Severity to apply note to, "" or NULL for any. * @param[in] threat Threat to apply note to, "" or NULL for any threat. * Only used if severity is "" or NULL. * @param[in] task Task to apply note to, 0 for any task. * @param[in] result Result to apply note to, 0 for any result. * @param[out] note Created note. * * @return 0 success, 1 failed to find NVT, 2 invalid port, 99 permission * denied, -1 error. */ int create_note (const char* active, const char* nvt, const char* text, const char* hosts, const char* port, const char* severity, const char* threat, task_t task, result_t result, note_t *note) { gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity; double severity_dbl; if (acl_user_may ("create_note") == 0) return 99; if (nvt == NULL) return -1; if (!nvt_exists (nvt)) return 1; if (port && validate_results_port (port)) return 2; if (text == NULL) return -1; if (threat && strcmp (threat, "High") && strcmp (threat, "Medium") && strcmp (threat, "Low") && strcmp (threat, "Log") && strcmp (threat, "")) return -1; quoted_text = sql_insert (text); quoted_hosts = sql_insert (hosts); quoted_port = sql_insert (port); severity_dbl = 0.0; if (severity != NULL && strcmp (severity, "")) { if (sscanf (severity, "%lf", &severity_dbl) != 1 || ((severity_dbl < 0.0 || severity_dbl > 10.0) && severity_dbl != SEVERITY_LOG)) return 3; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else if (threat != NULL && strcmp (threat, "")) { if (strcmp (threat, "Alarm") == 0) severity_dbl = 0.1; else if (strcmp (threat, "High") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Medium") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Low") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Log") == 0) severity_dbl = SEVERITY_LOG; else return -1; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else quoted_severity = g_strdup ("NULL"); sql ("INSERT INTO notes" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, task, result, end_time)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', %i, %i, %s, %s, %s, %s, %llu, %llu, %i);", current_credentials.uuid, nvt, time (NULL), time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, task, result, (active == NULL || (strcmp (active, "-1") == 0)) ? 0 : (strcmp (active, "0") ? (time (NULL) + (atoi (active) * 60 * 60 * 24)) : 1)); g_free (quoted_text); g_free (quoted_hosts); g_free (quoted_port); g_free (quoted_severity); if (note) *note = sql_last_insert_id (); return 0; } /** * @brief Create a note from an existing note. * * @param[in] note_id UUID of existing note. * @param[out] new_note New note. * * @return 0 success, 1 note exists already, 2 failed to find existing * note, -1 error. */ int copy_note (const char *note_id, note_t* new_note) { return copy_resource ("note", NULL, NULL, note_id, "nvt, text, hosts, port, severity, task, result," "end_time", 1, new_note, NULL); } /** * @brief Delete a note. * * @param[in] note_id UUID of note. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 2 failed to find note, 99 permission denied, -1 error. */ int delete_note (const char *note_id, int ultimate) { note_t note = 0; sql_begin_immediate (); if (acl_user_may ("delete_note") == 0) { sql_rollback (); return 99; } if (find_note_with_permission (note_id, ¬e, "delete_note")) { sql_rollback (); return -1; } if (note == 0) { if (find_trash ("note", note_id, ¬e)) { sql_rollback (); return -1; } if (note == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } permissions_set_orphans ("note", note, LOCATION_TRASH); tags_remove_resource ("note", note, LOCATION_TRASH); sql ("DELETE FROM notes_trash WHERE id = %llu;", note); sql_commit (); return 0; } if (ultimate == 0) { sql ("INSERT INTO notes_trash" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, task, result, end_time)" " SELECT uuid, owner, nvt, creation_time, modification_time, text," " hosts, port, severity, task, result, end_time" " FROM notes WHERE id = %llu;", note); permissions_set_locations ("note", note, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("note", note, sql_last_insert_id (), LOCATION_TRASH); } else { permissions_set_orphans ("note", note, LOCATION_TABLE); tags_remove_resource ("note", note, LOCATION_TABLE); } sql ("DELETE FROM notes WHERE id = %llu;", note); sql_commit (); return 0; } /** * @brief Return the UUID of a note. * * @param[in] note Note. * @param[out] id Pointer to a newly allocated string. * * @return 0. */ int note_uuid (note_t note, char ** id) { *id = sql_string ("SELECT uuid FROM notes WHERE id = %llu;", note); return 0; } /** * @brief Modify a note. * * @param[in] note_id Note. * @param[in] active NULL or -2 leave as is, -1 on, 0 off, n on for n * days. * @param[in] nvt OID of noted NVT. * @param[in] text Note text. * @param[in] hosts Hosts to apply note to, NULL for any host. * @param[in] port Port to apply note to, NULL for any port. * @param[in] severity Severity to apply note to, "" or NULL for any. * @param[in] threat Threat to apply note to, "" or NULL for any threat. * Only used if severity is "" or NULL. * @param[in] task_id Task to apply note to, NULL for any task. * @param[in] result_id Result to apply note to, 0 for any result. * * @return 0 success, -1 error, 1 syntax error in active, 2 invalid port, * 3 invalid severity, 4 failed to find NVT, 5 failed to find note, * 6 failed to find task, 7 failed to find result. */ int modify_note (const gchar *note_id, const char *active, const char *nvt, const char *text, const char *hosts, const char *port, const char *severity, const char *threat, const gchar *task_id, const gchar *result_id) { gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity; double severity_dbl; gchar *quoted_nvt; note_t note; task_t task; result_t result; note = 0; if (find_note_with_permission (note_id, ¬e, "modify_note")) return -1; else if (note == 0) return 5; task = 0; if (task_id) { if (find_task_with_permission (task_id, &task, NULL)) return -1; if (task == 0) { if (find_trash_task_with_permission (task_id, &task, NULL)) return -1; if (task == 0) return 6; } } result = 0; if (result_id) { if (find_result_with_permission (result_id, &result, NULL)) return -1; if (result == 0) return 7; } if (text == NULL) return -1; if (nvt && !nvt_exists (nvt)) return 4; if (threat && strcmp (threat, "High") && strcmp (threat, "Medium") && strcmp (threat, "Low") && strcmp (threat, "Log") && strcmp (threat, "Alarm") && strcmp (threat, "")) return -1; if (port && validate_results_port (port)) return 2; quoted_text = sql_insert (text); quoted_hosts = sql_insert (hosts); quoted_port = sql_insert (port); quoted_nvt = sql_insert (nvt); severity_dbl = 0.0; if (severity != NULL && strcmp (severity, "")) { if (sscanf (severity, "%lf", &severity_dbl) != 1 || ((severity_dbl < 0.0 || severity_dbl > 10.0) && severity_dbl != SEVERITY_LOG)) return 3; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else if (threat != NULL && strcmp (threat, "")) { if (strcmp (threat, "Alarm") == 0) severity_dbl = 0.1; else if (strcmp (threat, "High") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Medium") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Low") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Log") == 0) severity_dbl = SEVERITY_LOG; else return -1; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else quoted_severity = g_strdup ("NULL"); if ((active == NULL) || (strcmp (active, "-2") == 0)) sql ("UPDATE notes SET" " modification_time = %i," " text = %s," " hosts = %s," " port = %s," " severity = %s," " %s%s%s" " task = %llu," " result = %llu" " WHERE id = %llu;", time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, nvt ? "nvt = " : "", nvt ? quoted_nvt : "", nvt ? "," : "", task, result, note); else { const char *point; point = active; if (strcmp (point, "-1")) { while (*point && isdigit (*point)) point++; if (*point) return 1; } sql ("UPDATE notes SET" " end_time = %i," " modification_time = %i," " text = %s," " hosts = %s," " port = %s," " severity = %s," "%s%s%s" " task = %llu," " result = %llu" " WHERE id = %llu;", (strcmp (active, "-1") ? (strcmp (active, "0") ? (time (NULL) + atoi (active) * 60 * 60 * 24) : 1) : 0), time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, nvt ? "nvt = " : "", nvt ? quoted_nvt : "", nvt ? "," : "", task, result, note); } g_free (quoted_text); g_free (quoted_hosts); g_free (quoted_port); g_free (quoted_severity); g_free (quoted_nvt); return 0; } /** * @brief Filter columns for note iterator. */ #define NOTE_ITERATOR_FILTER_COLUMNS \ { ANON_GET_ITERATOR_FILTER_COLUMNS, "name", "nvt", "text", "nvt_id", \ "task_name", "task_id", "hosts", "port", "active", "result", "severity", \ "end_time", "active_days", NULL } /** * @brief Note iterator columns. */ #define NOTE_ITERATOR_COLUMNS \ { \ { "notes.id", "id", KEYWORD_TYPE_INTEGER }, \ { "notes.uuid", "uuid", KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN notes.nvt LIKE 'CVE-%%'" \ " THEN notes.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = notes.nvt)" \ " END)", \ "name", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (notes.creation_time)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (notes.modification_time)", NULL, KEYWORD_TYPE_STRING }, \ { "notes.creation_time", "created", KEYWORD_TYPE_INTEGER }, \ { "notes.modification_time", "modified", KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM users WHERE users.id = notes.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "owner", NULL, KEYWORD_TYPE_INTEGER }, \ /* Columns specific to notes. */ \ { "notes.nvt", "oid", KEYWORD_TYPE_STRING }, \ { "notes.text", "text", KEYWORD_TYPE_STRING }, \ { "notes.hosts", "hosts", KEYWORD_TYPE_STRING }, \ { "notes.port", "port", KEYWORD_TYPE_STRING }, \ { "notes.task", NULL, KEYWORD_TYPE_INTEGER }, \ { "notes.result", "result", KEYWORD_TYPE_INTEGER }, \ { "notes.end_time", "end_time", KEYWORD_TYPE_INTEGER }, \ { "CAST (((notes.end_time = 0) OR (notes.end_time >= m_now ()))" \ " AS INTEGER)", \ "active", \ KEYWORD_TYPE_INTEGER }, \ { \ "(CASE" \ " WHEN notes.nvt LIKE 'CVE-%%'" \ " THEN notes.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = notes.nvt)" \ " END)", \ "nvt", \ KEYWORD_TYPE_STRING \ }, \ { "notes.nvt", "nvt_id", KEYWORD_TYPE_STRING }, \ { "(SELECT uuid FROM tasks WHERE id = notes.task)", \ "task_id", \ KEYWORD_TYPE_STRING }, \ { "(SELECT name FROM tasks WHERE id = notes.task)", \ "task_name", \ KEYWORD_TYPE_STRING }, \ { "notes.severity", "severity", KEYWORD_TYPE_DOUBLE }, \ { "(SELECT name FROM users WHERE users.id = notes.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "days_from_now (notes.end_time)", \ "active_days", \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Note iterator columns for trash case. */ #define NOTE_ITERATOR_TRASH_COLUMNS \ { \ { "notes_trash.id", "id", KEYWORD_TYPE_INTEGER }, \ { "notes_trash.uuid", "uuid", KEYWORD_TYPE_STRING }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (notes_trash.creation_time)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (notes_trash.modification_time)", NULL, KEYWORD_TYPE_STRING }, \ { "notes_trash.creation_time", "created", KEYWORD_TYPE_INTEGER }, \ { "notes_trash.modification_time", "modified", KEYWORD_TYPE_INTEGER }, \ { "(SELECT name FROM users WHERE users.id = notes_trash.owner)", \ "_owner", \ KEYWORD_TYPE_STRING }, \ { "owner", NULL, KEYWORD_TYPE_INTEGER }, \ /* Columns specific to notes_trash. */ \ { "notes_trash.nvt", "oid", KEYWORD_TYPE_STRING }, \ { "notes_trash.text", "text", KEYWORD_TYPE_STRING }, \ { "notes_trash.hosts", "hosts", KEYWORD_TYPE_STRING }, \ { "notes_trash.port", "port", KEYWORD_TYPE_STRING }, \ { "severity_to_level (notes_trash.severity, 1)", \ "threat", \ KEYWORD_TYPE_STRING }, \ { "notes_trash.task", NULL, KEYWORD_TYPE_INTEGER }, \ { "notes_trash.result", "result", KEYWORD_TYPE_INTEGER }, \ { "notes_trash.end_time", NULL, KEYWORD_TYPE_INTEGER }, \ { "CAST (((notes_trash.end_time = 0) OR (notes_trash.end_time >= m_now ()))" \ " AS INTEGER)", \ "active", \ KEYWORD_TYPE_INTEGER }, \ { \ "(CASE" \ " WHEN notes_trash.nvt LIKE 'CVE-%%'" \ " THEN notes_trash.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = notes_trash.nvt)" \ " END)", \ "nvt", \ KEYWORD_TYPE_STRING \ }, \ { "notes_trash.nvt", "nvt_id", KEYWORD_TYPE_STRING }, \ { "(SELECT uuid FROM tasks WHERE id = notes_trash.task)", \ "task_id", \ KEYWORD_TYPE_STRING }, \ { "(SELECT name FROM tasks WHERE id = notes_trash.task)", \ "task_name", \ KEYWORD_TYPE_STRING }, \ { "notes_trash.severity", "severity", KEYWORD_TYPE_DOUBLE }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of notes. * * @param[in] get GET params. * @param[in] result Result to limit notes to, 0 for all. * @param[in] task If result is > 0, task whose notes on result to * include, otherwise task to limit notes to. 0 for * all tasks. * @param[in] nvt NVT to limit notes to, 0 for all. * * @return Total number of notes in filtered set. */ int note_count (const get_data_t *get, nvt_t nvt, result_t result, task_t task) { static const char *filter_columns[] = NOTE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = NOTE_ITERATOR_COLUMNS; static column_t trash_columns[] = NOTE_ITERATOR_TRASH_COLUMNS; gchar *result_clause, *filter, *task_id; int ret; /* Treat the "task_id" filter keyword as if the task was given in "task". */ if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; task_id = filter_term_value (filter ? filter : get->filter, "task_id"); g_free (filter); if (task_id) { find_task_with_permission (task_id, &task, "get_tasks"); g_free (task_id); } if (result) { gchar *severity_sql; if (setting_dynamic_severity_int ()) severity_sql = g_strdup_printf ("(SELECT CASE" " WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base AS real)" " ELSE results.severity END" " FROM results, nvts" " WHERE (nvts.oid = results.nvt)" " AND (results.id = %llu))", result); else severity_sql = g_strdup_printf ("(SELECT results.severity" " FROM results" " WHERE results.id = %llu)", result); result_clause = g_strdup_printf (" AND" " (result = %llu" " OR (result = 0 AND nvt =" " (SELECT results.nvt FROM results" " WHERE results.id = %llu)))" " AND (hosts is NULL" " OR hosts = ''" " OR hosts_contains (hosts," " (SELECT results.host FROM results" " WHERE results.id = %llu)))" " AND (port is NULL" " OR port = ''" " OR port =" " (SELECT results.port FROM results" " WHERE results.id = %llu))" " AND (severity_matches_ov (%s," " severity))" " AND (task = 0 OR task = %llu)", result, result, result, result, severity_sql, task); g_free (severity_sql); } else if (task) { result_clause = g_strdup_printf (" AND (notes.task = %llu OR notes.task = 0)" " AND nvt IN" " (SELECT DISTINCT nvt FROM results" " WHERE results.task = %llu)" " AND (notes.result = 0" " OR (SELECT task FROM results" " WHERE results.id = notes.result)" " = %llu)", task, task, task); } else if (nvt) { result_clause = g_strdup_printf (" AND (notes.nvt = (SELECT oid FROM nvts" " WHERE nvts.id = %llu))", nvt); } else result_clause = NULL; ret = count ("note", get, columns, trash_columns, filter_columns, task || nvt, NULL, result_clause, TRUE); g_free (result_clause); return ret; } /** * @brief Initialise a note iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * @param[in] result Result to limit notes to, 0 for all. * @param[in] task If result is > 0, task whose notes on result to * include, otherwise task to limit notes to. 0 for * all tasks. * @param[in] nvt NVT to limit notes to, 0 for all. * * @return 0 success, 1 failed to find target, 2 failed to find filter, * -1 error. */ int init_note_iterator (iterator_t* iterator, const get_data_t *get, nvt_t nvt, result_t result, task_t task) { static const char *filter_columns[] = NOTE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = NOTE_ITERATOR_COLUMNS; static column_t trash_columns[] = NOTE_ITERATOR_TRASH_COLUMNS; gchar *result_clause, *filter, *task_id; int ret; assert (current_credentials.uuid); assert ((nvt && get->id) == 0); assert ((task && get->id) == 0); assert (result ? nvt == 0 : 1); assert (task ? nvt == 0 : 1); /* Treat the "task_id" filter keyword as if the task was given in "task". */ if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; task_id = filter_term_value (filter ? filter : get->filter, "task_id"); g_free (filter); if (task_id) { find_task_with_permission (task_id, &task, "get_tasks"); g_free (task_id); } if (result) { gchar *severity_sql; if (setting_dynamic_severity_int ()) severity_sql = g_strdup_printf ("(SELECT CASE" " WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base AS real)" " ELSE results.severity END" " FROM results, nvts" " WHERE (nvts.oid = results.nvt)" " AND (results.id = %llu))", result); else severity_sql = g_strdup_printf ("(SELECT results.severity" " FROM results" " WHERE results.id = %llu)", result); result_clause = g_strdup_printf (" AND" " (result = %llu" " OR (result = 0 AND nvt =" " (SELECT results.nvt FROM results" " WHERE results.id = %llu)))" " AND (hosts is NULL" " OR hosts = ''" " OR hosts_contains (hosts," " (SELECT results.host FROM results" " WHERE results.id = %llu)))" " AND (port is NULL" " OR port = ''" " OR port =" " (SELECT results.port FROM results" " WHERE results.id = %llu))" " AND (severity_matches_ov (%s," " severity))" " AND (task = 0 OR task = %llu)", result, result, result, result, severity_sql, task); g_free (severity_sql); } else if (task) { result_clause = g_strdup_printf (" AND (notes.task = %llu OR notes.task = 0)" " AND nvt IN (SELECT DISTINCT nvt FROM results" " WHERE results.task = %llu)" " AND (notes.result = 0" " OR (SELECT task FROM results" " WHERE results.id = notes.result)" " = %llu)", task, task, task); } else if (nvt) { result_clause = g_strdup_printf (" AND (notes.nvt = (SELECT oid FROM nvts" " WHERE nvts.id = %llu))", nvt); } else result_clause = NULL; ret = init_get_iterator (iterator, "note", get, columns, trash_columns, filter_columns, task || nvt, NULL, result_clause, TRUE); g_free (result_clause); return ret; } /** * @brief Get the NVT OID from a note iterator. * * @param[in] iterator Iterator. * * @return NVT OID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (note_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the text from a note iterator. * * @param[in] iterator Iterator. * * @return Text, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (note_iterator_text, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the hosts from a note iterator. * * @param[in] iterator Iterator. * * @return Hosts, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (note_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the port from a note iterator. * * @param[in] iterator Iterator. * * @return Port, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (note_iterator_port, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the task from a note iterator. * * @param[in] iterator Iterator. * * @return The task associated with the note, or 0 on error. */ task_t note_iterator_task (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the result from a note iterator. * * @param[in] iterator Iterator. * * @return The result associated with the note, or 0 on error. */ result_t note_iterator_result (iterator_t* iterator) { if (iterator->done) return 0; return (result_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5); } /** * @brief Get the end time from an note iterator. * * @param[in] iterator Iterator. * * @return Time until which note applies. 0 for always. 1 means the * note has been explicitly turned off. */ time_t note_iterator_end_time (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6); return ret; } /** * @brief Get the active status from an note iterator. * * @param[in] iterator Iterator. * * @return 1 if active, else 0. */ int note_iterator_active (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 7); return ret; } /** * @brief Get the NVT name from a note iterator. * * @param[in] iterator Iterator. * * @return NVT name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (note_iterator_nvt_name, GET_ITERATOR_COLUMN_COUNT + 8); /** * @brief Get the NVT type from a note iterator. * * @param[in] iterator Iterator. * * @return NVT type, or NULL. Static string. */ const char * note_iterator_nvt_type (iterator_t *iterator) { const char *oid; oid = note_iterator_nvt_oid (iterator); if (oid == NULL) return NULL; if (g_str_has_prefix (oid, "CVE-")) return "cve"; if (g_str_has_prefix (oid, "oval:")) return "ovaldef"; return "nvt"; } /** * @brief Get the severity from a note iterator. * * @param[in] iterator Iterator. * * @return The severity to apply the note to, or NULL if iteration is complete. * Freed by cleanup_iterator. */ DEF_ACCESS (note_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 12); /* Overrides. */ /** * @brief Find a override for a specific permission, given a UUID. * * @param[in] uuid UUID of override. * @param[out] override Override return, 0 if successfully failed to find * override. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find override), TRUE on * error. */ gboolean find_override_with_permission (const char* uuid, override_t* override, const char *permission) { return find_resource_with_permission ("override", uuid, override, permission, 0); } /** * @brief Create an override. * * @param[in] active NULL or -1 on, 0 off, n on for n days. * @param[in] nvt OID of overridden NVT. * @param[in] text Override text. * @param[in] hosts Hosts to apply override to, NULL for any host. * @param[in] port Port to apply override to, NULL for any port. * @param[in] threat Threat to apply override to, "" or NULL for any threat. * @param[in] new_threat Threat to override result to. * @param[in] severity Severity to apply override to, "" or NULL for any. * @param[in] new_severity Severity score to override "Alarm" type results to. * @param[in] task Task to apply override to, 0 for any task. * @param[in] result Result to apply override to, 0 for any result. * @param[out] override Created override. * * @return 0 success, 1 failed to find NVT, 2 invalid port, 3 invalid severity, * 99 permission denied, -1 error. */ int create_override (const char* active, const char* nvt, const char* text, const char* hosts, const char* port, const char* threat, const char* new_threat, const char* severity, const char* new_severity, task_t task, result_t result, override_t* override) { gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity; double severity_dbl, new_severity_dbl; GHashTable *reports; GHashTableIter reports_iter; report_t *reports_ptr; gchar *override_id, *users_where; int auto_cache_rebuild; override_t new_override; if (acl_user_may ("create_override") == 0) return 99; if (nvt == NULL) return -1; if (text == NULL) return -1; if (!nvt_exists (nvt)) return 1; if (port && validate_results_port (port)) return 2; if (threat && strcmp (threat, "High") && strcmp (threat, "Medium") && strcmp (threat, "Low") && strcmp (threat, "Log") && strcmp (threat, "Alarm") && strcmp (threat, "")) return -1; if (new_threat && strcmp (new_threat, "High") && strcmp (new_threat, "Medium") && strcmp (new_threat, "Low") && strcmp (new_threat, "Log") && strcmp (new_threat, "False Positive") && strcmp (new_threat, "Alarm") && strcmp (new_threat, "")) return -1; severity_dbl = 0.0; if (severity != NULL && strcmp (severity, "")) { if (sscanf (severity, "%lf", &severity_dbl) != 1 || ((severity_dbl < 0.0 || severity_dbl > 10.0) && severity_dbl != SEVERITY_LOG)) return 3; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else if (threat != NULL && strcmp (threat, "")) { if (strcmp (threat, "Alarm") == 0) severity_dbl = 0.1; else if (strcmp (threat, "High") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Medium") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Low") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Log") == 0) severity_dbl = SEVERITY_LOG; else return -1; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else quoted_severity = g_strdup ("NULL"); new_severity_dbl = 0.0; if (new_severity != NULL && strcmp (new_severity, "")) { if (sscanf (new_severity, "%lf", &new_severity_dbl) != 1 || ((new_severity_dbl < 0.0 || new_severity_dbl > 10.0) && new_severity_dbl != SEVERITY_LOG && new_severity_dbl != SEVERITY_FP)) { g_free (quoted_severity); return 2; } } else if (new_threat != NULL && strcmp (new_threat, "")) { if (strcmp (new_threat, "Alarm") == 0) new_severity_dbl = 10.0; else if (strcmp (new_threat, "High") == 0) new_severity_dbl = 10.0; else if (strcmp (new_threat, "Medium") == 0) new_severity_dbl = 5.0; else if (strcmp (new_threat, "Low") == 0) new_severity_dbl = 2.0; else if (strcmp (new_threat, "Log") == 0) new_severity_dbl = SEVERITY_LOG; else return -1; } else { g_free (quoted_severity); return -1; } quoted_text = sql_insert (text); quoted_hosts = sql_insert (hosts); quoted_port = sql_insert (port); result_nvt_notice (nvt); sql ("INSERT INTO overrides" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, new_severity, task, result, end_time," " result_nvt)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', %i, %i, %s, %s, %s, %s, %1.1f, %llu, %llu, %i," " (SELECT id FROM result_nvts WHERE nvt = '%s'));", current_credentials.uuid, nvt, time (NULL), time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, new_severity_dbl, task, result, (active == NULL || (strcmp (active, "-1") == 0)) ? 0 : (strcmp (active, "0") ? (time (NULL) + (atoi (active) * 60 * 60 * 24)) : 1), nvt); g_free (quoted_text); g_free (quoted_hosts); g_free (quoted_port); g_free (quoted_severity); if (override) *override = sql_last_insert_id (); new_override = sql_last_insert_id (); override_uuid (new_override, &override_id); users_where = acl_users_with_access_where ("override", override_id, NULL, "id"); reports = reports_for_override (new_override); reports_ptr = NULL; g_hash_table_iter_init (&reports_iter, reports); auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, 0, 1, users_where); else report_clear_count_cache (*reports_ptr, 0, 1, users_where); } g_hash_table_destroy (reports); g_free (override_id); g_free (users_where); return 0; } /** * @brief Return the UUID of an override. * * @param[in] override Override. * @param[out] id Pointer to a newly allocated string. * * @return 0. */ int override_uuid (override_t override, char ** id) { *id = sql_string ("SELECT uuid FROM overrides WHERE id = %llu;", override); return 0; } /** * @brief Create a override from an existing override. * * @param[in] override_id UUID of existing override. * @param[out] new_override New override. * * @return 0 success, 1 override exists already, 2 failed to find existing * override, -1 error. */ int copy_override (const char *override_id, override_t* new_override) { return copy_resource ("override", NULL, NULL, override_id, "nvt, text, hosts, port, severity, new_severity, task," " result, end_time, result_nvt", 1, new_override, NULL); } /** * @brief Delete a override. * * @param[in] override_id UUID of override. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 2 failed to find override, 99 permission denied, -1 error. */ int delete_override (const char *override_id, int ultimate) { override_t override; GHashTable *reports; GHashTableIter reports_iter; report_t *reports_ptr; gchar *users_where; int auto_cache_rebuild; sql_begin_immediate (); if (acl_user_may ("delete_override") == 0) { sql_rollback (); return 99; } override = 0; if (find_override_with_permission (override_id, &override, "delete_override")) { sql_rollback (); return -1; } if (override == 0) { if (find_trash ("override", override_id, &override)) { sql_rollback (); return -1; } if (override == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } permissions_set_orphans ("override", override, LOCATION_TRASH); tags_remove_resource ("override", override, LOCATION_TRASH); sql ("DELETE FROM overrides_trash WHERE id = %llu;", override); sql_commit (); return 0; } reports = reports_for_override (override); users_where = acl_users_with_access_where ("override", override_id, NULL, "id"); if (ultimate == 0) { sql ("INSERT INTO overrides_trash" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, new_severity, task, result, end_time, result_nvt)" " SELECT uuid, owner, nvt, creation_time, modification_time, text," " hosts, port, severity, new_severity,task," " result, end_time, result_nvt" " FROM overrides WHERE id = %llu;", override); permissions_set_locations ("override", override, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("override", override, sql_last_insert_id (), LOCATION_TRASH); } else { permissions_set_orphans ("override", override, LOCATION_TABLE); tags_remove_resource ("override", override, LOCATION_TABLE); } sql ("DELETE FROM overrides WHERE id = %llu;", override); g_hash_table_iter_init (&reports_iter, reports); reports_ptr = NULL; auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, 0, 1, users_where); else report_clear_count_cache (*reports_ptr, 0, 1, users_where); } g_hash_table_destroy (reports); g_free (users_where); sql_commit (); return 0; } /** * @brief Modify an override. * * @param[in] override_id Override. * @param[in] active NULL or -2 leave as is, -1 on, 0 off, n on for n * days. * @param[in] nvt OID of noted NVT. * @param[in] text Override text. * @param[in] hosts Hosts to apply override to, NULL for any host. * @param[in] port Port to apply override to, NULL for any port. * @param[in] threat Threat to apply override to, "" or NULL for any threat. * @param[in] new_threat Threat to override result to. * @param[in] severity Severity to apply override to, "" or NULL for any threat. * @param[in] new_severity Severity score to override "Alarm" type results to. * @param[in] task_id Task to apply override to, 0 for any task. * @param[in] result_id Result to apply override to, 0 for any result. * * @return 0 success, -1 error, 1 syntax error in active, 2 invalid port, * 3 invalid severity score, 4 failed to find NVT, 5 failed to find * override, 6 failed to find task, 7 failed to find result, * 8 invalid threat, 9 invalid new_threat, 10 invalid new_severity, * 11 missing new_severity. */ int modify_override (const gchar *override_id, const char *active, const char *nvt, const char *text, const char *hosts, const char *port, const char *threat, const char *new_threat, const char *severity, const char *new_severity, const gchar *task_id, const gchar *result_id) { gchar *quoted_text, *quoted_hosts, *quoted_port, *quoted_severity; double severity_dbl, new_severity_dbl; gchar *quoted_nvt; GHashTable *reports; GString *cache_invalidated_sql; int cache_invalidated; override_t override; task_t task; result_t result; reports = NULL; cache_invalidated = 0; override = 0; if (find_override_with_permission (override_id, &override, "modify_override")) return -1; if (override == 0) return 5; task = 0; if (task_id) { if (find_task_with_permission (task_id, &task, NULL)) return -1; if (task == 0) { if (find_trash_task_with_permission (task_id, &task, NULL)) return -1; if (task == 0) return 6; } } result = 0; if (result_id) { if (find_result_with_permission (result_id, &result, NULL)) return -1; if (result == 0) return 7; } if (text == NULL) return -1; if (port && validate_results_port (port)) return 2; if (nvt && !nvt_exists (nvt)) return 4; severity_dbl = 0.0; if (severity != NULL && strcmp (severity, "")) { if (sscanf (severity, "%lf", &severity_dbl) != 1 || ((severity_dbl < 0.0 || severity_dbl > 10.0) && severity_dbl != SEVERITY_LOG)) return 3; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else if (threat != NULL && strcmp (threat, "")) { if (strcmp (threat, "Alarm") == 0) severity_dbl = 0.1; else if (strcmp (threat, "High") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Medium") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Low") == 0) severity_dbl = 0.1; else if (strcmp (threat, "Log") == 0) severity_dbl = SEVERITY_LOG; else return 8; quoted_severity = g_strdup_printf ("'%1.1f'", severity_dbl); } else quoted_severity = g_strdup ("NULL"); new_severity_dbl = 0.0; if (new_severity != NULL && strcmp (new_severity, "")) { if (sscanf (new_severity, "%lf", &new_severity_dbl) != 1 || ((new_severity_dbl < 0.0 || new_severity_dbl > 10.0) && new_severity_dbl != SEVERITY_LOG && new_severity_dbl != SEVERITY_FP)) { g_free (quoted_severity); return 10; } } else if (new_threat != NULL && strcmp (new_threat, "")) { if (strcmp (new_threat, "Alarm") == 0) new_severity_dbl = 10.0; else if (strcmp (new_threat, "High") == 0) new_severity_dbl = 10.0; else if (strcmp (new_threat, "Medium") == 0) new_severity_dbl = 5.0; else if (strcmp (new_threat, "Low") == 0) new_severity_dbl = 2.0; else if (strcmp (new_threat, "Log") == 0) new_severity_dbl = SEVERITY_LOG; else { g_free (quoted_severity); return 9; } } else { g_free (quoted_severity); return 11; } quoted_text = sql_insert (text); quoted_hosts = sql_insert (hosts); quoted_port = sql_insert (port); quoted_nvt = nvt ? sql_quote (nvt) : NULL; // Tests if a cache rebuild is necessary. // The "active" status is checked separately cache_invalidated_sql = g_string_new (""); g_string_append_printf (cache_invalidated_sql, "SELECT (cast (new_severity AS numeric) != %1.1f)", new_severity_dbl); g_string_append_printf (cache_invalidated_sql, " OR (task != %llu)", task); g_string_append_printf (cache_invalidated_sql, " OR (result != %llu)", result); if (strcmp (quoted_severity, "NULL") == 0) g_string_append_printf (cache_invalidated_sql, " OR (severity IS NOT NULL)"); else g_string_append_printf (cache_invalidated_sql, " OR (cast (severity AS numeric) != %1.1f)", severity_dbl); if (strcmp (quoted_hosts, "NULL") == 0) g_string_append_printf (cache_invalidated_sql, " OR (hosts IS NOT NULL)"); else g_string_append_printf (cache_invalidated_sql, " OR (hosts != %s)", quoted_hosts); if (strcmp (quoted_port, "NULL") == 0) g_string_append_printf (cache_invalidated_sql, " OR (hosts IS NOT NULL)"); else g_string_append_printf (cache_invalidated_sql, " OR (port != %s)", quoted_port); g_string_append_printf (cache_invalidated_sql, " FROM overrides WHERE id = %llu", override); if (sql_int ("%s", cache_invalidated_sql->str)) { cache_invalidated = 1; } g_string_free (cache_invalidated_sql, TRUE); // Check active status for changes, get old reports for rebuild if necessary // and update override. result_nvt_notice (quoted_nvt); if ((active == NULL) || (strcmp (active, "-2") == 0)) { if (cache_invalidated) reports = reports_for_override (override); sql ("UPDATE overrides SET" " modification_time = %i," " text = %s," " hosts = %s," " port = %s," " severity = %s," " %s%s%s" " %s%s%s" " new_severity = %f," " task = %llu," " result = %llu" " WHERE id = %llu;", time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, nvt ? "nvt = '" : "", nvt ? quoted_nvt : "", nvt ? "'," : "", nvt ? "result_nvt = (SELECT id FROM result_nvts WHERE nvt='" : "", nvt ? quoted_nvt : "", nvt ? "')," : "", new_severity_dbl, task, result, override); } else { const char *point; point = active; int new_end_time; if (strcmp (point, "-1")) { while (*point && isdigit (*point)) point++; if (*point) { return 1; } } new_end_time = (strcmp (active, "-1") ? (strcmp (active, "0") ? (time (NULL) + atoi (active) * 60 * 60 * 24) : 1) : 0); if (cache_invalidated == 0 && sql_int ("SELECT end_time != %d FROM overrides" " WHERE id = %llu", new_end_time, override)) cache_invalidated = 1; if (cache_invalidated) reports = reports_for_override (override); sql ("UPDATE overrides SET" " end_time = %i," " modification_time = %i," " text = %s," " hosts = %s," " port = %s," " severity = %s," " %s%s%s" " %s%s%s" " new_severity = %f," " task = %llu," " result = %llu" " WHERE id = %llu;", new_end_time, time (NULL), quoted_text, quoted_hosts, quoted_port, quoted_severity, nvt ? "nvt = '" : "", nvt ? quoted_nvt : "", nvt ? "'," : "", nvt ? "result_nvt = (SELECT id FROM result_nvts WHERE nvt='" : "", nvt ? quoted_nvt : "", nvt ? "')," : "", new_severity_dbl, task, result, override); } g_free (quoted_text); g_free (quoted_hosts); g_free (quoted_port); g_free (quoted_severity); g_free (quoted_nvt); if (cache_invalidated) { GHashTableIter reports_iter; report_t *reports_ptr; gchar *users_where; int auto_cache_rebuild; users_where = acl_users_with_access_where ("override", override_id, NULL, "id"); reports_add_for_override (reports, override); g_hash_table_iter_init (&reports_iter, reports); reports_ptr = NULL; auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, 0, 1, users_where); else report_clear_count_cache (*reports_ptr, 0, 1, users_where); } g_free (users_where); } if (reports) g_hash_table_destroy (reports); return 0; } /** * @brief Filter columns for override iterator. */ #define OVERRIDE_ITERATOR_FILTER_COLUMNS \ { ANON_GET_ITERATOR_FILTER_COLUMNS, "name", "nvt", "text", "nvt_id", \ "task_name", "task_id", "hosts", "port", "threat", "new_threat", "active", \ "result", "severity", "new_severity", "active_days", NULL } /** * @brief Override iterator columns. */ #define OVERRIDE_ITERATOR_COLUMNS \ { \ { "overrides.id", "id", KEYWORD_TYPE_INTEGER }, \ { "overrides.uuid", "uuid", KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN overrides.nvt LIKE 'CVE-%%'" \ " THEN overrides.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = overrides.nvt)" \ " END)", \ "name", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (overrides.creation_time)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (overrides.modification_time)", NULL, KEYWORD_TYPE_STRING }, \ { "overrides.creation_time", "created", KEYWORD_TYPE_INTEGER }, \ { "overrides.modification_time", "modified", KEYWORD_TYPE_INTEGER }, \ { \ "(SELECT name FROM users WHERE users.id = overrides.owner)", \ "_owner", \ KEYWORD_TYPE_STRING \ }, \ { "owner", NULL, KEYWORD_TYPE_INTEGER }, \ /* Columns specific to overrides. */ \ { "overrides.nvt", "oid", KEYWORD_TYPE_STRING }, \ { "overrides.text", "text", KEYWORD_TYPE_STRING }, \ { "overrides.hosts", "hosts", KEYWORD_TYPE_STRING }, \ { "overrides.port", "port", KEYWORD_TYPE_STRING }, \ { "severity_to_level (overrides.severity, 1)", \ "threat", \ KEYWORD_TYPE_STRING }, \ { "severity_to_level (overrides.new_severity, 0)", \ "new_threat", \ KEYWORD_TYPE_STRING }, \ { "overrides.task", NULL, KEYWORD_TYPE_STRING }, \ { "overrides.result", "result", KEYWORD_TYPE_INTEGER }, \ { "overrides.end_time", NULL, KEYWORD_TYPE_INTEGER }, \ { \ "CAST (((overrides.end_time = 0) OR (overrides.end_time >= m_now ()))" \ " AS INTEGER)", \ "active", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(CASE" \ " WHEN overrides.nvt LIKE 'CVE-%%'" \ " THEN overrides.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = overrides.nvt)" \ " END)", \ "name", \ KEYWORD_TYPE_STRING \ }, \ { "overrides.nvt", "nvt_id", KEYWORD_TYPE_STRING }, \ { "(SELECT uuid FROM tasks WHERE id = overrides.task)", \ "task_id", \ KEYWORD_TYPE_STRING }, \ { "(SELECT name FROM tasks WHERE id = overrides.task)", \ "task_name", \ KEYWORD_TYPE_STRING }, \ { "overrides.severity", "severity", KEYWORD_TYPE_DOUBLE }, \ { "overrides.new_severity", "new_severity", KEYWORD_TYPE_DOUBLE }, \ { \ "(SELECT name FROM users WHERE users.id = overrides.owner)", \ "_owner", \ KEYWORD_TYPE_STRING \ }, \ { "days_from_now (overrides.end_time)", \ "active_days", \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Override iterator columns for trash case. */ #define OVERRIDE_ITERATOR_TRASH_COLUMNS \ { \ { "overrides_trash.id", "id", KEYWORD_TYPE_INTEGER }, \ { "overrides_trash.uuid", "uuid", KEYWORD_TYPE_STRING }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "CAST ('' AS TEXT)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (overrides_trash.creation_time)", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "iso_time (overrides_trash.modification_time)", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "overrides_trash.creation_time", \ "created", \ KEYWORD_TYPE_INTEGER }, \ { "overrides_trash.modification_time", \ "modified", \ KEYWORD_TYPE_INTEGER }, \ { \ "(SELECT name FROM users WHERE users.id = overrides_trash.owner)", \ "_owner", \ KEYWORD_TYPE_STRING \ }, \ { "owner", NULL, KEYWORD_TYPE_STRING }, \ /* Columns specific to overrides_trash. */ \ { "overrides_trash.nvt", "oid", KEYWORD_TYPE_STRING }, \ { "overrides_trash.text", "text", KEYWORD_TYPE_STRING }, \ { "overrides_trash.hosts", "hosts", KEYWORD_TYPE_STRING }, \ { "overrides_trash.port", "port", KEYWORD_TYPE_STRING }, \ { "severity_to_level (overrides_trash.severity, 1)", \ "threat", \ KEYWORD_TYPE_STRING }, \ { "severity_to_level (overrides_trash.new_severity, 0)", \ "new_threat", \ KEYWORD_TYPE_STRING }, \ { "overrides_trash.task", NULL, KEYWORD_TYPE_INTEGER }, \ { "overrides_trash.result", "result", KEYWORD_TYPE_INTEGER }, \ { "overrides_trash.end_time", NULL, KEYWORD_TYPE_INTEGER }, \ { \ "CAST (((overrides_trash.end_time = 0)" \ " OR (overrides_trash.end_time >= m_now ())) AS INTEGER)", \ "active", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(CASE" \ " WHEN overrides_trash.nvt LIKE 'CVE-%%'" \ " THEN overrides_trash.nvt" \ " ELSE (SELECT name FROM nvts WHERE oid = overrides_trash.nvt)" \ " END)", \ "nvt", \ KEYWORD_TYPE_STRING \ }, \ { "overrides_trash.nvt", "nvt_id", KEYWORD_TYPE_STRING }, \ { \ "(SELECT uuid FROM tasks WHERE id = overrides_trash.task)", \ "task_id", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT name FROM tasks WHERE id = overrides_trash.task)", \ "task_name", \ KEYWORD_TYPE_STRING \ }, \ { "overrides_trash.severity", NULL, KEYWORD_TYPE_DOUBLE }, \ { "overrides_trash.new_severity", NULL, KEYWORD_TYPE_DOUBLE }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of overrides. * * @param[in] get GET params. * @param[in] result Result to limit overrides to, 0 for all. * @param[in] task If result is > 0, task whose overrides on result to * include, otherwise task to limit overrides to. 0 for * all tasks. * @param[in] nvt NVT to limit overrides to, 0 for all. * * @return Total number of overrides in filtered set. */ int override_count (const get_data_t *get, nvt_t nvt, result_t result, task_t task) { static const char *filter_columns[] = OVERRIDE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = OVERRIDE_ITERATOR_COLUMNS; static column_t trash_columns[] = OVERRIDE_ITERATOR_TRASH_COLUMNS; gchar *result_clause, *filter, *task_id; int ret; /* Treat the "task_id" filter keyword as if the task was given in "task". */ if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; task_id = filter_term_value (filter ? filter : get->filter, "task_id"); g_free (filter); if (task_id) { find_task_with_permission (task_id, &task, "get_tasks"); g_free (task_id); } if (result) { gchar *severity_sql; if (setting_dynamic_severity_int ()) severity_sql = g_strdup_printf ("(SELECT CASE" " WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base AS real)" " ELSE results.severity END" " FROM results, nvts" " WHERE (nvts.oid = results.nvt)" " AND (results.id = %llu))", result); else severity_sql = g_strdup_printf ("(SELECT results.severity" " FROM results" " WHERE results.id = %llu)", result); result_clause = g_strdup_printf (" AND" " (result = %llu" " OR (result = 0 AND nvt =" " (SELECT results.nvt FROM results" " WHERE results.id = %llu)))" " AND (hosts is NULL" " OR hosts = ''" " OR hosts_contains (hosts," " (SELECT results.host FROM results" " WHERE results.id = %llu)))" " AND (port is NULL" " OR port = ''" " OR port =" " (SELECT results.port FROM results" " WHERE results.id = %llu))" " AND (severity_matches_ov (%s," " severity))" " AND (task = 0 OR task = %llu)", result, result, result, result, severity_sql, task); g_free (severity_sql); } else if (task) { result_clause = g_strdup_printf (" AND (overrides.task = %llu OR overrides.task = 0)" " AND nvt IN" " (SELECT DISTINCT nvt FROM results" " WHERE results.task = %llu)" " AND (overrides.result = 0" " OR (SELECT task FROM results" " WHERE results.id = overrides.result)" " = %llu)", task, task, task); } else if (nvt) { result_clause = g_strdup_printf (" AND (overrides.nvt" " = (SELECT oid FROM nvts WHERE nvts.id = %llu))", nvt); } else result_clause = NULL; ret = count ("override", get, columns, trash_columns, filter_columns, task || nvt, NULL, result_clause, TRUE); g_free (result_clause); return ret; } /** * @brief Initialise an override iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * @param[in] result Result to limit overrides to, 0 for all. * @param[in] task If result is > 0, task whose overrides on result to * include, otherwise task to limit overrides to. 0 for * all tasks. * @param[in] nvt NVT to limit overrides to, 0 for all. * * @return 0 success, 1 failed to find target, 2 failed to find filter, * -1 error. */ int init_override_iterator (iterator_t* iterator, const get_data_t *get, nvt_t nvt, result_t result, task_t task) { static const char *filter_columns[] = OVERRIDE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = OVERRIDE_ITERATOR_COLUMNS; static column_t trash_columns[] = OVERRIDE_ITERATOR_TRASH_COLUMNS; gchar *result_clause, *filter, *task_id; int ret; assert (current_credentials.uuid); assert ((nvt && get->id) == 0); assert ((task && get->id) == 0); assert (result ? nvt == 0 : 1); assert (task ? nvt == 0 : 1); /* Treat the "task_id" filter keyword as if the task was given in "task". */ if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; task_id = filter_term_value (filter ? filter : get->filter, "task_id"); g_free (filter); if (task_id) { find_task_with_permission (task_id, &task, "get_tasks"); g_free (task_id); } if (result) { gchar *severity_sql; if (setting_dynamic_severity_int ()) severity_sql = g_strdup_printf ("(SELECT CASE" " WHEN results.severity" " > " G_STRINGIFY (SEVERITY_LOG) " THEN CAST (nvts.cvss_base AS real)" " ELSE results.severity END" " FROM results, nvts" " WHERE (nvts.oid = results.nvt)" " AND (results.id = %llu))", result); else severity_sql = g_strdup_printf ("(SELECT results.severity" " FROM results" " WHERE results.id = %llu)", result); result_clause = g_strdup_printf (" AND" " (result = %llu" " OR (result = 0 AND nvt =" " (SELECT results.nvt FROM results" " WHERE results.id = %llu)))" " AND (hosts is NULL" " OR hosts = ''" " OR hosts_contains (hosts," " (SELECT results.host FROM results" " WHERE results.id = %llu)))" " AND (port is NULL" " OR port = ''" " OR port =" " (SELECT results.port FROM results" " WHERE results.id = %llu))" " AND (severity_matches_ov (%s," " severity))" " AND (task = 0 OR task = %llu)", result, result, result, result, severity_sql, task); g_free (severity_sql); } else if (task) { result_clause = g_strdup_printf (" AND (overrides.task = %llu OR overrides.task = 0)" " AND nvt IN" " (SELECT DISTINCT nvt FROM results" " WHERE results.task = %llu)" " AND (overrides.result = 0" " OR (SELECT task FROM results" " WHERE results.id = overrides.result)" " = %llu)", task, task, task); } else if (nvt) { result_clause = g_strdup_printf (" AND (overrides.nvt = (SELECT oid FROM nvts" " WHERE nvts.id = %llu))", nvt); } else result_clause = NULL; ret = init_get_iterator (iterator, "override", get, columns, trash_columns, filter_columns, task || nvt, NULL, result_clause, TRUE); g_free (result_clause); return ret; } /** * @brief Get the NVT OID from a override iterator. * * @param[in] iterator Iterator. * * @return NVT OID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (override_iterator_nvt_oid, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the text from a override iterator. * * @param[in] iterator Iterator. * * @return Text, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (override_iterator_text, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the hosts from a override iterator. * * @param[in] iterator Iterator. * * @return Hosts, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (override_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the port from a override iterator. * * @param[in] iterator Iterator. * * @return Port, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (override_iterator_port, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the threat from a override iterator. * * @param[in] iterator Iterator. * * @return Threat. */ const char * override_iterator_threat (iterator_t *iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4); return ret; } /** * @brief Get the threat from an override iterator. * * @param[in] iterator Iterator. * * @return Threat. */ const char * override_iterator_new_threat (iterator_t *iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 5); return ret; } /** * @brief Get the task from a override iterator. * * @param[in] iterator Iterator. * * @return The task associated with the override, or 0 on error. */ task_t override_iterator_task (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Get the result from a override iterator. * * @param[in] iterator Iterator. * * @return The result associated with the override, or 0 on error. */ result_t override_iterator_result (iterator_t* iterator) { if (iterator->done) return 0; return (result_t) iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 7); } /** * @brief Get the end time from an override iterator. * * @param[in] iterator Iterator. * * @return Time until which override applies. 0 for always. 1 means the * override has been explicitly turned off. */ time_t override_iterator_end_time (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = (time_t) iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 8); return ret; } /** * @brief Get the active status from an override iterator. * * @param[in] iterator Iterator. * * @return 1 if active, else 0. */ int override_iterator_active (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 9); return ret; } /** * @brief Get the NVT name from a override iterator. * * @param[in] iterator Iterator. * * @return NVT name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (override_iterator_nvt_name, GET_ITERATOR_COLUMN_COUNT + 10); /** * @brief Get the NVT type from a override iterator. * * @param[in] iterator Iterator. * * @return NVT type, or NULL. Static string. */ const char * override_iterator_nvt_type (iterator_t *iterator) { const char *oid; oid = override_iterator_nvt_oid (iterator); if (oid == NULL) return NULL; if (g_str_has_prefix (oid, "CVE-")) return "cve"; if (g_str_has_prefix (oid, "oval:")) return "ovaldef"; return "nvt"; } /** * @brief Get the severity from an override iterator. * * @param[in] iterator Iterator. * * @return The severity score to which the override applies or NULL if * iteration is complete, Freed by cleanup_iterator. */ DEF_ACCESS (override_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 14); /** * @brief Get the new severity from an override iterator. * * @param[in] iterator Iterator. * * @return The severity score to override to or NULL if * iteration is complete, Freed by cleanup_iterator. */ DEF_ACCESS (override_iterator_new_severity, GET_ITERATOR_COLUMN_COUNT + 15); /* Scanners */ /** * @brief Create the given scanner. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of scanner. * @param[in] host Host of scanner. * @param[in] port Port of scanner. * @param[in] type Type of scanner. * @param[in] ca_pub_path CA Certificate path. * @param[in] credential_id UUID of credential to use or NULL to create. * @param[in] key_pub_path Certificate path. * @param[in] key_priv_path Private key path. * * @return 0 success, -1 error, -2 database is wrong version, -3 database needs * to be initialised from server. */ int manage_create_scanner (GSList *log_config, const db_conn_info_t *database, const char *name, const char *host, const char *port, const char *type, const char *ca_pub_path, const char *credential_id, const char *key_pub_path, const char *key_priv_path) { int ret; char *ca_pub, *key_pub, *key_priv; GError *error = NULL; credential_t new_credential; gchar *used_credential_id; gchar *name_for_credential; scanner_t scanner; g_info (" Creating scanner."); ret = manage_option_setup (log_config, database); if (ret) return ret; current_credentials.uuid = ""; if (!g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error)) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); manage_option_cleanup (); return -1; } if (credential_id) { key_pub = NULL; key_priv = NULL; used_credential_id = g_strdup (credential_id); } else { if (!g_file_get_contents (key_pub_path, &key_pub, NULL, &error)) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); g_free (ca_pub); manage_option_cleanup (); return -1; } if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error)) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); g_free (ca_pub); g_free (key_pub); manage_option_cleanup (); return -1; } name_for_credential = sql_quote (name); if (sql_int ("SELECT count(*) FROM credentials" " WHERE name = 'Credential for Scanner %s'" " AND owner = NULL;", name_for_credential)) sql ("INSERT INTO credentials" " (uuid, name, owner, comment, type," " creation_time, modification_time)" " VALUES" " (make_uuid ()," " uniquify ('scanner', 'Credential for Scanner %s'," " NULL, '')," " NULL, 'Autogenerated', 'cc'," " m_now (), m_now ());", name_for_credential); else sql ("INSERT INTO credentials" " (uuid, name, owner, comment, type," " creation_time, modification_time)" " VALUES" " (make_uuid (), 'Credential for Scanner %s'," " NULL, 'Autogenerated', 'cc'," " m_now (), m_now ());", name_for_credential); g_free (name_for_credential); new_credential = sql_last_insert_id(); set_credential_data (new_credential, "certificate", key_pub); if (disable_encrypted_credentials) { set_credential_data (new_credential, "private_key", key_priv); } else { lsc_crypt_ctx_t crypt_ctx; char *secret; crypt_ctx = lsc_crypt_new (); secret = lsc_crypt_encrypt (crypt_ctx, "private_key", key_priv, NULL); if (!secret) { fprintf (stderr, "Failed to encrypt private key.\n"); g_free (ca_pub); g_free (key_pub); g_free (key_priv); manage_option_cleanup (); return -1; } set_credential_data (new_credential, "secret", secret); } used_credential_id = credential_uuid (new_credential); } ret = create_scanner (name, NULL, host, port, type, &scanner, ca_pub, used_credential_id); g_free (ca_pub); g_free (key_pub); g_free (key_priv); g_free (used_credential_id); switch (ret) { case 0: { gchar *uuid; uuid = sql_string ("SELECT uuid FROM scanners WHERE id = %llu;", scanner); add_role_permission_resource (ROLE_UUID_ADMIN, "GET_SCANNERS", "scanner", uuid); add_role_permission_resource (ROLE_UUID_GUEST, "GET_SCANNERS", "scanner", uuid); add_role_permission_resource (ROLE_UUID_OBSERVER, "GET_SCANNERS", "scanner", uuid); add_role_permission_resource (ROLE_UUID_USER, "GET_SCANNERS", "scanner", uuid); g_free (uuid); } printf ("Scanner created.\n"); break; case 1: fprintf (stderr, "Scanner exists already.\n"); break; case 2: fprintf (stderr, "Invalid value provided.\n"); break; case 3: fprintf (stderr, "Credential not found.\n"); break; case 4: fprintf (stderr, "Credential should be 'up'.\n"); break; case 5: fprintf (stderr, "Credential should be 'cc'.\n"); break; case 6: fprintf (stderr, "Credential required.\n"); break; default: fprintf (stderr, "Failed to create scanner.\n"); break; } manage_option_cleanup (); return ret; } /** * @brief Delete the given scanner. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] uuid UUID of scanner. * * @return 0 success, 2 failed to find scanner, 3 scanner can't be deleted, * -1 error. -2 database is wrong version, -3 database needs to be * initialised from server. */ int manage_delete_scanner (GSList *log_config, const db_conn_info_t *database, const gchar *uuid) { int ret; assert (uuid); g_info (" Deleting scanner."); if (!strcmp (uuid, SCANNER_UUID_CVE)) { fprintf (stderr, "Default CVE Scanner can't be deleted.\n"); return 3; } if (!strcmp (uuid, SCANNER_UUID_DEFAULT)) { fprintf (stderr, "Default OpenVAS Scanner can't be deleted.\n"); return 3; } ret = manage_option_setup (log_config, database); if (ret) return ret; current_credentials.uuid = ""; switch ((ret = delete_scanner (uuid, 1))) { case 0: printf ("Scanner deleted.\n"); break; case 1: fprintf (stderr, "Scanner in use.\n"); break; case 2: fprintf (stderr, "Failed to find scanner.\n"); break; case 3: fprintf (stderr, "Scanner is predefined.\n"); break; default: fprintf (stderr, "Internal Error.\n"); break; } current_credentials.uuid = NULL; manage_option_cleanup (); return ret; } /** * @brief Modify the given scanner. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] scanner_id ID of scanner. * @param[in] name Name of scanner. * @param[in] host Host of scanner. * @param[in] port Port of scanner. * @param[in] type Type of scanner. * @param[in] ca_pub_path CA Certificate path. NULL to leave it as is. * "" to use the default. * @param[in] credential_id UUID of credential to use or NULL to create. * @param[in] key_pub_path Certificate path. * @param[in] key_priv_path Private key path. * * @return 0 success, , 1 failed to find scanner, 2 scanner with new name * exists, 3 scanner_id required, 4 invalid value, 99 permission * denied, -1 error, -2 database is wrong version, -3 database needs * to be initialised from server. */ int manage_modify_scanner (GSList *log_config, const db_conn_info_t *database, const char *scanner_id, const char *name, const char *host, const char *port, const char *type, const char *ca_pub_path, const char *credential_id, const char *key_pub_path, const char *key_priv_path) { int ret; char *ca_pub, *key_pub, *key_priv; GError *error = NULL; scanner_t scanner; credential_t new_credential; gchar *used_credential_id; gchar *name_for_credential; g_info (" Modifying scanner."); ret = manage_option_setup (log_config, database); if (ret) return ret; current_credentials.uuid = ""; if (scanner_id) { /* Because the credentials are empty this will find the scanner regardless * of permissions and ownership, but that's the intention. */ if (find_scanner_with_permission (scanner_id, &scanner, "get_scanners")) { fprintf (stderr, "Error finding scanner.\n"); manage_option_cleanup (); return -1; } if (scanner == 0) { fprintf (stderr, "Failed to find scanner %s.\n", scanner_id); manage_option_cleanup (); return 1; } } else { fprintf (stderr, "Scanner UUID required.\n"); manage_option_cleanup (); return 3; } if (name) name_for_credential = sql_quote (name); else { gchar *current_scanner_name = scanner_name (scanner); name_for_credential = sql_quote (current_scanner_name); g_free (current_scanner_name); } if (ca_pub_path) { if (*ca_pub_path == '\0') ca_pub = g_strdup (""); else if (g_file_get_contents (ca_pub_path, &ca_pub, NULL, &error) == 0) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); manage_option_cleanup (); return -1; } } else ca_pub = NULL; if (credential_id) { key_pub = NULL; key_priv = NULL; used_credential_id = g_strdup (credential_id); } else { if (key_pub_path) { if (g_file_get_contents (key_pub_path, &key_pub, NULL, &error) == 0) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); g_free (ca_pub); manage_option_cleanup (); return -1; } } else key_pub = scanner_key_pub (scanner); if (key_priv_path) { if (!g_file_get_contents (key_priv_path, &key_priv, NULL, &error)) { fprintf (stderr, "%s.\n", error->message); g_error_free (error); g_free (ca_pub); g_free (key_pub); manage_option_cleanup (); return -1; } } else key_priv = scanner_key_priv (scanner); if (key_priv || key_pub) { if (sql_int ("SELECT count(*) FROM credentials" " WHERE name = 'Credential for Scanner %s'" " AND owner IS NULL;", name_for_credential)) sql ("INSERT INTO credentials" " (uuid, name, owner, comment, type," " creation_time, modification_time)" " VALUES" " (make_uuid ()," " uniquify ('scanner', 'Credential for Scanner %s'," " NULL, '')," " NULL, 'Autogenerated', 'cc'," " m_now (), m_now ());", name_for_credential); else sql ("INSERT INTO credentials" " (uuid, name, owner, comment, type," " creation_time, modification_time)" " VALUES" " (make_uuid (), 'Credential for Scanner %s'," " NULL, 'Autogenerated', 'cc'," " m_now (), m_now ());", name_for_credential); g_free (name_for_credential); new_credential = sql_last_insert_id(); set_credential_data (new_credential, "certificate", key_pub); if (disable_encrypted_credentials) { set_credential_data (new_credential, "private_key", key_priv); } else { lsc_crypt_ctx_t crypt_ctx; char *secret; crypt_ctx = lsc_crypt_new (); secret = lsc_crypt_encrypt (crypt_ctx, "private_key", key_priv, NULL); if (!secret) { fprintf (stderr, "Failed to encrypt private key.\n"); g_free (ca_pub); g_free (key_pub); g_free (key_priv); manage_option_cleanup (); return -1; } set_credential_data (new_credential, "secret", secret); } used_credential_id = credential_uuid (new_credential); } else used_credential_id = NULL; } ret = modify_scanner (scanner_id, name, NULL, host, port, type, ca_pub, used_credential_id); g_free (ca_pub); g_free (key_pub); g_free (key_priv); g_free (used_credential_id); switch (ret) { case 0: printf ("Scanner modified.\n"); break; case 2: fprintf (stderr, "Scanner with new name exists already.\n"); break; case 3: fprintf (stderr, "Scanner ID required.\n"); break; case 4: fprintf (stderr, "Invalid value.\n"); break; case 5: fprintf (stderr, "Credential not found.\n"); break; case 6: fprintf (stderr, "Credential should be 'cc'.\n"); break; case 7: fprintf (stderr, "Credential should be 'up'.\n"); break; case 8: fprintf (stderr, "Credential missing.\n"); break; case 99: fprintf (stderr, "Permission denied.\n"); break; default: fprintf (stderr, "Failed to modify scanner.\n"); break; } manage_option_cleanup (); return ret; } /** * @brief Verify the given scanner. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] uuid UUID of scanner. * * @return 0 success, 1 failed to find scanner, 2 failed to verify scanner, * -1 error. -2 database is wrong version, -3 database needs to be * initialised from server. */ int manage_verify_scanner (GSList *log_config, const db_conn_info_t *database, const gchar *uuid) { int ret; char *version; assert (uuid); g_info (" Verifying scanner."); ret = manage_option_setup (log_config, database); if (ret) return ret; current_credentials.uuid = ""; switch ((ret = verify_scanner (uuid, &version))) { case 0: printf ("Scanner version: %s.\n", version); g_free (version); break; case 1: fprintf (stderr, "Failed to find scanner.\n"); break; case 2: fprintf (stderr, "Failed to verify scanner.\n"); break; case 3: fprintf (stderr, "Failed to authenticate. Scanner version: %s\n", version); break; default: fprintf (stderr, "Internal Error.\n"); break; } current_credentials.uuid = NULL; manage_option_cleanup (); return ret; } /** * @brief Find a scanner for a specific permission, given a UUID. * * @param[in] uuid UUID of scanner. * @param[out] scanner Scanner return, 0 if successfully failed to find * scanner. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find scanner), * TRUE on error. */ gboolean find_scanner_with_permission (const char* uuid, scanner_t* scanner, const char *permission) { return find_resource_with_permission ("scanner", uuid, scanner, permission, 0); } /** * @brief Insert a scanner for create_scanner. * * @param[in] name Name of scanner. * @param[in] comment Comment on scanner. * @param[in] host Host of scanner. * @param[in] ca_pub CA Certificate for scanner. * @param[in] iport Port of scanner. * @param[in] itype Type of scanner. * @param[out] new_scanner The created scanner. */ static void insert_scanner (const char* name, const char *comment, const char *host, const char *ca_pub, int iport, int itype, scanner_t *new_scanner) { char *quoted_name, *quoted_comment, *quoted_host, *quoted_ca_pub; assert (current_credentials.uuid); quoted_name = sql_quote (name ?: ""); quoted_comment = sql_quote (comment ?: ""); quoted_host = sql_quote (host ?: ""); quoted_ca_pub = sql_quote (ca_pub ?: ""); sql ("INSERT INTO scanners (uuid, name, owner, comment, host, port, type," " ca_pub,creation_time, modification_time)" " VALUES (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '%s', %d, %d, %s%s%s, m_now (), m_now ());", quoted_name, current_credentials.uuid, quoted_comment, quoted_host, iport, itype, ca_pub ? "'" : "", ca_pub ? quoted_ca_pub : "NULL", ca_pub ? "'" : ""); g_free (quoted_host); g_free (quoted_comment); g_free (quoted_name); g_free (quoted_ca_pub); if (new_scanner) *new_scanner = sql_last_insert_id (); } /** * @brief Create a scanner. * * @param[in] name Name of scanner. * @param[in] comment Comment on scanner. * @param[in] host Host of scanner. * @param[in] port Port of scanner. * @param[in] type Type of scanner. * @param[out] new_scanner The created scanner. * @param[in] ca_pub CA Certificate for scanner. * @param[in] credential_id ID of credential for scanner. * * @return 0 success, 1 scanner exists already, 2 Invalid value, * 3 credential not found, 4 credential should be 'up', * 5 credential should be 'cc', 6 credential required, * 99 permission denied. */ int create_scanner (const char* name, const char *comment, const char *host, const char *port, const char *type, scanner_t *new_scanner, const char *ca_pub, const char *credential_id) { int iport, itype, unix_socket = 0; credential_t credential; assert (name); sql_begin_immediate (); if (acl_user_may ("create_scanner") == 0) { sql_rollback (); return 99; } if (!host || !port || !type) return 2; if (*host == '/') { unix_socket = 1; ca_pub = NULL; } iport = atoi (port); itype = atoi (type); if (iport <= 0 || iport > 65535) return 2; if (scanner_type_valid (itype) == 0) return 2; /* XXX: Workaround for unix socket case. */ if (gvm_get_host_type (host) == -1 && !unix_socket) return 2; if (resource_with_name_exists (name, "scanner", 0)) { sql_rollback (); return 1; } if (unix_socket) insert_scanner (name, comment, host, ca_pub, iport, itype, new_scanner); else { credential = 0; if (credential_id && strcmp (credential_id, "") && strcmp (credential_id, "0")) { if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { sql_rollback (); return -1; } if (credential == 0) { sql_rollback (); return 3; } if (sql_int ("SELECT type != 'cc' FROM credentials" " WHERE id = %llu;", credential)) { sql_rollback (); return 5; } } insert_scanner (name, comment, host, ca_pub, iport, itype, new_scanner); if (credential) { sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;", credential, sql_last_insert_id ()); } } sql_commit (); return 0; } /** * @brief Create a scanner from an existing scanner. * * @param[in] name Name of new scanner. NULL to copy from existing. * @param[in] comment Comment on new scanner. NULL to copy from * existing. * @param[in] scanner_id UUID of existing scanner. * @param[out] new_scanner New scanner. * * @return 0 success, 1 scanner exists already, 2 failed to find existing * scanner, -1 error, 98 not allowed to copy cve scanner, * 99 permission denied. */ int copy_scanner (const char* name, const char* comment, const char *scanner_id, scanner_t* new_scanner) { if (strcmp (scanner_id, SCANNER_UUID_CVE) == 0) return 98; return copy_resource ("scanner", name, comment, scanner_id, "host, port, type, ca_pub, credential", 1, new_scanner, NULL); } /** * @brief Modify an scanner. * * @param[in] scanner_id UUID of scanner. * @param[in] name Name of scanner. * @param[in] comment Comment on scanner. * @param[in] host Host of scanner. * @param[in] port Port of scanner. * @param[in] type Type of scanner. * @param[in] ca_pub CA Certificate of scanner, or "" for default, or * to keep existing value. * @param[in] credential_id UUID of credential or NULL. * * @return 0 success, 1 failed to find scanner, 2 scanner with new name exists, * 3 scanner_id required, 4 invalid value, 5 credential not found, * 6 credential should be 'cc', 7 credential should be 'up', * 8 credential missing, 99 permission denied, -1 internal error. */ int modify_scanner (const char *scanner_id, const char *name, const char *comment, const char *host, const char *port, const char *type, const char *ca_pub, const char *credential_id) { gchar *quoted_name, *quoted_comment, *quoted_host, *new_port, *new_type; scanner_t scanner = 0; credential_t credential = 0; int iport, itype, unix_socket, credential_given; assert (current_credentials.uuid); if (scanner_id == NULL) return 3; if (port) { iport = atoi (port); if (iport <= 0 || iport > 65535) return 4; } else /* Keep compiler quiet. */ iport = 0; if (type) { itype = atoi (type); if (scanner_type_valid (itype) == 0) return 4; } else itype = 0; sql_begin_immediate (); if (acl_user_may ("modify_scanner") == 0) { sql_rollback (); return 99; } if (find_scanner_with_permission (scanner_id, &scanner, "modify_scanner")) { sql_rollback (); return -1; } if (scanner == 0) { sql_rollback (); return 1; } if (host) { unix_socket = (*host == '/'); if ((unix_socket == 0) && (gvm_get_host_type (host) == -1)) return 4; } else { char *old_host = scanner_host (scanner); unix_socket = (*old_host == '/'); g_free (old_host); } if (itype == 0) itype = sql_int ("SELECT type FROM scanners WHERE id = %llu;", scanner); if (credential_id && (strcmp (credential_id, "") == 0 || strcmp (credential_id, "0") == 0)) { credential = 0; credential_given = 1; } else if (credential_id && !unix_socket) { if (find_credential_with_permission (credential_id, &credential, "get_credentials")) { sql_rollback (); return -1; } if (credential == 0) { sql_rollback (); return 5; } credential_given = 1; } else { credential = 0; credential_given = 1; sql_int64 (&credential, "SELECT credential FROM scanners WHERE id = %llu;", scanner); } if (credential) { if (sql_int ("SELECT type != 'cc' FROM credentials WHERE id = %llu;", credential)) { sql_rollback (); return 6; } } /* Check whether a scanner with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "scanner", scanner)) { sql_rollback (); return 2; } } quoted_name = name ? sql_quote (name) : NULL; quoted_comment = sql_quote (comment ?: ""); quoted_host = host ? sql_quote (host) : NULL; new_port = port ? g_strdup_printf ("%d", iport) : g_strdup ("port"); new_type = type ? g_strdup_printf ("%d", itype) : g_strdup ("type"); sql ("UPDATE scanners SET name = %s%s%s, comment = %s%s%s, host = %s%s%s," " port = %s, type = %s, modification_time = m_now () WHERE id = %llu;", quoted_name ? "'" : "", quoted_name ? quoted_name : "name", quoted_name ? "'" : "", quoted_comment ? "'" : "", quoted_comment ? quoted_comment : "comment", quoted_comment ? "'" : "", quoted_host ? "'" : "", quoted_host ? quoted_host : "host", quoted_host ? "'" : "", new_port, new_type, scanner); g_free (new_type); g_free (new_port); g_free (quoted_host); g_free (quoted_comment); g_free (quoted_name); if (ca_pub && !unix_socket) { if (*ca_pub) { char *quoted_ca_pub = sql_quote (ca_pub); sql ("UPDATE scanners SET ca_pub = '%s' WHERE id = %llu;", quoted_ca_pub, scanner); g_free (quoted_ca_pub); } else /* Use default CA cert. */ sql ("UPDATE scanners SET ca_pub = NULL WHERE id = %llu;", scanner); } if (credential_given) { if (credential) sql ("UPDATE scanners SET credential = %llu WHERE id = %llu;", credential, scanner); else sql ("UPDATE scanners SET credential = NULL WHERE id = %llu;", scanner); } sql_commit (); return 0; } /** * @brief Delete a scanner. * * @param[in] scanner_id UUID of scanner. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 scanner in use, 2 failed to find scanner, * 3 predefined scanner, 99 permission denied, -1 error. */ int delete_scanner (const char *scanner_id, int ultimate) { scanner_t scanner = 0; sql_begin_immediate (); if (acl_user_may ("delete_scanner") == 0) { sql_rollback (); return 99; } if (strcmp (scanner_id, SCANNER_UUID_CVE) == 0 || strcmp (scanner_id, SCANNER_UUID_DEFAULT) == 0) return 3; if (find_scanner_with_permission (scanner_id, &scanner, "delete_scanner")) { sql_rollback (); return -1; } if (scanner == 0) { if (find_trash ("scanner", scanner_id, &scanner)) { sql_rollback (); return -1; } if (scanner == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by a config or task in the trashcan. */ if (sql_int ("SELECT count(*) FROM tasks" " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH) ";", scanner) || sql_int ("SELECT count(*) FROM configs_trash" " WHERE scanner = %llu" " AND scanner_location" " = " G_STRINGIFY (LOCATION_TRASH) ";", scanner)) { sql_rollback (); return 1; } permissions_set_orphans ("scanner", scanner, LOCATION_TRASH); tags_remove_resource ("scanner", scanner, LOCATION_TRASH); sql ("DELETE FROM scanners_trash WHERE id = %llu;", scanner); sql_commit (); return 0; } if (ultimate == 0) { scanner_t trash_scanner; if (sql_int ("SELECT count(*) FROM tasks" " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE) " AND hidden = 0;", scanner) || sql_int ("SELECT count(*) FROM configs" " WHERE scanner = %llu;", scanner)) { sql_rollback (); return 1; } sql ("INSERT INTO scanners_trash" " (uuid, owner, name, comment, host, port, type, ca_pub," " credential, credential_location," " creation_time, modification_time)" " SELECT uuid, owner, name, comment, host, port, type, ca_pub," " credential, " G_STRINGIFY (LOCATION_TABLE) "," " creation_time, modification_time" " FROM scanners WHERE id = %llu;", scanner); trash_scanner = sql_last_insert_id (); /* Update the location of the scanner in any trashcan configs & tasks. */ sql ("UPDATE configs_trash" " SET scanner = %llu," " scanner_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE scanner = %llu;", trash_scanner, scanner); sql ("UPDATE tasks" " SET scanner = %llu," " scanner_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TABLE) ";", trash_scanner, scanner); permissions_set_locations ("scanner", scanner, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("scanner", scanner, sql_last_insert_id (), LOCATION_TRASH); } else { permissions_set_orphans ("scanner", scanner, LOCATION_TABLE); tags_remove_resource ("scanner", scanner, LOCATION_TABLE); } sql ("DELETE FROM scanners WHERE id = %llu;", scanner); sql_commit (); return 0; } /** * @brief Filter columns for scanner iterator. */ #define SCANNER_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "host", "port", "type", NULL } /** * @brief Scanner iterator columns. */ #define SCANNER_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (scanners), \ { "host", NULL, KEYWORD_TYPE_STRING }, \ { "port", NULL, KEYWORD_TYPE_INTEGER }, \ { "type", NULL, KEYWORD_TYPE_INTEGER }, \ { "ca_pub", NULL, KEYWORD_TYPE_STRING }, \ { \ "(SELECT name FROM credentials WHERE id = credential)", \ "credential", \ KEYWORD_TYPE_STRING \ }, \ { "credential", NULL, KEYWORD_TYPE_INTEGER }, \ { "0", NULL, KEYWORD_TYPE_INTEGER }, \ { "credential_value (credential, 0, CAST ('certificate' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "credential_value (credential, 0, CAST ('private_key' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "credential_value (credential, 0, CAST ('secret' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { \ "(SELECT type FROM credentials WHERE id = credential)", \ "credential_type", \ KEYWORD_TYPE_STRING \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Scanner iterator columns for trash case. */ #define SCANNER_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (scanners_trash), \ { "host" , NULL, KEYWORD_TYPE_STRING }, \ { "port" , NULL, KEYWORD_TYPE_INTEGER }, \ { "type", NULL, KEYWORD_TYPE_INTEGER }, \ { "ca_pub", NULL, KEYWORD_TYPE_STRING }, \ { \ "(SELECT CASE" \ " WHEN credential_location = " G_STRINGIFY (LOCATION_TABLE) \ " THEN (SELECT name FROM credentials WHERE id = credential)" \ " ELSE (SELECT name FROM credentials_trash WHERE id = credential)" \ " END)", \ "credential", \ KEYWORD_TYPE_STRING \ }, \ { "credential", NULL, KEYWORD_TYPE_INTEGER }, \ { "credential_location", NULL, KEYWORD_TYPE_INTEGER }, \ { "credential_value (credential, 1, CAST ('certificate' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "credential_value (credential, 1, CAST ('private_key' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { "credential_value (credential, 1, CAST ('secret' AS TEXT))", \ NULL, \ KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Initialise an scanner iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find scanner, 2 failed to find filter, -1 error. */ int init_scanner_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = SCANNER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = SCANNER_ITERATOR_COLUMNS; static column_t trash_columns[] = SCANNER_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "scanner", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the host from an scanner iterator. * * @param[in] iterator Iterator. * * @return Host, or NULL if iteration is complete. Freed * by cleanup_iterator. */ DEF_ACCESS (scanner_iterator_host, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the port from an scanner iterator. * * @param[in] iterator Iterator. * * @return Port, or -1 if iteration is complete. */ int scanner_iterator_port (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); return ret; } /** * @brief Get the type from an scanner iterator. * * @param[in] iterator Iterator. * * @return Type, or SCANNER_TYPE_NONE if iteration is complete. */ int scanner_iterator_type (iterator_t* iterator) { int ret; if (iterator->done) return SCANNER_TYPE_NONE; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2); return ret; } /** * @brief Get the CA Certificate from a scanner iterator. * * @param[in] iterator Iterator. * * @return CA Certificate, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (scanner_iterator_ca_pub, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the Credential name from a scanner iterator. * * @param[in] iterator Iterator. * * @return Credential name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (scanner_iterator_credential_name, GET_ITERATOR_COLUMN_COUNT + 4); /** * @brief Get the credential of the scanner from a scanner iterator. * * @param[in] iterator Iterator. * * @return Credential of the scanner or 0 if iteration is complete. */ credential_t scanner_iterator_credential (iterator_t *iterator) { if (iterator->done) return 0; else return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5); } /** * @brief Get the credential location of the scanner from a scanner iterator. * * @param[in] iterator Iterator. * * @return Location of the credential or NULL if iteration is complete. */ int scanner_iterator_credential_trash (iterator_t *iterator) { if (iterator->done) return 0; else return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Get the Scanner Certificate from a scanner iterator. * * @param[in] iterator Iterator. * * @return Scanner Certificate, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (scanner_iterator_key_pub, GET_ITERATOR_COLUMN_COUNT + 7); /** * @brief Get the Scanner private key from a scanner iterator. * * @param[in] iterator Iterator. * * @return Scanner private key, or NULL if iteration is complete. Freed by * cleanup_iterator. */ static const char* scanner_iterator_key_priv (iterator_t* iterator) { const char *private_key; private_key = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 8); if (private_key == NULL) { const char *secret; if (!iterator->crypt_ctx) iterator->crypt_ctx = lsc_crypt_new (); secret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 9); private_key = lsc_crypt_get_private_key (iterator->crypt_ctx, secret); } return private_key; } /** * @brief Get the Credential type from a scanner iterator. * * @param[in] iterator Iterator. * * @return Credential type, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (scanner_iterator_credential_type, GET_ITERATOR_COLUMN_COUNT + 10); /** * @brief Initialise a scanner config iterator. * * @param[in] iterator Iterator. * @param[in] scanner Scanner. */ void init_scanner_config_iterator (iterator_t* iterator, scanner_t scanner) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (scanner); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_configs")); available = acl_where_owned ("config", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT id, uuid, name, %s FROM configs" " WHERE scanner = %llu" " ORDER BY name ASC;", with_clause ? with_clause : "", available, scanner); g_free (with_clause); g_free (available); } /** * @brief Get the UUID from a scanner config iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (scanner_config_iterator_uuid, 1); /** * @brief Get the name from a scanner config iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (scanner_config_iterator_name, 2); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int scanner_config_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 3); } /** * @brief Initialise a scanner task iterator. * * @param[in] iterator Iterator. * @param[in] scanner Scanner. */ void init_scanner_task_iterator (iterator_t* iterator, scanner_t scanner) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (scanner); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_tasks")); available = acl_where_owned ("task", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT id, uuid, name, %s FROM tasks" " WHERE scanner = %llu AND hidden = 0" " ORDER BY name ASC;", with_clause ? with_clause : "", available, scanner); g_free (with_clause); g_free (available); } /** * @brief Get the UUID from a scanner task iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (scanner_task_iterator_uuid, 1); /** * @brief Get the name from a scanner task iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (scanner_task_iterator_name, 2); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int scanner_task_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 3); } /** * @brief Check whether an scanner is in use. * * @param[in] scanner Scanner. * * @return 1 yes, 0 no. */ int scanner_in_use (scanner_t scanner) { return !!(sql_int ("SELECT count(*) FROM tasks WHERE scanner = %llu" " AND hidden = 0;", scanner) || sql_int ("SELECT count(*) FROM configs WHERE scanner = %llu", scanner)); } /** * @brief Check whether a trashcan scanner is writable. * * @param[in] scanner Scanner. * * @return 1 yes, 0 no. */ int trash_scanner_in_use (scanner_t scanner) { return !!(sql_int ("SELECT count(*) FROM tasks" " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH), scanner) || sql_int ("SELECT count(*) FROM configs_trash" " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH), scanner)); } /** * @brief Check whether a scanner is writable. * * @param[in] scanner Scanner. * * @return 1 yes, 0 no. */ int scanner_writable (scanner_t scanner) { return 1; } /** * @brief Check whether a trashcan scanner is writable. * * @param[in] scanner Scanner. * * @return 1 yes, 0 no. */ int trash_scanner_writable (scanner_t scanner) { return trash_scanner_in_use (scanner) == 0; } /** * @brief Return whether a trashcan scanner is readable. * * @param[in] scanner Scanner. * * @return 1 if readable, else 0. */ int trash_scanner_readable (scanner_t scanner) { char *uuid; scanner_t found = 0; if (scanner == 0) return 0; uuid = scanner_uuid (scanner); if (find_trash ("scanner", uuid, &found)) { g_free (uuid); return 0; } g_free (uuid); return found > 0; } /** * @brief Return the name of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated name if available, else NULL. */ char* scanner_name (scanner_t scanner) { return sql_string ("SELECT name FROM scanners WHERE id = %llu;", scanner); } /** * @brief Return the UUID of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated UUID. */ char * scanner_uuid (scanner_t scanner) { return sql_string ("SELECT uuid FROM scanners WHERE id = %llu;", scanner); } /** * @brief Return the UUID of the default scanner. * * @return UUID. */ const char * scanner_uuid_default () { return SCANNER_UUID_DEFAULT; } /** * @brief Return the host of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated host. */ char * scanner_host (scanner_t scanner) { return sql_string ("SELECT host FROM scanners WHERE id = %llu;", scanner); } /** * @brief Return the port of a scanner. * * @param[in] scanner Scanner. * * @return Scanner port, -1 if not found; */ int scanner_port (scanner_t scanner) { int port; char *str; str = sql_string ("SELECT port FROM scanners WHERE id = %llu;", scanner); if (!str) return -1; port = atoi (str); g_free (str); return port; } /** * @brief Return the type of a scanner. * * @param[in] scanner Scanner. * * @return Scanner type, -1 if not found; */ int scanner_type (scanner_t scanner) { int type; char *str; str = sql_string ("SELECT type FROM scanners WHERE id = %llu;", scanner); if (!str) return -1; type = atoi (str); g_free (str); return type; } /** * @brief Return the CA Certificate of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated CA Certificate. */ char * scanner_ca_pub (scanner_t scanner) { return sql_string ("SELECT ca_pub FROM scanners WHERE id = %llu;", scanner); } /** * @brief Return the Certificate of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated Certificate. */ char * scanner_key_pub (scanner_t scanner) { if (scanner == 0) return NULL; return sql_string ("SELECT value FROM credentials_data" " WHERE credential = (SELECT credential FROM scanners" " WHERE id = %llu)" " AND type = 'certificate';", scanner); } /** * @brief Return the private key of a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated private key. */ char * scanner_key_priv (scanner_t scanner) { gchar *key; key = sql_string ("SELECT value FROM credentials_data" " WHERE credential = (SELECT credential FROM scanners" " WHERE id = %llu)" " AND type = 'private_key';", scanner); if (key == NULL) { gchar *secret; lsc_crypt_ctx_t crypt_ctx; crypt_ctx = lsc_crypt_new (); secret = sql_string ("SELECT value FROM credentials_data" " WHERE credential" " = (SELECT credential FROM scanners" " WHERE id = %llu)" " AND type = 'secret';", scanner); key = g_strdup (lsc_crypt_get_private_key (crypt_ctx, secret)); lsc_crypt_release (crypt_ctx); g_free (secret); } return key; } /** * @brief Return the login associated with a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated login if available, else NULL. */ char* scanner_login (scanner_t scanner) { return sql_string ("SELECT credentials_data.value" " FROM scanners, credentials_data" " WHERE scanners.id = %llu" " AND credentials_data.credential = scanners.credential" " AND credentials_data.type = 'username';", scanner); } /** * @brief Return the password associated with a scanner. * * @param[in] scanner Scanner. * * @return Newly allocated password if available, else NULL. */ char* scanner_password (scanner_t scanner) { gchar *password; password = sql_string ("SELECT credentials_data.value" " FROM scanners, credentials_data" " WHERE scanners.id = %llu" " AND credentials_data.credential" " = scanners.credential" " AND credentials_data.type = 'password';", scanner); if (password == NULL) { gchar *secret; lsc_crypt_ctx_t crypt_ctx; crypt_ctx = lsc_crypt_new (); secret = sql_string ("SELECT credentials_data.value" " FROM scanners, credentials_data" " WHERE scanners.id = %llu" " AND credentials_data.credential" " = scanners.credential" " AND credentials_data.type = 'secret';", scanner); password = g_strdup (lsc_crypt_get_password (crypt_ctx, secret)); lsc_crypt_release (crypt_ctx); g_free (secret); } return password; } /** * @brief Return the name of a scanner in the trashcan. * * @param[in] scanner Scanner. * * @return Newly allocated name if available, else NULL. */ char* trash_scanner_name (scanner_t scanner) { return sql_string ("SELECT name FROM scanners_trash WHERE id = %llu;", scanner); } /** * @brief Return the UUID of a scanner in the trashcan. * * @param[in] scanner Scanner. * * @return Newly allocated UUID. */ char * trash_scanner_uuid (scanner_t scanner) { return sql_string ("SELECT uuid FROM scanners_trash WHERE id = %llu;", scanner); } /** * @brief Count number of scanners. * * @param[in] get GET params. * * @return Total number of scanners in filtered set. */ int scanner_count (const get_data_t *get) { static const char *extra_columns[] = SCANNER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = SCANNER_ITERATOR_COLUMNS; static column_t trash_columns[] = SCANNER_ITERATOR_TRASH_COLUMNS; return count ("scanner", get, columns, trash_columns, extra_columns, 0, 0, 0, TRUE); } /** * @brief Get the default scanner path or host. * * @return Newly allocated scanner path or host. */ char * openvas_default_scanner_host () { return sql_string ("SELECT host FROM scanners WHERE uuid = '%s'", SCANNER_UUID_DEFAULT); } /** * @brief Create a new connection to an OSP scanner relay. * * @param[in] host Original host name or IP address. * @param[in] port Original port. * @param[in] ca_pub Original CA certificate. * @param[in] key_pub Public key for authentication. * @param[in] key_priv Private key for authentication. * * @return New connection if success, NULL otherwise. */ static osp_connection_t * osp_scanner_relay_connect (const char *host, int port, const char *ca_pub, const char *key_pub, const char *key_priv) { int ret, new_port; gchar *new_host, *new_ca_pub; osp_connection_t *connection; new_host = NULL; new_ca_pub = NULL; new_port = 0; ret = slave_get_relay (host, port, ca_pub, "OSP", &new_host, &new_port, &new_ca_pub); switch (ret) { case 0: break; case 1: g_warning ("No relay found for Scanner at %s:%d", host, port); return NULL; default: g_warning ("%s: Error getting relay for Scanner at %s:%d", __func__, host, port); return NULL; } connection = osp_connection_new (new_host, new_port, new_ca_pub, key_pub, key_priv); if (connection == NULL) { if (new_port) g_warning ("Could not connect to relay at %s:%d" " for Scanner at %s:%d", new_host, new_port, host, port); else g_warning ("Could not connect to relay at %s" " for Scanner at %s:%d", new_host, host, port); } g_free (new_host); g_free (new_ca_pub); return connection; } /** * @brief Create a new connection to an OSP scanner using the scanner data. * * @param[in] host Host name or IP address. * @param[in] port Port. * @param[in] ca_pub CA certificate. * @param[in] key_pub Public key. * @param[in] key_priv Private key. * * @return New connection if success, NULL otherwise. */ osp_connection_t * osp_connect_with_data (const char *host, int port, const char *ca_pub, const char *key_pub, const char *key_priv) { osp_connection_t *connection; int is_unix_socket = (host && *host == '/') ? 1 : 0; if (is_unix_socket == 0 && get_relay_mapper_path ()) { connection = osp_scanner_relay_connect (host, port, ca_pub, key_pub, key_priv); } else { connection = osp_connection_new (host, port, ca_pub, key_pub, key_priv); if (connection == NULL) { if (is_unix_socket) g_warning ("Could not connect to Scanner at %s", host); else g_warning ("Could not connect to Scanner at %s:%d", host, port); } } return connection; } /** * @brief Create a new connection to an OSP scanner. * * @param[in] scanner Scanner. * * @return New connection if success, NULL otherwise. */ osp_connection_t * osp_scanner_connect (scanner_t scanner) { int port; osp_connection_t *connection; char *host, *ca_pub, *key_pub, *key_priv; assert (scanner); host = scanner_host (scanner); if (host && *host == '/') { port = 0; ca_pub = NULL; key_pub = NULL; key_priv = NULL; } else { port = scanner_port (scanner); ca_pub = scanner_ca_pub (scanner); key_pub = scanner_key_pub (scanner); key_priv = scanner_key_priv (scanner); } connection = osp_connect_with_data (host, port, ca_pub, key_pub, key_priv); g_free (host); g_free (ca_pub); g_free (key_pub); g_free (key_priv); return connection; } /** * @brief Get an OSP Scanner's get_version info. * * @param[in] iterator Scanner object iterator. * @param[out] s_name Scanner name. * @param[out] s_ver Scanner version. * @param[out] d_name Daemon name. * @param[out] d_ver Daemon version. * @param[out] p_name Protocol name. * @param[out] p_ver Protocol version. * * @return 0 success, 1 for failure. */ int osp_get_version_from_iterator (iterator_t *iterator, char **s_name, char **s_ver, char **d_name, char **d_ver, char **p_name, char **p_ver) { osp_connection_t *connection; assert (iterator); connection = osp_connect_with_data (scanner_iterator_host (iterator), scanner_iterator_port (iterator), scanner_iterator_ca_pub (iterator), scanner_iterator_key_pub (iterator), scanner_iterator_key_priv (iterator)); if (!connection) return 1; if (osp_get_version (connection, s_name, s_ver, d_name, d_ver, p_name, p_ver)) return 1; osp_connection_close (connection); return 0; } /** * @brief Get an OSP Scanner's get_scanner_details info. * * @param[in] iterator Scanner object iterator. * @param[out] desc Scanner description. * @param[out] params Scanner parameters. * * @return 0 success, 1 for failure. */ int osp_get_details_from_iterator (iterator_t *iterator, char **desc, GSList **params) { osp_connection_t *connection; assert (iterator); connection = osp_connect_with_data (scanner_iterator_host (iterator), scanner_iterator_port (iterator), scanner_iterator_ca_pub (iterator), scanner_iterator_key_pub (iterator), scanner_iterator_key_priv (iterator)); if (!connection) return 1; if (osp_get_scanner_details (connection, desc, params)) return 1; osp_connection_close (connection); return 0; } /** * @brief Verify a scanner. * * @param[in] scanner_id Scanner UUID. * @param[out] version Version returned by the scanner. * * @return 0 success, 1 failed to find scanner, 2 failed to get version, * 3 authentication failed, 99 if permission denied, -1 error. */ int verify_scanner (const char *scanner_id, char **version) { get_data_t get; iterator_t scanner; if (acl_user_may ("verify_scanner") == 0) return 99; memset (&get, '\0', sizeof (get)); get.id = g_strdup (scanner_id); if (init_scanner_iterator (&scanner, &get) || !next (&scanner)) { g_free (get.id); return 1; } g_free (get.id); if (scanner_iterator_type (&scanner) == SCANNER_TYPE_OSP || scanner_iterator_type (&scanner) == SCANNER_TYPE_OPENVAS || scanner_iterator_type (&scanner) == SCANNER_TYPE_OSP_SENSOR) { int ret = osp_get_version_from_iterator (&scanner, NULL, version, NULL, NULL, NULL, NULL); cleanup_iterator (&scanner); if (ret) return 2; return 0; } else if (scanner_iterator_type (&scanner) == SCANNER_TYPE_CVE) { if (version) *version = g_strdup ("GVM/" GVMD_VERSION); cleanup_iterator (&scanner); return 0; } assert (0); return -1; } /** * @brief List scanners. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * * @return 0 success, -1 error. */ int manage_get_scanners (GSList *log_config, const db_conn_info_t *database) { iterator_t scanners; int ret; g_info (" Getting scanners."); ret = manage_option_setup (log_config, database); if (ret) return ret; init_iterator (&scanners, "SELECT uuid, type, host, port, name FROM scanners;"); while (next (&scanners)) { const char *scanner_id, *scanner_host, *scanner_port, *scanner_name; scanner_type_t scanner_type; const char *scanner_type_str; scanner_id = iterator_string (&scanners, 0); scanner_type = iterator_int (&scanners, 1); scanner_host = iterator_string (&scanners, 2); scanner_port = iterator_string (&scanners, 3); scanner_name = iterator_string (&scanners, 4); switch (scanner_type) { case SCANNER_TYPE_OSP: scanner_type_str = "OSP"; break; case SCANNER_TYPE_OPENVAS: scanner_type_str = "OpenVAS"; break; case SCANNER_TYPE_CVE: scanner_type_str = "CVE"; break; case SCANNER_TYPE_OSP_SENSOR: scanner_type_str = "OSP-Sensor"; break; default: scanner_type_str = NULL; } if (scanner_type_str) printf ("%s %s %s %s %s\n", scanner_id, scanner_type_str, scanner_host, scanner_port, scanner_name); else printf ("%s Unknown-%d %s %s %s\n", scanner_id, scanner_type, scanner_host, scanner_port, scanner_name); } cleanup_iterator (&scanners); manage_option_cleanup (); return 0; } /* Schedules. */ /** * @brief Find a schedule for a specific permission, given a UUID. * * @param[in] uuid UUID of schedule. * @param[out] schedule Schedule return, 0 if successfully failed to find schedule. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find schedule), TRUE on error. */ gboolean find_schedule_with_permission (const char* uuid, schedule_t* schedule, const char *permission) { return find_resource_with_permission ("schedule", uuid, schedule, permission, 0); } /** * @brief Create a schedule. * * @param[in] name Name of schedule. * @param[in] comment Comment on schedule. * @param[in] ical_string iCalendar string. Overrides first_time, period, * period_months, byday and duration. * @param[in] zone Timezone. * @param[out] schedule Created schedule. * @param[out] error_out Output for iCalendar errors and warnings. * * @return 0 success, 1 schedule exists already, * 3 error in iCal string, 4 error in timezone, 99 permission denied. */ int create_schedule (const char* name, const char *comment, const char *ical_string, const char* zone, schedule_t *schedule, gchar **error_out) { gchar *quoted_comment, *quoted_name, *quoted_timezone; gchar *insert_timezone; int byday_mask; icalcomponent *ical_component; icaltimezone *ical_timezone; gchar *quoted_ical; time_t first_time, period, period_months, duration; assert (current_credentials.uuid); assert (ical_string && strcmp (ical_string, "")); sql_begin_immediate (); if (acl_user_may ("create_schedule") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (name, "schedule", 0)) { sql_rollback (); return 1; } quoted_name = sql_quote (name); if (zone && strcmp (zone, "")) insert_timezone = g_strdup (zone); else insert_timezone = sql_string ("SELECT timezone FROM users" " WHERE users.uuid = '%s';", current_credentials.uuid); if (insert_timezone == NULL) insert_timezone = g_strdup ("UTC"); else { insert_timezone = g_strstrip (insert_timezone); if (strcmp (insert_timezone, "") == 0) { g_free (insert_timezone); insert_timezone = g_strdup ("UTC"); } } ical_timezone = icalendar_timezone_from_string (insert_timezone); if (ical_timezone == NULL) { g_free (insert_timezone); return 4; } quoted_comment = sql_quote (comment ? comment : ""); quoted_timezone = sql_quote (insert_timezone); ical_component = icalendar_from_string (ical_string, ical_timezone, error_out); if (ical_component == NULL) { g_free (quoted_name); g_free (quoted_comment); g_free (insert_timezone); g_free (quoted_timezone); return 3; } quoted_ical = sql_quote (icalcomponent_as_ical_string (ical_component)); first_time = icalendar_first_time_from_vcalendar (ical_component, ical_timezone); duration = icalendar_duration_from_vcalendar (ical_component); icalendar_approximate_rrule_from_vcalendar (ical_component, &period, &period_months, &byday_mask); sql ("INSERT INTO schedules" " (uuid, name, owner, comment, first_time, period, period_months," " byday, duration, timezone, icalendar," " creation_time, modification_time)" " VALUES" " (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', %i, %i, %i, %i, %i," " '%s', '%s'," " m_now (), m_now ());", quoted_name, current_credentials.uuid, quoted_comment, first_time, period, period_months, byday_mask, duration, quoted_timezone, quoted_ical); if (schedule) *schedule = sql_last_insert_id (); g_free (quoted_name); g_free (quoted_comment); g_free (insert_timezone); g_free (quoted_timezone); g_free (quoted_ical); sql_commit (); return 0; } /** * @brief Create a schedule from an existing schedule. * * @param[in] name Name of new schedule. NULL to copy from existing. * @param[in] comment Comment on new schedule. NULL to copy from * existing. * @param[in] schedule_id UUID of existing schedule. * @param[out] new_schedule New schedule. * * @return 0 success, 1 schedule exists already, 2 failed to find existing * schedule, -1 error. */ int copy_schedule (const char* name, const char* comment, const char *schedule_id, schedule_t* new_schedule) { return copy_resource ("schedule", name, comment, schedule_id, "first_time, period, period_months, byday, duration," " timezone, icalendar", 1, new_schedule, NULL); } /** * @brief Delete a schedule. * * @param[in] schedule_id Schedule. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a task refers to the schedule, * 2 failed to find schedule, 99 permission denied, -1 error. */ int delete_schedule (const char *schedule_id, int ultimate) { schedule_t schedule = 0; sql_begin_immediate (); if (acl_user_may ("delete_schedule") == 0) { sql_rollback (); return 99; } if (find_schedule_with_permission (schedule_id, &schedule, "delete_schedule")) { sql_rollback (); return -1; } if (schedule == 0) { if (find_trash ("schedule", schedule_id, &schedule)) { sql_rollback (); return -1; } if (schedule == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by a task in the trashcan. */ if (sql_int ("SELECT count(*) FROM tasks" " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH) ";", schedule)) { sql_rollback (); return 1; } permissions_set_orphans ("schedule", schedule, LOCATION_TRASH); tags_remove_resource ("schedule", schedule, LOCATION_TRASH); sql ("DELETE FROM schedules_trash WHERE id = %llu;", schedule); sql_commit (); return 0; } if (ultimate == 0) { if (sql_int ("SELECT count(*) FROM tasks" " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE) " AND hidden = 0;", schedule)) { sql_rollback (); return 1; } sql ("INSERT INTO schedules_trash" " (uuid, owner, name, comment, first_time, period, period_months," " byday, duration, timezone, creation_time," " modification_time, icalendar)" " SELECT uuid, owner, name, comment, first_time, period, period_months," " byday, duration, timezone, creation_time," " modification_time, icalendar" " FROM schedules WHERE id = %llu;", schedule); /* Update the location of the schedule in any trashcan tasks. */ sql ("UPDATE tasks" " SET schedule = %llu," " schedule_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE) ";", sql_last_insert_id (), schedule); permissions_set_locations ("schedule", schedule, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("schedule", schedule, sql_last_insert_id (), LOCATION_TRASH); } else if (sql_int ("SELECT count(*) FROM tasks" " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TABLE), schedule)) { sql_rollback (); return 1; } else { permissions_set_orphans ("schedule", schedule, LOCATION_TABLE); tags_remove_resource ("schedule", schedule, LOCATION_TABLE); } sql ("DELETE FROM schedules WHERE id = %llu;", schedule); sql_commit (); return 0; } /** * @brief Return whether a schedule is in use by a task. * * @param[in] schedule Schedule. * * @return 1 if in use, else 0. */ int schedule_in_use (schedule_t schedule) { return !!sql_int ("SELECT count(*) FROM tasks WHERE schedule = %llu" " AND hidden = 0;", schedule); } /** * @brief Return whether a trashcan schedule is in use by a task. * * @param[in] schedule schedule. * * @return 1 if in use, else 0. */ int trash_schedule_in_use (schedule_t schedule) { return !!sql_int ("SELECT count(*) FROM tasks" " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH), schedule); } /** * @brief Return whether a schedule is writable. * * @param[in] schedule Schedule. * * @return 1 if writable, else 0. */ int schedule_writable (schedule_t schedule) { return 1; } /** * @brief Return whether a trashcan schedule is writable. * * @param[in] schedule Schedule. * * @return 1 if writable, else 0. */ int trash_schedule_writable (schedule_t schedule) { return trash_schedule_in_use (schedule) == 0; } /** * @brief Return whether a trashcan schedule is readable. * * @param[in] schedule Schedule. * * @return 1 if readable, else 0. */ int trash_schedule_readable (schedule_t schedule) { char *uuid; schedule_t found = 0; if (schedule == 0) return 0; uuid = schedule_uuid (schedule); if (find_trash ("schedule", uuid, &found)) { g_free (uuid); return 0; } g_free (uuid); return found > 0; } /** * @brief Return the UUID of a schedule. * * @param[in] schedule Schedule. * * @return Newly allocated UUID. */ char * schedule_uuid (schedule_t schedule) { return sql_string ("SELECT uuid FROM schedules WHERE id = %llu;", schedule); } /** * @brief Return the UUID of a trash schedule. * * @param[in] schedule Schedule. * * @return Newly allocated UUID. */ char * trash_schedule_uuid (schedule_t schedule) { return sql_string ("SELECT uuid FROM schedules_trash WHERE id = %llu;", schedule); } /** * @brief Return the name of a schedule. * * @param[in] schedule Schedule. * * @return Newly allocated name. */ char * schedule_name (schedule_t schedule) { return sql_string ("SELECT name FROM schedules WHERE id = %llu;", schedule); } /** * @brief Return the name of a trash schedule. * * @param[in] schedule Schedule. * * @return Newly allocated name. */ char * trash_schedule_name (schedule_t schedule) { return sql_string ("SELECT name FROM schedules_trash WHERE id = %llu;", schedule); } /** * @brief Return the period of a schedule. * * @param[in] schedule Schedule. * * @return Period in seconds. */ int schedule_period (schedule_t schedule) { return sql_int ("SELECT period FROM schedules WHERE id = %llu;", schedule); } /** * @brief Return the duration of a schedule. * * @param[in] schedule Schedule. * * @return Duration in seconds. */ int schedule_duration (schedule_t schedule) { return sql_int ("SELECT duration FROM schedules WHERE id = %llu;", schedule); } /** * @brief Return info about a schedule. * * @param[in] schedule Schedule. * @param[in] trash Whether to get schedule from trash. * @param[out] icalendar iCalendar string. * @param[out] zone Timezone string. * * @return 0 success, -1 error. */ int schedule_info (schedule_t schedule, int trash, gchar **icalendar, gchar **zone) { iterator_t schedules; init_iterator (&schedules, "SELECT icalendar, timezone FROM schedules%s" " WHERE id = %llu;", trash ? "_trash" : "", schedule); if (next (&schedules)) { *icalendar = g_strdup (iterator_string (&schedules, 0)); *zone = g_strdup (iterator_string (&schedules, 1)); cleanup_iterator (&schedules); return 0; } cleanup_iterator (&schedules); return -1; } /** * @brief Filter columns for schedule iterator. */ #define SCHEDULE_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "first_time", "period", "period_months", \ "duration", "timezone", "first_run", "next_run", NULL } /** * @brief Schedule iterator columns. */ #define SCHEDULE_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (schedules), \ { "first_time", NULL, KEYWORD_TYPE_INTEGER }, \ { "period", NULL, KEYWORD_TYPE_INTEGER }, \ { "period_months", NULL, KEYWORD_TYPE_INTEGER }, \ { "duration", NULL, KEYWORD_TYPE_INTEGER }, \ { "timezone", NULL, KEYWORD_TYPE_STRING }, \ { "icalendar", NULL, KEYWORD_TYPE_STRING }, \ { "next_time_ical (icalendar, timezone)", \ "next_run", \ KEYWORD_TYPE_INTEGER }, \ { "first_time", "first_run", KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Schedule iterator columns for trash case. */ #define SCHEDULE_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (schedules_trash), \ { "first_time", NULL, KEYWORD_TYPE_INTEGER }, \ { "period", NULL, KEYWORD_TYPE_INTEGER }, \ { "period_months", NULL, KEYWORD_TYPE_INTEGER }, \ { "duration", NULL, KEYWORD_TYPE_INTEGER }, \ { "timezone", NULL, KEYWORD_TYPE_STRING }, \ { "icalendar", NULL, KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count the number of schedules. * * @param[in] get GET params. * * @return Total number of schedules filtered set. */ int schedule_count (const get_data_t *get) { static const char *filter_columns[] = SCHEDULE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = SCHEDULE_ITERATOR_COLUMNS; static column_t trash_columns[] = SCHEDULE_ITERATOR_TRASH_COLUMNS; return count ("schedule", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a schedule iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find filter, 2 failed to find" * filter (filt_id), -1 error. */ int init_schedule_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = SCHEDULE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = SCHEDULE_ITERATOR_COLUMNS; static column_t trash_columns[] = SCHEDULE_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "schedule", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the timezone from a schedule iterator. * * @param[in] iterator Iterator. * * @return Timezone, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (schedule_iterator_timezone, GET_ITERATOR_COLUMN_COUNT + 4); /** * @brief Get the iCalendar string from a schedule iterator. * * @param[in] iterator Iterator. * * @return The iCalendar string or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (schedule_iterator_icalendar, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Initialise a task schedule iterator. * * Lock the database before initialising. * * @param[in] iterator Iterator. * * @return 0 success, 1 failed to get lock, -1 error. */ int init_task_schedule_iterator (iterator_t* iterator) { int ret; ret = sql_begin_immediate_giveup (); if (ret) return ret; init_iterator (iterator, "SELECT tasks.id, tasks.uuid," " schedules.id, tasks.schedule_next_time," " schedules.icalendar, schedules.timezone," " schedules.duration," " users.uuid, users.name" " FROM tasks, schedules, users" " WHERE tasks.schedule = schedules.id" " AND tasks.hidden = 0" " AND (tasks.owner = (users.id))" /* Sort by task and prefer owner of task or schedule as user */ " ORDER BY tasks.id," " (users.id = tasks.owner) DESC," " (users.id = schedules.owner) DESC;"); return 0; } /** * @brief Cleanup a task schedule iterator. * * @param[in] iterator Iterator. */ void cleanup_task_schedule_iterator (iterator_t* iterator) { cleanup_iterator (iterator); sql_commit (); } /** * @brief Get the task from a task schedule iterator. * * @param[in] iterator Iterator. * * @return task. */ task_t task_schedule_iterator_task (iterator_t* iterator) { if (iterator->done) return 0; return (task_t) iterator_int64 (iterator, 0); } /** * @brief Get the task UUID from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Task UUID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_schedule_iterator_task_uuid, 1); /** * @brief Get the next time from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Next time. */ static time_t task_schedule_iterator_next_time (iterator_t* iterator) { if (iterator->done) return 0; return (time_t) iterator_int64 (iterator, 3); } /** * @brief Get the iCalendar string from a task schedule iterator. * * @param[in] iterator Iterator. * * @return period. */ DEF_ACCESS (task_schedule_iterator_icalendar, 4); /** * @brief Get the timezone from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Timezone, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_schedule_iterator_timezone, 5); /** * @brief Get the next time from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Next time. */ static time_t task_schedule_iterator_duration (iterator_t* iterator) { if (iterator->done) return 0; return (time_t) iterator_int64 (iterator, 6); } /** * @brief Get the task owner uuid from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Owner UUID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_schedule_iterator_owner_uuid, 7); /** * @brief Get the task owner name from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Owner name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (task_schedule_iterator_owner_name, 8); /** * @brief Get the start due state from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Start due flag. */ gboolean task_schedule_iterator_start_due (iterator_t* iterator) { task_status_t run_status; time_t start_time; if (iterator->done) return FALSE; if (task_schedule_iterator_next_time (iterator) == 0) return FALSE; run_status = task_run_status (task_schedule_iterator_task (iterator)); start_time = task_schedule_iterator_next_time (iterator); if ((run_status == TASK_STATUS_DONE || run_status == TASK_STATUS_INTERRUPTED || run_status == TASK_STATUS_NEW || run_status == TASK_STATUS_STOPPED) && (start_time > 0) && (start_time <= time (NULL))) return TRUE; return FALSE; } /** * @brief Get the stop due state from a task schedule iterator. * * @param[in] iterator Iterator. * * @return Stop due flag. */ gboolean task_schedule_iterator_stop_due (iterator_t* iterator) { const char *icalendar, *zone; time_t duration; if (iterator->done) return FALSE; icalendar = task_schedule_iterator_icalendar (iterator); zone = task_schedule_iterator_timezone (iterator); duration = task_schedule_iterator_duration (iterator); if (duration) { report_t report; task_status_t run_status; report = task_running_report (task_schedule_iterator_task (iterator)); if (report && (report_scheduled (report) == 0)) return FALSE; run_status = task_run_status (task_schedule_iterator_task (iterator)); if (run_status == TASK_STATUS_RUNNING || run_status == TASK_STATUS_REQUESTED || run_status == TASK_STATUS_QUEUED) { time_t now, start; now = time (NULL); start = icalendar_next_time_from_string (icalendar, zone, -1); if ((start + duration) < now) return TRUE; } } return FALSE; } /** * @brief Get if schedule of task in iterator is timed out. * * @param[in] iterator Iterator. * * @return Whether task schedule is timed out. */ gboolean task_schedule_iterator_timed_out (iterator_t* iterator) { time_t schedule_timeout_secs; int duration; task_status_t run_status; time_t start_time, timeout_time; if (get_schedule_timeout () < 0) return FALSE; if (iterator->done) return FALSE; start_time = task_schedule_iterator_next_time (iterator); if (start_time == 0) return FALSE; schedule_timeout_secs = get_schedule_timeout () * 60; if (schedule_timeout_secs < SCHEDULE_TIMEOUT_MIN_SECS) schedule_timeout_secs = SCHEDULE_TIMEOUT_MIN_SECS; run_status = task_run_status (task_schedule_iterator_task (iterator)); duration = task_schedule_iterator_duration (iterator); if (duration && (duration < schedule_timeout_secs)) timeout_time = start_time + duration; else timeout_time = start_time + schedule_timeout_secs; if ((run_status == TASK_STATUS_DONE || run_status == TASK_STATUS_INTERRUPTED || run_status == TASK_STATUS_NEW || run_status == TASK_STATUS_STOPPED) && (timeout_time <= time (NULL))) return TRUE; return FALSE; } /** * @brief Initialise a schedule task iterator. * * @param[in] iterator Iterator. * @param[in] schedule Schedule. */ void init_schedule_task_iterator (iterator_t* iterator, schedule_t schedule) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (current_credentials.uuid); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_tasks")); available = acl_where_owned ("task", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT id, uuid, name, %s FROM tasks" " WHERE schedule = %llu AND hidden = 0" " ORDER BY name ASC;", with_clause ? with_clause : "", available, schedule, current_credentials.uuid); g_free (with_clause); g_free (available); } /** * @brief Get the UUID from a schedule task iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (schedule_task_iterator_uuid, 1); /** * @brief Get the name from a schedule task iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (schedule_task_iterator_name, 2); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int schedule_task_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 3); } /** * @brief Modify a schedule. * * @param[in] schedule_id UUID of schedule. * @param[in] name Name of schedule. * @param[in] comment Comment on schedule. * @param[in] ical_string iCalendar string. Overrides first_time, period, * period_months, byday and duration. * @param[in] zone Timezone. * @param[out] error_out Output for iCalendar errors and warnings. * * @return 0 success, 1 failed to find schedule, 2 schedule with new name exists, * 3 error in type name, 4 schedule_id required, * 6 error in iCalendar, 7 error in zone, * 99 permission denied, -1 internal error. */ int modify_schedule (const char *schedule_id, const char *name, const char *comment, const char *ical_string, const char *zone, gchar **error_out) { gchar *quoted_name, *quoted_comment, *quoted_timezone, *quoted_icalendar; icalcomponent *ical_component; icaltimezone *ical_timezone; schedule_t schedule; time_t new_next_time, ical_first_time, ical_duration; gchar *real_timezone; assert (ical_string && strcmp (ical_string, "")); if (schedule_id == NULL) return 4; sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_schedule") == 0) { sql_rollback (); return 99; } schedule = 0; if (find_schedule_with_permission (schedule_id, &schedule, "modify_schedule")) { sql_rollback (); return -1; } if (schedule == 0) { sql_rollback (); return 1; } /* Check whether a schedule with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "schedule", schedule)) { sql_rollback (); return 2; } quoted_name = sql_quote (name); } else quoted_name = NULL; /* Update basic data */ quoted_comment = comment ? sql_quote (comment) : NULL; if (zone == NULL) quoted_timezone = NULL; else { quoted_timezone = g_strstrip (sql_quote (zone)); if (strcmp (quoted_timezone, "") == 0) { g_free (quoted_timezone); quoted_timezone = NULL; } } sql ("UPDATE schedules SET" " name = %s%s%s," " comment = %s%s%s," " timezone = %s%s%s" " WHERE id = %llu;", quoted_name ? "'" : "", quoted_name ? quoted_name : "name", quoted_name ? "'" : "", quoted_comment ? "'" : "", quoted_comment ? quoted_comment : "comment", quoted_comment ? "'" : "", quoted_timezone ? "'" : "", quoted_timezone ? quoted_timezone : "timezone", quoted_timezone ? "'" : "", schedule); real_timezone = sql_string ("SELECT timezone FROM schedules WHERE id = %llu;", schedule); /* Update times */ ical_timezone = icalendar_timezone_from_string (real_timezone); if (ical_timezone == NULL) { g_free (real_timezone); sql_rollback (); return 7; } ical_component = icalendar_from_string (ical_string, ical_timezone, error_out); if (ical_component == NULL) { g_free (real_timezone); sql_rollback (); return 6; } quoted_icalendar = sql_quote (icalcomponent_as_ical_string (ical_component)); ical_first_time = icalendar_first_time_from_vcalendar (ical_component, ical_timezone); ical_duration = icalendar_duration_from_vcalendar (ical_component); sql ("UPDATE schedules SET" " icalendar = '%s'," " first_time = %ld," " period = 0," " period_months = 0," " byday = 0," " duration = %d," " modification_time = m_now ()" " WHERE id = %llu;", quoted_icalendar, ical_first_time, ical_duration, schedule); // Update scheduled next times for tasks new_next_time = icalendar_next_time_from_vcalendar (ical_component, real_timezone, 0); sql ("UPDATE tasks SET schedule_next_time = %ld" " WHERE schedule = %llu;", new_next_time, schedule); g_free (quoted_comment); g_free (quoted_name); g_free (quoted_timezone); g_free (quoted_icalendar); free (real_timezone); icalcomponent_free (ical_component); sql_commit (); return 0; } /* Groups. */ /** * @brief Find a group for a specific permission, given a UUID. * * @param[in] uuid UUID of group. * @param[out] group Group return, 0 if successfully failed to find group. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find group), TRUE on error. */ static gboolean find_group_with_permission (const char* uuid, group_t* group, const char *permission) { return find_resource_with_permission ("group", uuid, group, permission, 0); } /** * @brief Create a group from an existing group. * * @param[in] name Name of new group. NULL to copy from existing. * @param[in] comment Comment on new group. NULL to copy from existing. * @param[in] group_id UUID of existing group. * @param[out] new_group_return New group. * * @return 0 success, 1 group exists already, 2 failed to find existing * group, 99 permission denied, -1 error. */ int copy_group (const char *name, const char *comment, const char *group_id, group_t *new_group_return) { int ret; group_t new, old; sql_begin_immediate (); ret = copy_resource_lock ("group", name, comment, group_id, NULL, 1, &new, &old); if (ret) { sql_rollback (); return ret; } sql ("INSERT INTO group_users (\"group\", \"user\")" " SELECT %llu, \"user\" FROM group_users" " WHERE \"group\" = %llu;", new, old); sql_commit (); if (new_group_return) *new_group_return = new; return 0; } /** * @brief Add users to a group. * * Caller must take care of transaction. * * @param[in] type Type. * @param[in] resource Group or role. * @param[in] users List of users. * * @return 0 success, 2 failed to find user, 4 user name validation failed, * 99 permission denied, -1 error. */ static int add_users (const gchar *type, resource_t resource, const char *users) { if (users) { gchar **split, **point; GList *added; /* Add each user. */ added = NULL; split = g_strsplit_set (users, " ,", 0); point = split; while (*point) { user_t user; gchar *name; name = *point; g_strstrip (name); if (strcmp (name, "") == 0) { point++; continue; } if (g_list_find_custom (added, name, (GCompareFunc) strcmp)) { point++; continue; } added = g_list_prepend (added, name); if (user_exists (name) == 0) { g_list_free (added); g_strfreev (split); return 2; } if (find_user_by_name (name, &user)) { g_list_free (added); g_strfreev (split); return -1; } if (user == 0) { gchar *uuid; if (validate_username (name)) { g_list_free (added); g_strfreev (split); return 4; } uuid = user_uuid_any_method (name); if (uuid == NULL) { g_list_free (added); g_strfreev (split); return -1; } if (sql_int ("SELECT count(*) FROM users WHERE uuid = '%s';", uuid) == 0) { gchar *quoted_name; quoted_name = sql_quote (name); sql ("INSERT INTO users" " (uuid, name, creation_time, modification_time)" " VALUES" " ('%s', '%s', m_now (), m_now ());", uuid, quoted_name); g_free (quoted_name); user = sql_last_insert_id (); } else { /* find_user_by_name should have found it. */ assert (0); g_free (uuid); g_list_free (added); g_strfreev (split); return -1; } g_free (uuid); } if (find_user_by_name_with_permission (name, &user, "get_users")) { g_list_free (added); g_strfreev (split); return -1; } if (user == 0) { g_list_free (added); g_strfreev (split); return 99; } sql ("INSERT INTO %s_users (\"%s\", \"user\") VALUES (%llu, %llu);", type, type, resource, user); point++; } g_list_free (added); g_strfreev (split); } return 0; } /** * @brief Create a group. * * @param[in] group_name Group name. * @param[in] comment Comment on group. * @param[in] users Users group applies to. * @param[in] special_full Whether to give group super on itself (full * sharing between members). * @param[out] group Group return. * * @return 0 success, 1 group exists already, 2 failed to find user, 4 user * name validation failed, 99 permission denied, -1 error. */ int create_group (const char *group_name, const char *comment, const char *users, int special_full, group_t* group) { int ret; gchar *quoted_group_name, *quoted_comment; assert (current_credentials.uuid); assert (group_name); assert (group); sql_begin_immediate (); if (acl_user_may ("create_group") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (group_name, "group", 0)) { sql_rollback (); return 1; } quoted_group_name = sql_quote (group_name); quoted_comment = comment ? sql_quote (comment) : g_strdup (""); sql ("INSERT INTO groups" " (uuid, name, owner, comment, creation_time, modification_time)" " VALUES" " (make_uuid (), '%s'," " (SELECT id FROM users WHERE uuid = '%s')," " '%s', m_now (), m_now ());", quoted_group_name, current_credentials.uuid, quoted_comment); g_free (quoted_comment); g_free (quoted_group_name); *group = sql_last_insert_id (); ret = add_users ("group", *group, users); if (ret) sql_rollback (); else { if (special_full) { char *group_id; group_id = group_uuid (*group); ret = create_permission_internal (1, "Super", NULL, "group", group_id, "group", group_id, NULL); g_free (group_id); if (ret) { sql_rollback (); return ret; } } sql_commit (); } return ret; } /** * @brief Delete a group. * * @param[in] group_id UUID of group. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a permission refers to the group, 2 failed * to find group, 3 predefined group, 99 permission denied, -1 error. */ int delete_group (const char *group_id, int ultimate) { group_t group = 0; GArray *affected_users; iterator_t users_iter; sql_begin_immediate (); if (acl_user_may ("delete_group") == 0) { sql_rollback (); return 99; } if (find_group_with_permission (group_id, &group, "delete_group")) { sql_rollback (); return -1; } if (group == 0) { if (find_trash ("group", group_id, &group)) { sql_rollback (); return -1; } if (group == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } if (trash_group_in_use (group)) { sql_rollback (); return 1; } sql ("DELETE FROM permissions" " WHERE resource_type = 'group'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); sql ("DELETE FROM permissions_trash" " WHERE resource_type = 'group'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); sql ("DELETE FROM permissions" " WHERE subject_type = 'group'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); sql ("DELETE FROM permissions_trash" " WHERE subject_type = 'group'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); tags_remove_resource ("group", group, LOCATION_TRASH); sql ("DELETE FROM group_users_trash WHERE \"group\" = %llu;", group); sql ("DELETE FROM groups_trash WHERE id = %llu;", group); sql_commit (); return 0; } if (group_in_use (group)) { sql_rollback (); return 1; } if (ultimate == 0) { group_t trash_group; sql ("INSERT INTO groups_trash" " (uuid, owner, name, comment, creation_time, modification_time)" " SELECT uuid, owner, name, comment, creation_time," " modification_time" " FROM groups WHERE id = %llu;", group); trash_group = sql_last_insert_id (); sql ("INSERT INTO group_users_trash" " (\"group\", \"user\")" " SELECT %llu, \"user\"" " FROM group_users WHERE \"group\" = %llu;", trash_group, group); permissions_set_locations ("group", group, trash_group, LOCATION_TRASH); tags_set_locations ("group", group, trash_group, LOCATION_TRASH); permissions_set_subjects ("group", group, trash_group, LOCATION_TRASH); } else { sql ("DELETE FROM permissions" " WHERE resource_type = 'group'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); sql ("DELETE FROM permissions_trash" " WHERE resource_type = 'group'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", group); sql ("DELETE FROM permissions" " WHERE subject_type = 'group'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", group); sql ("DELETE FROM permissions_trash" " WHERE subject_type = 'group'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", group); } tags_remove_resource ("group", group, LOCATION_TABLE); affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM group_users" " WHERE \"group\" = %llu", group); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group); sql ("DELETE FROM groups WHERE id = %llu;", group); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); sql_commit (); return 0; } /** * @brief Return the UUID of a group. * * @param[in] group Group. * * @return Newly allocated UUID if available, else NULL. */ char* group_uuid (group_t group) { return sql_string ("SELECT uuid FROM groups WHERE id = %llu;", group); } /** * @brief Gets users of group as a string. * * @param[in] group Group. * * @return Users. */ gchar * group_users (group_t group) { return sql_string ("SELECT group_concat (name, ', ')" " FROM (SELECT users.name FROM users, group_users" " WHERE group_users.\"group\" = %llu" " AND group_users.user = users.id" " GROUP BY users.name)" " AS sub;", group); } /** * @brief Check whether a group is writable. * * @param[in] group Group. * * @return 1 yes, 0 no. */ int group_writable (group_t group) { return 1; } /** * @brief Check whether a trashcan group is writable. * * @param[in] group Group. * * @return 1 yes, 0 no. */ int trash_group_writable (group_t group) { return 1; } /** * @brief Check whether a group is in use. * * @param[in] group Group. * * @return 1 yes, 0 no. */ int group_in_use (group_t group) { return 0; } /** * @brief Check whether a trashcan group is in use. * * @param[in] group Group. * * @return 1 yes, 0 no. */ int trash_group_in_use (group_t group) { return 0; } /** * @brief Filter columns for group iterator. */ #define GROUP_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, NULL } /** * @brief Group iterator columns. */ #define GROUP_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (groups), \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Group iterator columns for trash case. */ #define GROUP_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (groups_trash), \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of groups. * * @param[in] get GET params. * * @return Total number of groups in grouped set. */ int group_count (const get_data_t *get) { static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS; static column_t columns[] = GROUP_ITERATOR_COLUMNS; static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS; return count ("group", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a group iterator, including observed groups. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find group, 2 failed to find group (filt_id), * -1 error. */ int init_group_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = GROUP_ITERATOR_FILTER_COLUMNS; static column_t columns[] = GROUP_ITERATOR_COLUMNS; static column_t trash_columns[] = GROUP_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "group", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Modify a group. * * @param[in] group_id UUID of group. * @param[in] name Name of group. * @param[in] comment Comment on group. * @param[in] users Group users. * * @return 0 success, 1 failed to find group, 2 failed to find user, 3 group_id * required, 4 user name validation failed, 5 group with new name * exists, 99 permission denied, -1 internal error. */ int modify_group (const char *group_id, const char *name, const char *comment, const char *users) { int ret; gchar *quoted_name, *quoted_comment; group_t group; GArray *affected_users; iterator_t users_iter; assert (current_credentials.uuid); if (group_id == NULL) return 3; sql_begin_immediate (); if (acl_user_may ("modify_group") == 0) { sql_rollback (); return 99; } group = 0; if (find_group_with_permission (group_id, &group, "modify_group")) { sql_rollback (); return -1; } if (group == 0) { sql_rollback (); return 1; } /* Check whether a group with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "group", group)) { sql_rollback (); return 5; } } quoted_name = sql_quote(name ?: ""); quoted_comment = sql_quote (comment ? comment : ""); sql ("UPDATE groups SET" " name = '%s'," " comment = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, quoted_comment, group); g_free (quoted_comment); g_free (quoted_name); affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM group_users" " WHERE \"group\" = %llu", group); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); sql ("DELETE FROM group_users WHERE \"group\" = %llu;", group); ret = add_users ("group", group, users); init_iterator (&users_iter, "SELECT \"user\" FROM group_users" " WHERE \"group\" = %llu", group); // users not looked for in this above loop were removed // -> possible permissions change while (next (&users_iter)) { int index, found_user; user_t user = iterator_int64 (&users_iter, 0); found_user = 0; for (index = 0; index < affected_users->len && found_user == 0; index++) { if (g_array_index (affected_users, user_t, index) == user) { found_user = 1; break; } } if (found_user) { // users found here stay in the group -> no change in permissions g_array_remove_index_fast (affected_users, index); } else { // user added to group -> possible permissions change g_array_append_val (affected_users, user); } } cleanup_iterator (&users_iter); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); if (ret) sql_rollback (); else sql_commit (); return ret; } /* Permissions. */ /** * @brief Adjust location of resource in permissions. * * @param[in] type Type. * @param[in] old Resource ID in old table. * @param[in] new Resource ID in new table. * @param[in] to Destination, trash or table. */ void permissions_set_locations (const char *type, resource_t old, resource_t new, int to) { sql ("UPDATE permissions SET resource_location = %i, resource = %llu" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", to, new, type, old, to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE); sql ("UPDATE permissions_trash SET resource_location = %i, resource = %llu" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", to, new, type, old, to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE); } /** * @brief Set permissions to orphan. * * @param[in] type Type. * @param[in] resource Resource ID. * @param[in] location Location: table or trash. */ void permissions_set_orphans (const char *type, resource_t resource, int location) { sql ("UPDATE permissions SET resource = -1" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", type, resource, location); sql ("UPDATE permissions_trash SET resource = -1" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", type, resource, location); } /** * @brief Adjust subject in permissions. * * @param[in] type Subject type. * @param[in] old Resource ID in old table. * @param[in] new Resource ID in new table. * @param[in] to Destination, trash or table. */ static void permissions_set_subjects (const char *type, resource_t old, resource_t new, int to) { assert (type && (strcmp (type, "group") == 0 || strcmp (type, "role") == 0)); sql ("UPDATE permissions" " SET subject_location = %i, subject = %llu" " WHERE subject_location = %i" " AND subject_type = '%s'" " AND subject = %llu;", to, new, to == LOCATION_TRASH ? LOCATION_TABLE : LOCATION_TRASH, type, old); sql ("UPDATE permissions_trash" " SET subject_location = %i, subject = %llu" " WHERE subject_location = %i" " AND subject_type = '%s'" " AND subject = %llu;", to, new, to == LOCATION_TRASH ? LOCATION_TABLE : LOCATION_TRASH, type, old); } /** * @brief Find a permission given a UUID. * * @param[in] uuid UUID of permission. * @param[out] permission Permission return, 0 if successfully failed to find * permission. * * @return FALSE on success (including if failed to find permission), TRUE on * error. */ static gboolean find_permission (const char* uuid, permission_t* permission) { return find_resource ("permission", uuid, permission); } /** * @brief Check args for create_permission or modify_permission. * * @param[in] name_arg Name of permission. * @param[in] resource_type_arg Type of resource, for special permissions. * @param[in] resource_id_arg UUID of resource. * @param[in] subject_type Type of subject. * @param[in] subject_id UUID of subject. * @param[out] name Name return. * @param[out] resource Resource return. * @param[out] resource_type Resource type return. * @param[out] resource_id Resource ID return. * @param[out] subject Subject return. * * @return 0 success, 2 failed to find subject, 3 failed to find resource, * 5 error in resource, 6 error in subject, 7 error in name, * 8 permission on permission, 9 permission does not accept resource, * 99 permission denied, -1 error. */ static int check_permission_args (const char *name_arg, const char *resource_type_arg, const char *resource_id_arg, const char *subject_type, const char *subject_id, gchar **name, resource_t *resource, char **resource_type, const char **resource_id, resource_t *subject) { if ((name_arg == NULL) || ((valid_gmp_command (name_arg) == 0) && strcasecmp (name_arg, "super")) || (strcasecmp (name_arg, "get_version") == 0)) return 7; if (resource_id_arg && strcmp (resource_id_arg, "") && strcmp (resource_id_arg, "0") && (((gmp_command_takes_resource (name_arg) == 0) && strcasecmp (name_arg, "super")))) return 9; if (resource_type_arg && strcasecmp (name_arg, "super") == 0 && strcmp (resource_type_arg, "group") && strcmp (resource_type_arg, "role") && strcmp (resource_type_arg, "user")) return 5; if (resource_type_arg && strcasecmp (name_arg, "super") && (valid_db_resource_type (resource_type_arg) == 0 || gmp_command_takes_resource (name_arg) == 0)) return 5; if (subject_type && strcmp (subject_type, "group") && strcmp (subject_type, "role") && strcmp (subject_type, "user")) return 6; if (subject_id == NULL) /* For now a permission must have a subject. */ return 6; if (subject_id && (subject_type == NULL)) return 6; assert (subject_type); *name = strcasecmp (name_arg, "super") ? g_ascii_strdown (name_arg, -1) : g_strdup ("Super"); *resource = 0; if (resource_id_arg && strcmp (resource_id_arg, "") && strcmp (resource_id_arg, "0")) { *resource_type = strcasecmp (*name, "super") ? gmp_command_type (*name) : g_strdup (resource_type_arg); if (*resource_type == NULL) { g_free (*name); return 3; } if (strcasecmp (*resource_type, "asset") == 0) { g_free (*resource_type); *resource_type = g_strdup ("host"); if (find_resource (*resource_type, resource_id_arg, resource)) { g_free (*name); g_free (*resource_type); return -1; } if (*resource == 0) { g_free (*resource_type); *resource_type = g_strdup ("os"); if (find_resource (*resource_type, resource_id_arg, resource)) { g_free (*name); g_free (*resource_type); return -1; } } } else { gchar *get_permission; get_permission = g_strdup_printf ("get_%ss", *resource_type); if (find_resource_with_permission (*resource_type, resource_id_arg, resource, get_permission, 0)) { g_free (*name); g_free (*resource_type); g_free (get_permission); return -1; } g_free (get_permission); } if (*resource == 0) { g_free (*name); g_free (*resource_type); return 3; } *resource_id = resource_id_arg; } else { *resource_id = NULL; *resource_type = NULL; } if (strcasecmp (*name, "super") == 0) { if (*resource == 0) { g_free (*name); g_free (*resource_type); return 3; } if ((acl_user_is_owner (*resource_type, *resource_id) == 0) && (acl_user_can_super_everyone (current_credentials.uuid) == 0)) { g_free (*name); g_free (*resource_type); return 99; } } /* For simplicity refuse to make permissions on permissions. */ if (*resource && strcasestr (*name, "permission")) { g_free (*name); g_free (*resource_type); return 8; } /* Ensure the user may grant this permission. */ if (((*resource == 0) || strcasecmp (*name, "super") == 0) && (acl_user_can_everything (current_credentials.uuid) == 0)) { g_free (*name); g_free (*resource_type); return 99; } *subject = 0; assert (subject_id); if (*resource) { /* Permission on a particular resource. Only need read access to the * subject. */ if (find_resource_with_permission (subject_type, subject_id, subject, NULL, /* GET permission. */ 0)) /* Trash. */ { g_free (*name); g_free (*resource_type); return -1; } } else { gchar *permission; /* Command level permission. Must have write access to the subject. */ /* However, modification of the predefined roles is forbidden. */ if (subject_id && strcmp (subject_type, "role") == 0 && role_is_predefined_id (subject_id)) return 99; permission = g_strdup_printf ("modify_%s", subject_type); if (find_resource_with_permission (subject_type, subject_id, subject, permission, 0)) /* Trash. */ { g_free (*name); g_free (*resource_type); g_free (permission); return -1; } g_free (permission); } if (*subject == 0) { g_free (*name); g_free (*resource_type); return 2; } return 0; } /** * @brief Create a SQL clause to select the subject users. * * @param[in] subject_type Subject type. * @param[in] subject The subject. * * @return Newly allocated string containing the SQL clause. */ static gchar* subject_where_clause (const char* subject_type, resource_t subject) { gchar *subject_where = NULL; if (subject && subject_type) { if (strcmp (subject_type, "user") == 0) { subject_where = g_strdup_printf ("id = %llu", subject); } else if (strcmp (subject_type, "group") == 0) { subject_where = g_strdup_printf ("id IN (SELECT \"user\" FROM group_users" " WHERE \"group\" = %llu)", subject); } else if (strcmp (subject_type, "role") == 0) { subject_where = g_strdup_printf ("id IN (SELECT \"user\" FROM role_users" " WHERE \"role\" = %llu)", subject); } else { subject_where = strdup ("t()"); g_warning ("%s: unknown subject_type %s", __func__, subject_type); } } return subject_where; } /** * @brief Create a permission. * * Caller must organise the transaction. * * @param[in] check_access Whether to check if user may CREATE_PERMISSION. * @param[in] name_arg Name of permission. * @param[in] comment Comment on permission. * @param[in] resource_type_arg Type of resource, for special permissions. * @param[in] resource_id_arg UUID of resource. * @param[in] subject_type Type of subject. * @param[in] subject_id UUID of subject. * @param[out] permission Permission. * * @return 0 success, 2 failed to find subject, 3 failed to find resource, * 5 error in resource, 6 error in subject, 7 error in name, * 8 permission on permission, 9 permission does not accept resource, * 99 permission denied, -1 internal error. */ int create_permission_internal (int check_access, const char *name_arg, const char *comment, const char *resource_type_arg, const char *resource_id_arg, const char *subject_type, const char *subject_id, permission_t *permission) { int ret; gchar *name, *quoted_name, *quoted_comment, *resource_type; resource_t resource, subject; const char *resource_id; GHashTable *reports = NULL; int clear_original = 0; gchar *subject_where; assert (current_credentials.uuid); if (check_access && (acl_user_may ("create_permission") == 0)) return 99; ret = check_permission_args (name_arg, resource_type_arg, resource_id_arg, subject_type, subject_id, &name, &resource, &resource_type, &resource_id, &subject); if (ret) return ret; assert (subject); assert ((resource_id == resource_id_arg) || (resource_id == NULL)); quoted_name = sql_quote (name); g_free (name); quoted_comment = sql_quote (comment ? comment : ""); sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource_uuid, resource," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', '%s', '%s', '%s', %llu, " G_STRINGIFY (LOCATION_TABLE) "," " %s%s%s, %llu, " G_STRINGIFY (LOCATION_TABLE) ", m_now (), m_now ());", current_credentials.uuid, quoted_name, quoted_comment, resource_id ? resource_type : "", resource_id ? resource_id : "", resource, subject_id ? "'" : "", subject_id ? subject_type : "NULL", subject_id ? "'" : "", subject); subject_where = subject_where_clause (subject_type, subject); if (permission) *permission = sql_last_insert_id (); /* Update Permissions cache */ if (strcasecmp (quoted_name, "super") == 0) cache_all_permissions_for_users (NULL); else if (resource_type && resource) cache_permissions_for_resource (resource_type, resource, NULL); /* Update Reports cache */ if (resource_type && resource_id && strcmp (resource_type, "override") == 0) { reports = reports_for_override (resource); } else if (strcasecmp (quoted_name, "super") == 0) { reports = reports_hashtable (); clear_original = 1; } if (reports && g_hash_table_size (reports)) { GHashTableIter reports_iter; report_t *reports_ptr; int auto_cache_rebuild; reports_ptr = NULL; g_hash_table_iter_init (&reports_iter, reports); auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, clear_original, 1, subject_where); else report_clear_count_cache (*reports_ptr, clear_original, 1, subject_where); } } if (reports) g_hash_table_destroy (reports); g_free (quoted_comment); g_free (quoted_name); g_free (resource_type); g_free (subject_where); return 0; } /** * @brief Create a permission. * * @param[in] name_arg Name of permission. * @param[in] comment Comment on permission. * @param[in] resource_type_arg Type of resource, for special permissions. * @param[in] resource_id_arg UUID of resource. * @param[in] subject_type Type of subject. * @param[in] subject_id UUID of subject. * @param[out] permission Permission. * * @return 0 success, 2 failed to find subject, 3 failed to find resource, * 5 error in resource, 6 error in subject, 7 error in name, * 8 permission on permission, 9 permission does not accept resource, * 99 permission denied, -1 internal error. */ int create_permission (const char *name_arg, const char *comment, const char *resource_type_arg, const char *resource_id_arg, const char *subject_type, const char *subject_id, permission_t *permission) { int ret; sql_begin_immediate (); ret = create_permission_internal (1, name_arg, comment, resource_type_arg, resource_id_arg, subject_type, subject_id, permission); if (ret) sql_rollback (); else sql_commit (); return ret; } /** * @brief Create a permission. * * Does not require current user to have CREATE_PERMISSION access. * * @param[in] name_arg Name of permission. * @param[in] comment Comment on permission. * @param[in] resource_type_arg Type of resource, for special permissions. * @param[in] resource_id_arg UUID of resource. * @param[in] subject_type Type of subject. * @param[in] subject_id UUID of subject. * @param[out] permission Permission. * * @return 0 success, 2 failed to find subject, 3 failed to find resource, * 5 error in resource, 6 error in subject, 7 error in name, * 8 permission on permission, 9 permission does not accept resource, * 99 permission denied, -1 internal error. */ int create_permission_no_acl (const char *name_arg, const char *comment, const char *resource_type_arg, const char *resource_id_arg, const char *subject_type, const char *subject_id, permission_t *permission) { return create_permission_internal (0, name_arg, comment, resource_type_arg, resource_id_arg, subject_type, subject_id, permission); } /** * @brief Create a permission from an existing permission. * * @param[in] comment Comment on new permission. NULL to copy from existing. * @param[in] permission_id UUID of existing permission. * @param[out] new_permission New permission. * * @return 0 success, 1 permission exists already, 2 failed to find existing * permission, 99 permission denied, -1 error. */ int copy_permission (const char* comment, const char *permission_id, permission_t* new_permission) { int ret; permission_t permission, new, old; char *subject_type, *name; resource_t subject; sql_begin_immediate (); permission = 0; /* There are no permissions on permissions, so no need for the * "_with_permission" version. */ if (find_permission (permission_id, &permission)) { sql_rollback (); return -1; } if (permission == 0) { sql_rollback (); return 2; } /* Prevent copying of command level permissions for predefined roles. */ subject_type = permission_subject_type (permission); subject = permission_subject (permission); if (permission_resource (permission) == 0 && subject_type && strcmp (subject_type, "role") == 0 && subject && role_is_predefined (subject)) { free (subject_type); sql_rollback (); return 99; } free (subject_type); /* Refuse to copy Super On Everyone. */ name = permission_name (permission); if ((strcmp (name, "Super") == 0) && (permission_resource (permission) == 0)) { free (name); sql_rollback (); return 99; } free (name); ret = copy_resource_lock ("permission", NULL, comment, permission_id, "resource_type, resource, resource_uuid," " resource_location, subject_type, subject," " subject_location", 0, &new, &old); if (ret) { sql_rollback (); return ret; } sql_commit (); if (new_permission) *new_permission = new; return 0; } /** * @brief Return the UUID of a permission. * * @param[in] permission Permission. * * @return Newly allocated UUID if available, else NULL. */ char* permission_uuid (permission_t permission) { return sql_string ("SELECT uuid FROM permissions WHERE id = %llu;", permission); } /** * @brief Return the resource of a permission. * * @param[in] permission Permission. * * @return Resource if there is one, else 0. */ static resource_t permission_resource (permission_t permission) { resource_t resource; sql_int64 (&resource, "SELECT resource FROM permissions WHERE id = %llu;", permission); return resource; } /** * @brief Return the name of a permission. * * @param[in] permission Permission. * * @return Newly allocated name if available, else NULL. */ static char * permission_name (permission_t permission) { return sql_string ("SELECT name FROM permissions WHERE id = %llu;", permission); } /** * @brief Return the subject type of a permission. * * @param[in] permission Permission. * * @return Newly allocated subject type if available, else NULL. */ static char * permission_subject_type (permission_t permission) { return sql_string ("SELECT subject_type FROM permissions WHERE id = %llu;", permission); } /** * @brief Return the subject of a permission. * * @param[in] permission Permission. * * @return Subject if there is one, else 0. */ static resource_t permission_subject (permission_t permission) { resource_t subject; sql_int64 (&subject, "SELECT subject FROM permissions WHERE id = %llu;", permission); return subject; } /** * @brief Return the UUID of the subject of a permission. * * @param[in] permission Permission. * * @return Newly allocated subject ID if available, else NULL. */ static char * permission_subject_id (permission_t permission) { return sql_string ("SELECT subject_id FROM permissions WHERE id = %llu;", permission); } /** * @brief Return the resource type of a permission. * * @param[in] permission Permission. * * @return Newly allocated resource type if available, else NULL. */ static char * permission_resource_type (permission_t permission) { return sql_string ("SELECT resource_type FROM permissions WHERE id = %llu;", permission); } /** * @brief Return the UUID of the resource of a permission. * * @param[in] permission Permission. * * @return Newly allocated resource ID if available, else NULL. */ static char * permission_resource_id (permission_t permission) { return sql_string ("SELECT resource_id FROM permissions WHERE id = %llu;", permission); } /** * @brief Return whether a permission is predefined. * * @param[in] permission Permission. * * @return 1 if predefined, else 0. */ static int permission_is_predefined (permission_t permission) { return !!sql_int ("SELECT COUNT (*) FROM permissions" " WHERE id = %llu" " AND (uuid = '" PERMISSION_UUID_ADMIN_EVERYTHING "'" " OR (subject_type = 'role'" " AND resource = 0" " AND subject" " IN (SELECT id FROM roles" " WHERE uuid = '" ROLE_UUID_ADMIN "'" " OR uuid = '" ROLE_UUID_GUEST "'" " OR uuid = '" ROLE_UUID_INFO "'" " OR uuid = '" ROLE_UUID_MONITOR "'" " OR uuid = '" ROLE_UUID_USER "'" " OR uuid = '" ROLE_UUID_SUPER_ADMIN "'" " OR uuid = '" ROLE_UUID_OBSERVER "')))", permission); } /** * @brief Test whether a permission is the special Admin permission. * * @param[in] permission_id UUID of permission. * * @return 1 permission is Admin, else 0. */ int permission_is_admin (const char *permission_id) { if (permission_id) return strcmp (permission_id, PERMISSION_UUID_ADMIN_EVERYTHING); return 0; } /** * @brief Return whether a permission is in use. * * @param[in] permission Permission. * * @return 1 if in use, else 0. */ int permission_in_use (permission_t permission) { return 0; } /** * @brief Return whether a trashcan permission is referenced by a task. * * @param[in] permission Permission. * * @return 1 if in use, else 0. */ int trash_permission_in_use (permission_t permission) { return 0; } /** * @brief Return whether a permission is writable. * * @param[in] permission Permission. * * @return 1 if writable, else 0. */ int permission_writable (permission_t permission) { if (permission_is_predefined (permission)) return 0; return 1; } /** * @brief Return whether a trashcan permission is writable. * * @param[in] permission Permission. * * @return 1 if writable, else 0. */ int trash_permission_writable (permission_t permission) { return 1; } /** * @brief Filter columns for permission iterator. */ #define PERMISSION_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "type", "resource_uuid", "subject_type", \ "_subject", "_resource", "subject_uuid", "orphan", NULL } /** * @brief Permission iterator columns. */ #define PERMISSION_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (permissions), \ { "resource_type", "type", KEYWORD_TYPE_STRING }, \ { "resource_uuid", NULL, KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN resource_type = '' OR resource_type IS NULL" \ " THEN ''" \ " ELSE resource_name (resource_type, resource_uuid, resource_location)" \ " END)", \ "_resource", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ((resource_location = " G_STRINGIFY (LOCATION_TRASH) ")" \ " AS INTEGER)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { \ "(CASE" \ " WHEN resource = -1" \ " THEN 1" \ " ELSE 0" \ " END)", \ "orphan", \ KEYWORD_TYPE_INTEGER \ }, \ { "subject_type", NULL, KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN subject_type = 'user'" \ " THEN (SELECT uuid FROM users WHERE users.id = subject)" \ " WHEN subject_type = 'group'" \ " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT uuid FROM groups_trash" \ " WHERE groups_trash.id = subject)" \ " WHEN subject_type = 'group'" \ " THEN (SELECT uuid FROM groups WHERE groups.id = subject)" \ " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT uuid FROM roles_trash" \ " WHERE roles_trash.id = subject)" \ " ELSE (SELECT uuid FROM roles WHERE roles.id = subject)" \ " END)", \ "subject_uuid", \ KEYWORD_TYPE_STRING \ }, \ { \ "(CASE" \ " WHEN subject_type = 'user'" \ " THEN (SELECT name FROM users WHERE users.id = subject)" \ " WHEN subject_type = 'group'" \ " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT name FROM groups_trash" \ " WHERE groups_trash.id = subject)" \ " WHEN subject_type = 'group'" \ " THEN (SELECT name FROM groups WHERE groups.id = subject)" \ " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT name FROM roles_trash" \ " WHERE roles_trash.id = subject)" \ " ELSE (SELECT name FROM roles WHERE roles.id = subject)" \ " END)", \ "_subject", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ((subject_location = " G_STRINGIFY (LOCATION_TRASH) ")" \ " AS INTEGER)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Permission iterator columns for trash case. */ #define PERMISSION_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (permissions_trash), \ { "resource_type", "type", KEYWORD_TYPE_STRING }, \ { "resource_uuid", NULL, KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN resource_type = '' OR resource_type IS NULL" \ " THEN ''" \ " ELSE resource_name (resource_type, resource_uuid, resource_location)" \ " END)", \ "_resource", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ((resource_location = " G_STRINGIFY (LOCATION_TRASH) ")" \ " AS INTEGER)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { "resource = -1", NULL, KEYWORD_TYPE_INTEGER }, \ { "subject_type", NULL, KEYWORD_TYPE_STRING }, \ { \ "(CASE" \ " WHEN subject_type = 'user'" \ " THEN (SELECT uuid FROM users WHERE users.id = subject)" \ " WHEN subject_type = 'group'" \ " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT uuid FROM groups_trash" \ " WHERE groups_trash.id = subject)" \ " WHEN subject_type = 'group'" \ " THEN (SELECT uuid FROM groups WHERE groups.id = subject)" \ " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT uuid FROM roles_trash" \ " WHERE roles_trash.id = subject)" \ " ELSE (SELECT uuid FROM roles WHERE roles.id = subject)" \ " END)", \ "subject_uuid", \ KEYWORD_TYPE_STRING \ }, \ { \ "(CASE" \ " WHEN subject_type = 'user'" \ " THEN (SELECT name FROM users WHERE users.id = subject)" \ " WHEN subject_type = 'group'" \ " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT name FROM groups_trash" \ " WHERE groups_trash.id = subject)" \ " WHEN subject_type = 'group'" \ " THEN (SELECT name FROM groups WHERE groups.id = subject)" \ " WHEN subject_location = " G_STRINGIFY (LOCATION_TRASH) \ " THEN (SELECT name FROM roles_trash" \ " WHERE roles_trash.id = subject)" \ " ELSE (SELECT name FROM roles WHERE roles.id = subject)" \ " END)", \ "_subject", \ KEYWORD_TYPE_STRING \ }, \ { "CAST ((subject_location = " G_STRINGIFY (LOCATION_TRASH) ")" \ " AS INTEGER)", \ NULL, \ KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of permissions. * * @param[in] get GET params. * * @return Total number of permissions in filtered set. */ int permission_count (const get_data_t *get) { static const char *filter_columns[] = PERMISSION_ITERATOR_FILTER_COLUMNS; static column_t columns[] = PERMISSION_ITERATOR_COLUMNS; static column_t trash_columns[] = PERMISSION_ITERATOR_TRASH_COLUMNS; return count ("permission", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a permission iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find target, 2 failed to find filter, * -1 error. */ int init_permission_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = PERMISSION_ITERATOR_FILTER_COLUMNS; static column_t columns[] = PERMISSION_ITERATOR_COLUMNS; static column_t trash_columns[] = PERMISSION_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "permission", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the type of resource from a permission iterator. * * @param[in] iterator Iterator. * * @return Type, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_resource_type, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the UUID of the resource from a permission iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_resource_uuid, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the name of the resource from a permission iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_resource_name, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Return the permission resource location. * * @param[in] iterator Iterator. * * @return Whether the resource is in the trashcan */ int permission_iterator_resource_in_trash (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 3); } /** * @brief Check if the permission resource has been deleted. * * @param[in] iterator Iterator. * * @return Whether the resource has been deleted. */ int permission_iterator_resource_orphan (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the readable status of a resource from a permission iterator. * * @param[in] iterator Iterator. * * @return 1 if readable, otherwise 0. */ int permission_iterator_resource_readable (iterator_t* iterator) { resource_t found; const char *type, *uuid; gchar *permission; if (iterator->done) return 0; type = permission_iterator_resource_type (iterator); uuid = permission_iterator_resource_uuid (iterator); if (type == NULL || uuid == NULL) return 0; if (type_is_info_subtype (type)) permission = g_strdup ("get_info"); else if (type_is_asset_subtype (type)) permission = g_strdup ("get_assets"); else permission = g_strdup_printf ("get_%ss", type); found = 0; find_resource_with_permission (type, uuid, &found, permission, permission_iterator_resource_in_trash (iterator)); g_free (permission); return found > 0; } /** * @brief Get the type of subject from a permission iterator. * * @param[in] iterator Iterator. * * @return Type, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_subject_type, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the subject UUID from a permission iterator. * * @param[in] iterator Iterator. * * @return UUID, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_subject_uuid, GET_ITERATOR_COLUMN_COUNT + 6); /** * @brief Get the subject name from a permission iterator. * * @param[in] iterator Iterator. * * @return Name, or NULL if iteration is complete. */ DEF_ACCESS (permission_iterator_subject_name, GET_ITERATOR_COLUMN_COUNT + 7); /** * @brief Return the permission subject location. * * @param[in] iterator Iterator. * * @return Whether the subject is in the trashcan */ int permission_iterator_subject_in_trash (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 8); } /** * @brief Get the readable status of a subject from a permission iterator. * * @param[in] iterator Iterator. * * @return 1 if readable, otherwise 0. */ int permission_iterator_subject_readable (iterator_t* iterator) { resource_t found; const char *type, *uuid; gchar *permission; if (iterator->done) return 0; type = permission_iterator_subject_type (iterator); uuid = permission_iterator_subject_uuid (iterator); if (type == NULL || uuid == NULL) return 0; if ((strcmp (type, "user") == 0) || (strcmp (type, "role") == 0) || (strcmp (type, "group") == 0)) permission = g_strdup_printf ("get_%ss", type); else return 0; found = 0; find_resource_with_permission (type, uuid, &found, permission, permission_iterator_subject_in_trash (iterator)); g_free (permission); return found > 0; } /** * @brief Find a permission with a given permission, given a UUID. * * @param[in] uuid UUID of permission. * @param[out] resource Permission return, 0 if successfully failed to find * permission. * @param[in] permission Required permission, for example "delete". * * @return FALSE on success (including if failed to find permission), TRUE on * error. */ static gboolean find_permission_with_permission (const char *uuid, permission_t *resource, const char *permission) { gchar *quoted_uuid = sql_quote (uuid); if (acl_user_has_access_uuid ("permission", quoted_uuid, permission, 0) == 0) { g_free (quoted_uuid); *resource = 0; return FALSE; } switch (sql_int64 (resource, "SELECT id FROM permissions WHERE uuid = '%s';", quoted_uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ *resource = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return TRUE; break; } g_free (quoted_uuid); return FALSE; } /** * @brief Delete a permission. * * @param[in] permission_id UUID of permission. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 2 failed to find permission, 3 predefined permission, * 99 permission denied, -1 error. */ int delete_permission (const char *permission_id, int ultimate) { permission_t permission = 0; char *name, *subject_type, *resource_type; resource_t subject, resource; GHashTable *reports = NULL; int clear_original = 0; gchar *subject_where; if (strcasecmp (permission_id, PERMISSION_UUID_ADMIN_EVERYTHING) == 0) return 3; sql_begin_immediate (); if (acl_user_may ("delete_permission") == 0) { sql_rollback (); return 99; } if (find_permission_with_permission (permission_id, &permission, "delete_permission")) { sql_rollback (); return -1; } if (permission == 0) { if (find_trash ("permission", permission_id, &permission)) { sql_rollback (); return -1; } if (permission == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } tags_remove_resource ("permission", permission, LOCATION_TRASH); sql ("DELETE FROM permissions_trash WHERE id = %llu;", permission); sql_commit (); return 0; } /* Prevent deletion of command level permissions for predefined roles. */ subject_type = permission_subject_type (permission); subject = permission_subject (permission); resource = permission_resource (permission); if (resource == 0 && subject_type && strcmp (subject_type, "role") == 0 && subject && role_is_predefined (subject)) { free (subject_type); sql_rollback (); return 99; } subject_where = subject_where_clause (subject_type, subject); free (subject_type); if (ultimate == 0) { sql ("INSERT INTO permissions_trash" " (uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time)" " SELECT uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time" " FROM permissions" " WHERE id = %llu;", permission); tags_set_locations ("permission", permission, sql_last_insert_id (), LOCATION_TRASH); } else tags_remove_resource ("permission", permission, LOCATION_TABLE); name = permission_name (permission); resource_type = permission_resource_type (permission); sql ("DELETE FROM permissions WHERE id = %llu;", permission); /* Update Permissions cache */ if (strcasecmp (name, "super") == 0) cache_all_permissions_for_users (NULL); else if (resource_type && resource) cache_permissions_for_resource (resource_type, resource, NULL); /* Update Reports cache */ if (resource_type && (resource > 0) && strcmp (resource_type, "override") == 0) { reports = reports_for_override (resource); } else if (strcasecmp (name, "super") == 0) { reports = reports_hashtable (); clear_original = 1; } free (name); free (resource_type); if (reports && g_hash_table_size (reports)) { GHashTableIter reports_iter; report_t *reports_ptr; int auto_cache_rebuild; reports_ptr = NULL; g_hash_table_iter_init (&reports_iter, reports); auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, clear_original, 1, subject_where); else report_clear_count_cache (*reports_ptr, clear_original, 1, subject_where); } } g_free (subject_where); if (reports) g_hash_table_destroy (reports); sql_commit (); return 0; } /** * @brief Modify a permission. * * @param[in] permission_id UUID of permission. * @param[in] name_arg Name of permission. * @param[in] comment Comment on permission. * @param[in] resource_id_arg UUID of resource. * @param[in] resource_type_arg Type of resource, for Super permissions. * @param[in] subject_type Type of subject. * @param[in] subject_id UUID of subject. * * @return 0 success, 1 failed to find permission, 2 failed to find subject, * 3 failed to find resource, 4 permission_id required, 5 error in * resource, 6 error in subject, 7 error in name, 8 name required to * find resource, 9 permission does not accept resource, 99 permission * denied, -1 internal error. */ int modify_permission (const char *permission_id, const char *name_arg, const char *comment, const char *resource_id_arg, const char *resource_type_arg, const char *subject_type, const char *subject_id) { int ret; permission_t permission; resource_t resource, subject; char *existing_subject_type, *new_name, *new_resource_type; char *new_resource_id, *new_subject_type, *new_subject_id; gchar *name, *quoted_name, *resource_type; const char *resource_id; char *old_name, *old_resource_type; resource_t old_resource; GHashTable *reports = NULL; int clear_original = 0; gchar *subject_where_old, *subject_where_new, *subject_where; if (permission_id == NULL) return 4; sql_begin_immediate (); if (acl_user_may ("modify_permission") == 0) { sql_rollback (); return 99; } /* Find the permission. */ permission = 0; /* There are no permissions on permissions, so no need for the * "_with_permission" version. */ if (find_permission (permission_id, &permission)) { sql_rollback (); return -1; } if (permission == 0) { sql_rollback (); return 1; } /* Refuse to modify command-level permissions on predefined roles. */ existing_subject_type = permission_subject_type (permission); resource = permission_resource (permission); subject = permission_subject (permission); if (resource == 0 && existing_subject_type && strcmp (existing_subject_type, "role") == 0 && subject && role_is_predefined (subject)) { free (existing_subject_type); sql_rollback (); return 99; } /* Get old subject clause */ subject_where_old = subject_where_clause (existing_subject_type, subject); /* Set the comment first, to make things easier. */ if (comment) { gchar *quoted_comment; quoted_comment = sql_quote (comment); sql ("UPDATE permissions SET" " comment = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_comment, permission); g_free (quoted_comment); } /* Check the arguments. */ new_name = name_arg ? NULL : permission_name (permission); if (resource_type_arg && resource_id_arg && strcmp (resource_id_arg, "0")) /* Given a resource. */ new_resource_type = NULL; else if (resource_id_arg && (strcmp (resource_id_arg, "0") == 0)) /* User wants to clear the resource. */ new_resource_type = NULL; else { new_resource_type = permission_resource_type (permission); if (new_resource_type && strcmp (new_resource_type, "group") && strcmp (new_resource_type, "role") && strcmp (new_resource_type, "user")) /* Type will come from command name. */ new_resource_type = NULL; } new_resource_id = (resource_id_arg && strcmp (resource_id_arg, "")) ? NULL : permission_resource_id (permission); new_subject_type = subject_type ? NULL : existing_subject_type; new_subject_id = subject_id ? NULL : permission_subject_id (permission); ret = check_permission_args (new_name ? new_name : name_arg, new_resource_type ? new_resource_type : resource_type_arg, new_resource_id ? new_resource_id : resource_id_arg, new_subject_type ? new_subject_type : subject_type, new_subject_id ? new_subject_id : subject_id, &name, &resource, &resource_type, &resource_id, &subject); free (new_name); if (ret) { free (new_resource_type); free (new_resource_id); free (existing_subject_type); free (new_subject_id); g_free (subject_where_old); sql_rollback (); return ret; } subject_where_new = subject_where_clause (new_subject_type ? new_subject_type : subject_type, subject); if (strcmp (subject_where_new, subject_where_old)) { subject_where = g_strdup_printf ("(%s) OR (%s)", subject_where_new, subject_where_old); g_free (subject_where_new); g_free (subject_where_old); } else { subject_where = subject_where_old; g_free (subject_where_new); } /* Get old values and check if caches are affected by previous resource. */ old_name = permission_name (permission); old_resource_type = permission_resource_type (permission); old_resource = permission_resource (permission); if (old_resource && strcmp (old_resource_type, "override") == 0) { reports = reports_for_override (resource); } else if (strcasecmp (old_name, "super")) { reports = reports_hashtable (); clear_original = 1; } else { reports = new_resources_hashtable (); } /* Modify the permission. */ assert (subject); assert ((resource_id == new_resource_id) || (resource_id == resource_id_arg) || (resource_id == NULL)); quoted_name = sql_quote (name); sql ("UPDATE permissions SET" " name = '%s'," " resource_type = '%s'," " resource_uuid = '%s'," " resource = %llu," " resource_location = " G_STRINGIFY (LOCATION_TABLE) "," " subject_type = '%s'," " subject = %llu," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, (resource_id && resource_type) ? resource_type : "", resource_id ? resource_id : "", resource, new_subject_type ? new_subject_type : subject_type, subject, permission); /* Update permission caches according to the modifications. */ if (strcasecmp (name, "super") == 0 || strcasecmp (old_name, "super") == 0) cache_all_permissions_for_users (NULL); else { if (resource_type && resource_id && strcmp (resource_id, "")) cache_permissions_for_resource (resource_type, resource, NULL); if (old_resource && old_resource_type && ((resource != old_resource) || (resource_type && strcmp (old_resource_type, resource_type)))) cache_permissions_for_resource (old_resource_type, old_resource, NULL); } /* Check if caches are affected by the permission and update reports cache */ if (resource_type && resource && (resource != old_resource || strcmp (old_resource_type, "override")) && strcmp (resource_type, "override") == 0) { reports_add_for_override (reports, resource); } else if (strcasecmp (quoted_name, "super") == 0 && strcasecmp (old_name, quoted_name)) { reports_add_all (reports); clear_original = 1; } if (reports) { GHashTableIter reports_iter; report_t *reports_ptr; int auto_cache_rebuild; g_hash_table_iter_init (&reports_iter, reports); reports_ptr = NULL; auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, clear_original, 1, subject_where); else report_clear_count_cache (*reports_ptr, clear_original, 1, subject_where); } g_hash_table_destroy (reports); reports = NULL; } /* Cleanup. */ g_free (quoted_name); free (new_resource_type); free (new_resource_id); free (existing_subject_type); free (new_subject_id); g_free (name); free (old_name); free (old_resource_type); g_free (subject_where); sql_commit (); return 0; } /** * @brief Add role permissions to feed objects according to the * 'Feed Import Roles' setting. * * @param[in] type The object type, e.g. report_format. * @param[in] type_cap Capitalized type, e.g. "Report Format" * @param[out] permission_count Number of permissions added. * @param[out] object_count Number of data objects affected. */ static void add_feed_role_permissions (const char *type, const char *type_cap, int *permission_count, int *object_count) { char *roles_str; gchar **roles; iterator_t resources; roles_str = NULL; setting_value (SETTING_UUID_FEED_IMPORT_ROLES, &roles_str); if (roles_str == NULL || strlen (roles_str) == 0) { g_message ("%s: No feed import roles defined", __func__); g_free (roles_str); return; } roles = g_strsplit (roles_str, ",", 0); free (roles_str); init_iterator (&resources, "SELECT id, uuid, name, owner FROM %ss" " WHERE predefined = 1", type); while (next (&resources)) { gboolean added_permission = FALSE; resource_t permission_resource = iterator_int64 (&resources, 0); const char *permission_resource_id = iterator_string (&resources, 1); const char *permission_resource_name = iterator_string (&resources, 2); user_t owner = iterator_int64 (&resources, 3); gchar **role = roles; while (*role) { char *role_name = NULL; resource_name ("role", *role, LOCATION_TABLE, &role_name); if (sql_int ("SELECT count(*) FROM permissions" " WHERE name = 'get_%ss'" " AND subject_type = 'role'" " AND subject" " = (SELECT id FROM roles WHERE uuid='%s')" " AND resource = %llu", type, *role, permission_resource)) { g_debug ("Role %s (%s) already has read permission" " for %s %s (%s).", role_name, *role, type_cap, permission_resource_name, permission_resource_id); } else { gchar *permission_name; g_info ("Creating read permission for role %s (%s)" " on %s %s (%s).", role_name, *role, type_cap, permission_resource_name, permission_resource_id); added_permission = TRUE; if (permission_count) *permission_count = *permission_count + 1; permission_name = g_strdup_printf ("get_%ss", type); current_credentials.uuid = user_uuid (owner); switch (create_permission_internal (0, permission_name, "Automatically created by" " --optimize", type, permission_resource_id, "role", *role, NULL)) { case 0: // success break; case 2: g_warning ("%s: failed to find role %s for permission", __func__, *role); break; case 3: g_warning ("%s: failed to find %s %s for permission", __func__, type_cap, permission_resource_id); break; case 5: g_warning ("%s: error in resource when creating permission" " for %s %s", __func__, type_cap, permission_resource_id); break; case 6: g_warning ("%s: error in subject (Role %s)", __func__, *role); break; case 7: g_warning ("%s: error in name %s", __func__, permission_name); break; case 8: g_warning ("%s: permission on permission", __func__); break; case 9: g_warning ("%s: permission %s does not accept resource", __func__, permission_name); break; case 99: g_warning ("%s: permission denied to create %s permission" " for role %s on %s %s", __func__, permission_name, *role, type_cap, permission_resource_id); break; default: g_warning ("%s: internal error creating %s permission" " for role %s on %s %s", __func__, permission_name, *role, type_cap, permission_resource_id); break; } free (current_credentials.uuid); current_credentials.uuid = NULL; } free (role_name); role ++; } if (object_count && added_permission) *object_count = *object_count + 1; } cleanup_iterator (&resources); g_strfreev (roles); return; } /** * @brief Delete permissions to feed objects for roles that are not set * in the 'Feed Import Roles' setting. * * @param[in] type The object type, e.g. report_format. * @param[in] type_cap Capitalized type, e.g. "Report Format" * @param[out] permission_count Number of permissions added. * @param[out] object_count Number of data objects affected. */ static void clean_feed_role_permissions (const char *type, const char *type_cap, int *permission_count, int *object_count) { char *roles_str; gchar **roles, **role; GString *sql_roles; iterator_t resources; roles_str = NULL; setting_value (SETTING_UUID_FEED_IMPORT_ROLES, &roles_str); if (roles_str == NULL || strlen (roles_str) == 0) { g_message ("%s: No feed import roles defined", __func__); g_free (roles_str); return; } sql_roles = g_string_new ("("); if (roles_str) { roles = g_strsplit (roles_str, ",", 0); role = roles; while (*role) { gchar *quoted_role = sql_insert (*role); g_string_append (sql_roles, quoted_role); role ++; if (*role) g_string_append (sql_roles, ", "); } } g_string_append (sql_roles, ")"); g_debug ("%s: Keeping permissions for roles %s\n", __func__, sql_roles->str); init_iterator (&resources, "SELECT id, uuid, name FROM %ss" " WHERE predefined = 1", type); while (next (&resources)) { gboolean removed_permission = FALSE; resource_t permission_resource = iterator_int64 (&resources, 0); const char *permission_resource_id = iterator_string (&resources, 1); const char *permission_resource_name = iterator_string (&resources, 2); iterator_t permissions; roles = NULL; init_iterator (&permissions, "DELETE FROM permissions" " WHERE name = 'get_%ss'" " AND resource = %llu" " AND subject_type = 'role'" " AND subject NOT IN" " (SELECT id FROM roles WHERE uuid IN %s)" " RETURNING" " (SELECT uuid FROM roles WHERE id = subject)," " (SELECT name FROM roles WHERE id = subject)", type, permission_resource, sql_roles->str); while (next (&permissions)) { const char *role_id = iterator_string (&permissions, 0); const char *role_name = iterator_string (&permissions, 1); g_info ("Removed permission on %s %s (%s) for role %s (%s)", type_cap, permission_resource_name, permission_resource_id, role_name, role_id); if (permission_count) *permission_count = *permission_count + 1; removed_permission = TRUE; } if (object_count && removed_permission) *object_count = *object_count + 1; } cleanup_iterator (&resources); g_strfreev (roles); return; } /* Roles. */ /** * @brief List roles. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] verbose Whether to print UUID. * * @return 0 success, -1 error. */ int manage_get_roles (GSList *log_config, const db_conn_info_t *database, int verbose) { iterator_t roles; int ret; g_info (" Getting roles."); ret = manage_option_setup (log_config, database); if (ret) return ret; init_iterator (&roles, "SELECT name, uuid FROM roles;"); while (next (&roles)) if (verbose) printf ("%s %s\n", iterator_string (&roles, 0), iterator_string (&roles, 1)); else printf ("%s\n", iterator_string (&roles, 0)); cleanup_iterator (&roles); manage_option_cleanup (); return 0; } /** * @brief Create a role from an existing role. * * @param[in] name Name of new role. NULL to copy from existing. * @param[in] comment Comment on new role. NULL to copy from existing. * @param[in] role_id UUID of existing role. * @param[out] new_role_return New role. * * @return 0 success, 1 role exists already, 2 failed to find existing * role, 99 permission denied, -1 error. */ int copy_role (const char *name, const char *comment, const char *role_id, role_t *new_role_return) { int ret; role_t new_role, old_role; sql_begin_immediate (); if (acl_user_may ("create_role") == 0) return 99; if (acl_role_can_super_everyone (role_id)) return 99; ret = copy_resource_lock ("role", name, comment, role_id, NULL, 1, &new_role, &old_role); if (ret) { sql_rollback (); return ret; } sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource_uuid, resource," " resource_location, subject_type, subject, subject_location," " creation_time, modification_time)" " SELECT make_uuid ()," " (SELECT id FROM users WHERE users.uuid = '%s')," " name, comment, resource_type," " resource_uuid, resource, resource_location, subject_type, %llu," " subject_location, m_now (), m_now ()" " FROM permissions" " WHERE subject_type = 'role'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) " AND (resource = 0 OR owner IS NULL);", current_credentials.uuid, new_role, old_role); sql_commit (); if (new_role_return) *new_role_return = new_role; return 0; } /** * @brief Create a role. * * @param[in] role_name Role name. * @param[in] comment Comment on role. * @param[in] users Users role applies to. * @param[in] role Role return. * * @return 0 success, 1 role exists already, 2 failed to find user, 4 user * name validation failed, 99 permission denied, -1 error. */ int create_role (const char *role_name, const char *comment, const char *users, role_t* role) { int ret; gchar *quoted_role_name, *quoted_comment; assert (current_credentials.uuid); assert (role_name); assert (role); sql_begin_immediate (); if (acl_user_may ("create_role") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (role_name, "role", 0)) { sql_rollback (); return 1; } quoted_role_name = sql_quote (role_name); quoted_comment = comment ? sql_quote (comment) : g_strdup (""); sql ("INSERT INTO roles" " (uuid, name, owner, comment, creation_time, modification_time)" " VALUES" " (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', m_now (), m_now ());", quoted_role_name, current_credentials.uuid, quoted_comment); g_free (quoted_comment); g_free (quoted_role_name); *role = sql_last_insert_id (); ret = add_users ("role", *role, users); if (ret) sql_rollback (); else sql_commit (); return ret; } /** * @brief Return whether a role is predefined. * * @param[in] role Role. * * @return 1 if predefined, else 0. */ static int role_is_predefined (role_t role) { return sql_int ("SELECT COUNT (*) FROM roles" " WHERE id = %llu" " AND (uuid = '" ROLE_UUID_ADMIN "'" " OR uuid = '" ROLE_UUID_GUEST "'" " OR uuid = '" ROLE_UUID_MONITOR "'" " OR uuid = '" ROLE_UUID_INFO "'" " OR uuid = '" ROLE_UUID_USER "'" " OR uuid = '" ROLE_UUID_SUPER_ADMIN "'" " OR uuid = '" ROLE_UUID_OBSERVER "');", role) != 0; } /** * @brief Return whether a role is predefined. * * @param[in] uuid UUID of role. * * @return 1 if predefined, else 0. */ static int role_is_predefined_id (const char *uuid) { return uuid && ((strcmp (uuid, ROLE_UUID_ADMIN) == 0) || (strcmp (uuid, ROLE_UUID_GUEST) == 0) || (strcmp (uuid, ROLE_UUID_MONITOR) == 0) || (strcmp (uuid, ROLE_UUID_INFO) == 0) || (strcmp (uuid, ROLE_UUID_USER) == 0) || (strcmp (uuid, ROLE_UUID_SUPER_ADMIN) == 0) || (strcmp (uuid, ROLE_UUID_OBSERVER) == 0)); } /** * @brief Delete a role. * * @param[in] role_id UUID of role. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a task refers to the role, 2 failed * to find role, 3 predefined role, -1 error. */ int delete_role (const char *role_id, int ultimate) { role_t role = 0; GArray *affected_users; iterator_t users_iter; sql_begin_immediate (); if (acl_user_may ("delete_role") == 0) { sql_rollback (); return 99; } if (find_role_with_permission (role_id, &role, "delete_role")) { sql_rollback (); return -1; } if (role == 0) { if (find_trash ("role", role_id, &role)) { sql_rollback (); return -1; } if (role == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } if (trash_role_in_use (role)) { sql_rollback (); return 1; } sql ("DELETE FROM permissions" " WHERE resource_type = 'role'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); sql ("DELETE FROM permissions_trash" " WHERE resource_type = 'role'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); sql ("DELETE FROM permissions" " WHERE subject_type = 'role'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); sql ("DELETE FROM permissions_trash" " WHERE subject_type = 'role'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); tags_remove_resource ("role", role, LOCATION_TRASH); sql ("DELETE FROM role_users_trash WHERE role = %llu;", role); sql ("DELETE FROM roles_trash WHERE id = %llu;", role); sql_commit (); return 0; } if (role_is_predefined (role)) { sql_rollback (); return 3; } if (role_in_use (role)) { sql_rollback (); return 1; } if (ultimate == 0) { role_t trash_role; sql ("INSERT INTO roles_trash" " (uuid, owner, name, comment, creation_time, modification_time)" " SELECT uuid, owner, name, comment, creation_time," " modification_time" " FROM roles WHERE id = %llu;", role); trash_role = sql_last_insert_id (); sql ("INSERT INTO role_users_trash" " (\"role\", \"user\")" " SELECT %llu, \"user\"" " FROM role_users WHERE \"role\" = %llu;", trash_role, role); permissions_set_locations ("role", role, trash_role, LOCATION_TRASH); tags_set_locations ("role", role, trash_role, LOCATION_TRASH); permissions_set_subjects ("role", role, trash_role, LOCATION_TRASH); } else { sql ("DELETE FROM permissions" " WHERE resource_type = 'role'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); sql ("DELETE FROM permissions_trash" " WHERE resource_type = 'role'" " AND resource = %llu" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", role); sql ("DELETE FROM permissions" " WHERE subject_type = 'role'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", role); sql ("DELETE FROM permissions_trash" " WHERE subject_type = 'role'" " AND subject = %llu" " AND subject_location = " G_STRINGIFY (LOCATION_TABLE) ";", role); tags_remove_resource ("role", role, LOCATION_TABLE); } affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM role_users" " WHERE \"role\" = %llu", role); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role); sql ("DELETE FROM roles WHERE id = %llu;", role); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); sql_commit (); return 0; } /** * @brief Find a role for a specific permission, given a UUID. * * @param[in] uuid UUID of role. * @param[out] role Role return, 0 if successfully failed to find role. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find role), TRUE on error. */ static gboolean find_role_with_permission (const char* uuid, role_t* role, const char *permission) { return find_resource_with_permission ("role", uuid, role, permission, 0); } /** * @brief Find a role given a name. * * @param[in] name A role name. * @param[out] role Role return, 0 if successfully failed to find role. * * @return FALSE on success (including if failed to find role), TRUE on error. */ static gboolean find_role_by_name (const char* name, role_t *role) { return find_resource_by_name ("role", name, role); } /** * @brief Gets UUID of role. * * @param[in] role Role. * * @return Users. */ gchar * role_uuid (role_t role) { return sql_string ("SELECT uuid FROM roles WHERE id = %llu;", role); } /** * @brief Gets users of role as a string. * * @param[in] role Role. * * @return Users. */ gchar * role_users (role_t role) { return sql_string ("SELECT group_concat (name, ', ')" " FROM (SELECT users.name FROM users, role_users" " WHERE role_users.role = %llu" " AND role_users.user = users.id" " GROUP BY users.name)" " AS sub;", role); } /** * @brief Check whether a role is writable. * * @param[in] role Role. * * @return 1 yes, 0 no. */ int role_writable (role_t role) { if (role_is_predefined (role)) return 0; return 1; } /** * @brief Check whether a trashcan role is writable. * * @param[in] role Role. * * @return 1 yes, 0 no. */ int trash_role_writable (role_t role) { return 1; } /** * @brief Check whether a role is in use. * * @param[in] role Role. * * @return 1 yes, 0 no. */ int role_in_use (role_t role) { return 0; } /** * @brief Check whether a trashcan role is in use. * * @param[in] role Role. * * @return 1 yes, 0 no. */ int trash_role_in_use (role_t role) { return 0; } /** * @brief Modify a role. * * @param[in] role_id UUID of role. * @param[in] name Name of role. * @param[in] comment Comment on role. * @param[in] users Role users. * * @return 0 success, 1 failed to find role, 2 failed to find user, 3 role_id * required, 4 user name validation failed, 5 role with new name * exists, 99 permission denied, -1 internal error. */ int modify_role (const char *role_id, const char *name, const char *comment, const char *users) { int ret; gchar *quoted_name, *quoted_comment; role_t role; GArray *affected_users; iterator_t users_iter; assert (current_credentials.uuid); if (role_id == NULL) return 3; sql_begin_immediate (); if (acl_user_may ("modify_role") == 0) { sql_rollback (); return 99; } role = 0; if (find_role_with_permission (role_id, &role, "modify_role")) { sql_rollback (); return -1; } if (role == 0) { sql_rollback (); return 1; } /* Check whether a role with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "role", role)) { sql_rollback (); return 5; } } quoted_name = sql_quote (name ?: ""); quoted_comment = sql_quote (comment ?: ""); sql ("UPDATE roles SET" " name = '%s'," " comment = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, quoted_comment, role); g_free (quoted_comment); g_free (quoted_name); affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM role_users" " WHERE \"role\" = %llu", role); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); sql ("DELETE FROM role_users WHERE \"role\" = %llu;", role); ret = add_users ("role", role, users); init_iterator (&users_iter, "SELECT \"user\" FROM role_users" " WHERE \"role\" = %llu", role); // users not looked for in this loop were removed // -> possible permissions change while (next (&users_iter)) { int index, found_user; user_t user = iterator_int64 (&users_iter, 0); found_user = 0; for (index = 0; index < affected_users->len && found_user == 0; index++) { if (g_array_index (affected_users, user_t, index) == user) { found_user = 1; break; } } if (found_user) { // users found here stay in the role -> no change in permissions g_array_remove_index_fast (affected_users, index); } else { // user added to role -> possible permissions change g_array_append_val (affected_users, user); } } cleanup_iterator (&users_iter); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); if (ret) sql_rollback (); else sql_commit (); return ret; } /** * @brief Filter columns for role iterator. */ #define ROLE_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, NULL } /** * @brief Role iterator columns. */ #define ROLE_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (roles), \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Role iterator columns for trash case. */ #define ROLE_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (roles_trash), \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of roles. * * @param[in] get GET params. * * @return Total number of roles in roleed set. */ int role_count (const get_data_t *get) { static const char *extra_columns[] = ROLE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = ROLE_ITERATOR_COLUMNS; static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS; return count ("role", get, columns, trash_columns, extra_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a role iterator, including observed roles. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find role, 2 failed to find role (filt_id), * -1 error. */ int init_role_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = ROLE_ITERATOR_FILTER_COLUMNS; static column_t columns[] = ROLE_ITERATOR_COLUMNS; static column_t trash_columns[] = ROLE_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "role", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /* Filters. */ /** * @brief Find a filter for a specific permission, given a UUID. * * @param[in] uuid UUID of filter. * @param[out] filter Filter return, 0 if successfully failed to find * filter. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find filter), TRUE on error. */ gboolean find_filter_with_permission (const char* uuid, filter_t* filter, const char *permission) { return find_resource_with_permission ("filter", uuid, filter, permission, 0); } /** * @brief Return the UUID of a filter. * * @param[in] filter Filter. * * @return Newly allocated UUID if available, else NULL. */ char* filter_uuid (filter_t filter) { return sql_string ("SELECT uuid FROM filters WHERE id = %llu;", filter); } /** * @brief Return the UUID of a trashcan filter. * * @param[in] filter Filter. * * @return Newly allocated UUID if available, else NULL. */ static char* trash_filter_uuid (filter_t filter) { return sql_string ("SELECT uuid FROM filters_trash WHERE id = %llu;", filter); } /** * @brief Return the name of a filter. * * @param[in] filter Filter. * * @return name of filter. */ char* filter_name (filter_t filter) { return sql_string ("SELECT name FROM filters WHERE id = %llu;", filter); } /** * @brief Return the name of a trashcan filter. * * @param[in] filter Filter. * * @return name of filter. */ static char* trash_filter_name (filter_t filter) { return sql_string ("SELECT name FROM filters_trash WHERE id = %llu;", filter); } /** * @brief Return the term of a filter. * * @param[in] uuid Filter UUID. * * @return Newly allocated term if available, else NULL. */ gchar* filter_term (const char *uuid) { gchar *quoted_uuid, *ret; quoted_uuid = sql_quote (uuid); ret = sql_string ("SELECT term FROM filters WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return ret; } /** * @brief Return the value of a column keyword of a filter term. * * @param[in] term Filter term. * @param[in] column Column name. * * @return Value of column keyword if one exists, else NULL. */ gchar* filter_term_value (const char *term, const char *column) { keyword_t **point; array_t *split; if (term == NULL) return NULL; split = split_filter (term); point = (keyword_t**) split->pdata; while (*point) { keyword_t *keyword; keyword = *point; if (keyword->column && ((strcasecmp (keyword->column, column) == 0) || (keyword->column[0] == '_' && (strcasecmp (keyword->column + 1, column) == 0)))) { gchar *ret = g_strdup (keyword->string); filter_free (split); return ret; } point++; } filter_free (split); return NULL; } /** * @brief Return the value of the apply_overrides keyword of a filter term. * * @param[in] term Filter term. * * @return Value of apply_overrides if it exists, else APPLY_OVERRIDES_DEFAULT. */ int filter_term_apply_overrides (const char *term) { if (term) { int ret; gchar *apply_overrides_str; apply_overrides_str = filter_term_value (term, "apply_overrides"); ret = apply_overrides_str ? (strcmp (apply_overrides_str, "0") ? 1 : 0) : APPLY_OVERRIDES_DEFAULT; g_free (apply_overrides_str); return ret; } else return APPLY_OVERRIDES_DEFAULT; } /** * @brief Return the value of the min_qod keyword of a filter term. * * @param[in] term Filter term. * * @return Value of min_qod if it exists, else MIN_QOD_DEFAULT. */ int filter_term_min_qod (const char *term) { if (term) { int ret; gchar *min_qod_str; min_qod_str = filter_term_value (term, "min_qod"); ret = (min_qod_str && strcmp (min_qod_str, "")) ? atoi (min_qod_str) : MIN_QOD_DEFAULT; g_free (min_qod_str); return ret; } else return MIN_QOD_DEFAULT; } /** * @brief Create a filter. * * @param[in] name Name of filter. * @param[in] comment Comment on filter. * @param[in] type Type of resource. * @param[in] term Filter term. * @param[out] filter Created filter. * * @return 0 success, 1 filter exists already, 2 error in type, 99 permission * denied. */ int create_filter (const char *name, const char *comment, const char *type, const char *term, filter_t* filter) { gchar *quoted_name, *quoted_comment, *quoted_term, *clean_term; assert (current_credentials.uuid); if (type && strlen (type)) { type = type_db_name (type); if (type == NULL || !valid_type (type)) return 2; } sql_begin_immediate (); if (acl_user_may ("create_filter") == 0) { sql_rollback (); return 99; } if (resource_with_name_exists (name, "filter", 0)) { sql_rollback (); return 1; } quoted_name = sql_quote (name ?: ""); clean_term = manage_clean_filter (term ? term : ""); quoted_term = sql_quote (clean_term); g_free (clean_term); if (comment) { quoted_comment = sql_quote (comment); sql ("INSERT INTO filters" " (uuid, name, owner, comment, type, term, creation_time," " modification_time)" " VALUES (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '%s', %s%s%s, '%s', m_now (), m_now ());", quoted_name, current_credentials.uuid, quoted_comment, type ? "lower ('" : "", type ? type : "''", type ? "')" : "", quoted_term); g_free (quoted_comment); } else sql ("INSERT INTO filters" " (uuid, name, owner, comment, type, term, creation_time," " modification_time)" " VALUES (make_uuid (), '%s'," " (SELECT id FROM users WHERE users.uuid = '%s')," " '', %s%s%s, '%s', m_now (), m_now ());", quoted_name, current_credentials.uuid, type ? "lower ('" : "", type ? type : "''", type ? "')" : "", quoted_term); if (filter) *filter = sql_last_insert_id (); g_free (quoted_name); g_free (quoted_term); sql_commit (); return 0; } /** * @brief Create a filter from an existing filter. * * @param[in] name Name of new filter. NULL to copy from existing. * @param[in] comment Comment on new filter. NULL to copy from existing. * @param[in] filter_id UUID of existing filter. * @param[out] new_filter New filter. * * @return 0 success, 1 filter exists already, 2 failed to find existing * filter, -1 error. */ int copy_filter (const char* name, const char* comment, const char *filter_id, filter_t* new_filter) { return copy_resource ("filter", name, comment, filter_id, "term, type", 1, new_filter, NULL); } /** * @brief Delete a filter. * * @param[in] filter_id UUID of filter. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 1 fail because a task refers to the filter, 2 failed * to find filter, 99 permission denied, -1 error. */ int delete_filter (const char *filter_id, int ultimate) { gchar *quoted_filter_id; filter_t filter = 0; sql_begin_immediate (); if (acl_user_may ("delete_filter") == 0) { sql_rollback (); return 99; } if (find_filter_with_permission (filter_id, &filter, "delete_filter")) { sql_rollback (); return -1; } if (filter == 0) { if (find_trash ("filter", filter_id, &filter)) { sql_rollback (); return -1; } if (filter == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } /* Check if it's in use by an alert in the trashcan. */ if (sql_int ("SELECT count(*) FROM alerts_trash" " WHERE filter = %llu" " AND filter_location = " G_STRINGIFY (LOCATION_TRASH) ";", filter)) { sql_rollback (); return 1; } /* Check if it's in use by the condition of an alert in the trashcan. */ if (sql_int ("SELECT count(*) FROM alert_condition_data_trash" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters_trash" " WHERE id = %llu)" " AND (SELECT condition = %i OR condition = %i" " FROM alerts_trash WHERE id = alert);", filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED)) { sql_rollback (); return 1; } permissions_set_orphans ("filter", filter, LOCATION_TRASH); tags_remove_resource ("filter", filter, LOCATION_TRASH); sql ("DELETE FROM filters_trash WHERE id = %llu;", filter); sql_commit (); return 0; } /* Check if it's in use by an alert. */ if (sql_int ("SELECT count(*) FROM alerts" " WHERE filter = %llu;", filter)) { sql_rollback (); return 1; } /* Check if it's in use by the condition of an alert. */ if (sql_int ("SELECT count(*) FROM alert_condition_data" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND (SELECT condition = %i OR condition = %i" " FROM alerts WHERE id = alert);", filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED)) { sql_rollback (); return 1; } if (ultimate) { /* Check if it's in use by the condition of an alert in the trashcan. */ if (sql_int ("SELECT count(*) FROM alert_condition_data_trash" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND (SELECT condition = %i OR condition = %i" " FROM alerts_trash WHERE id = alert);", filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED)) { sql_rollback (); return 1; } } quoted_filter_id = sql_quote (filter_id); sql ("DELETE FROM settings WHERE name %s '%% Filter' AND value = '%s';", sql_ilike_op (), quoted_filter_id); g_free (quoted_filter_id); if (ultimate == 0) { sql ("INSERT INTO filters_trash" " (uuid, owner, name, comment, type, term, creation_time," " modification_time)" " SELECT uuid, owner, name, comment, type, term, creation_time," " modification_time" " FROM filters WHERE id = %llu;", filter); /* Update the location of the filter in any trashcan alerts. */ sql ("UPDATE alerts_trash" " SET filter = %llu," " filter_location = " G_STRINGIFY (LOCATION_TRASH) " WHERE filter = %llu" " AND filter_location = " G_STRINGIFY (LOCATION_TABLE) ";", sql_last_insert_id (), filter); permissions_set_locations ("filter", filter, sql_last_insert_id (), LOCATION_TRASH); tags_set_locations ("filter", filter, sql_last_insert_id (), LOCATION_TRASH); } else { permissions_set_orphans ("filter", filter, LOCATION_TABLE); tags_remove_resource ("filter", filter, LOCATION_TABLE); } sql ("DELETE FROM filters WHERE id = %llu;", filter); sql_commit (); return 0; } /** * @brief Check whether a filter is in use. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ int filter_in_use (filter_t filter) { return !!sql_int ("SELECT count (*) FROM alerts" /* Filter applied to results passed to alert's "generate". */ " WHERE filter = %llu" /* Filter applied to check alert condition. */ " OR (EXISTS (SELECT * FROM alert_condition_data" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND alert = alerts.id)" " AND (condition = %i OR condition = %i))", filter, filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED); } /** * @brief Check whether a filter is in use for the output of any alert. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ static int filter_in_use_for_output (filter_t filter) { return !!sql_int ("SELECT count (*) FROM alerts" " WHERE filter = %llu;", filter); } /** * @brief Check whether a filter is in use by any result alert conditions. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ static int filter_in_use_for_result_event (filter_t filter) { return !!sql_int ("SELECT count (*) FROM alerts" " WHERE event = %llu" " AND (EXISTS (SELECT * FROM alert_condition_data" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND alert = alerts.id)" " AND (condition = %i OR condition = %i))", EVENT_TASK_RUN_STATUS_CHANGED, filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED); } /** * @brief Check whether a filter is in use by any secinfo alert conditions. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ static int filter_in_use_for_secinfo_event (filter_t filter) { return !!sql_int ("SELECT count (*) FROM alerts" " WHERE (event = %llu OR event = %llu)" " AND (EXISTS (SELECT * FROM alert_condition_data" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND alert = alerts.id)" " AND (condition = %i OR condition = %i))", EVENT_NEW_SECINFO, EVENT_UPDATED_SECINFO, filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED); } /** * @brief Check whether a trashcan filter is in use. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ int trash_filter_in_use (filter_t filter) { return !!sql_int ("SELECT count (*) FROM alerts_trash" " WHERE (filter = %llu" " AND filter_location = " G_STRINGIFY (LOCATION_TRASH) ")" " OR (EXISTS (SELECT *" " FROM alert_condition_data_trash" " WHERE name = 'filter_id'" " AND data = (SELECT uuid" " FROM filters_trash" " WHERE id = %llu)" " AND alert = alerts_trash.id)" " AND (condition = %i OR condition = %i))", filter, filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED); } /** * @brief Check whether a filter is writable. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ int filter_writable (filter_t filter) { return 1; } /** * @brief Check whether a trashcan filter is writable. * * @param[in] filter Filter. * * @return 1 yes, 0 no. */ int trash_filter_writable (filter_t filter) { return 1; } /** * @brief Filter columns for filter iterator. */ #define FILTER_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "type", "term", NULL } /** * @brief Filter iterator columns. */ #define FILTER_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (filters), \ { "type" , NULL, KEYWORD_TYPE_STRING }, \ { "term", NULL, KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Filter iterator columns for trash case. */ #define FILTER_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (filters_trash), \ { "type" , NULL, KEYWORD_TYPE_STRING }, \ { "term", NULL, KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of filters. * * @param[in] get GET params. * * @return Total number of filters in filtered set. */ int filter_count (const get_data_t *get) { static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = FILTER_ITERATOR_COLUMNS; static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS; return count ("filter", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a filter iterator, including observed filters. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find filter, 2 failed to find filter (filt_id), * -1 error. */ int init_filter_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = FILTER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = FILTER_ITERATOR_COLUMNS; static column_t trash_columns[] = FILTER_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "filter", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the type from a filter iterator. * * @param[in] iterator Iterator. * * @return The type of the filter, or NULL if iteration is complete. Freed by * cleanup_iterator. "" for any type. */ const char* filter_iterator_type (iterator_t* iterator) { const char *ret; if (iterator->done) return NULL; ret = iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT); return ret ? ret : ""; } /** * @brief Get the term from a filter iterator. * * @param[in] iterator Iterator. * * @return The term of the filter, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (filter_iterator_term, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Initialise a filter alert iterator. * * Iterates over all alerts that use the filter. * * @param[in] iterator Iterator. * @param[in] filter Filter. */ void init_filter_alert_iterator (iterator_t* iterator, filter_t filter) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (filter); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_alerts")); available = acl_where_owned ("alert", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT name, uuid, %s FROM alerts" " WHERE filter = %llu" " OR (EXISTS (SELECT * FROM alert_condition_data" " WHERE name = 'filter_id'" " AND data = (SELECT uuid FROM filters" " WHERE id = %llu)" " AND alert = alerts.id)" " AND (condition = %i OR condition = %i))" " ORDER BY name ASC;", with_clause ? with_clause : "", available, filter, filter, ALERT_CONDITION_FILTER_COUNT_AT_LEAST, ALERT_CONDITION_FILTER_COUNT_CHANGED); g_free (with_clause); g_free (available); } /** * @brief Get the name from a filter_alert iterator. * * @param[in] iterator Iterator. * * @return The name of the host, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (filter_alert_iterator_name, 0); /** * @brief Get the UUID from a filter_alert iterator. * * @param[in] iterator Iterator. * * @return The UUID of the host, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (filter_alert_iterator_uuid, 1); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int filter_alert_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 2); } /** * @brief Modify a filter. * * @param[in] filter_id UUID of filter. * @param[in] name Name of filter. * @param[in] comment Comment on filter. * @param[in] term Filter term. * @param[in] type Type of filter. * * @return 0 success, 1 failed to find filter, 2 filter with new name exists, * 3 error in type name, 4 filter_id required, 5 filter is in use so * type must be "result", 6 filter is in use so type must be "info", * 99 permission denied, -1 internal error. */ int modify_filter (const char *filter_id, const char *name, const char *comment, const char *term, const char *type) { gchar *quoted_name, *quoted_comment, *quoted_term, *quoted_type, *clean_term; filter_t filter; if (filter_id == NULL) return 4; type = type_db_name (type); if (type && !((strcmp (type, "") == 0) || valid_type (type))) return 3; sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_filter") == 0) { sql_rollback (); return 99; } filter = 0; if (find_filter_with_permission (filter_id, &filter, "modify_filter")) { sql_rollback (); return -1; } if (filter == 0) { sql_rollback (); return 1; } /* If the filter is linked to an alert, check that the type is valid. */ if ((filter_in_use_for_output (filter) || filter_in_use_for_result_event (filter)) && type && strcasecmp (type, "result")) { sql_rollback (); return 5; } if (filter_in_use_for_secinfo_event (filter) && type && strcasecmp (type, "info")) { sql_rollback (); return 6; } /* Check whether a filter with the same name exists already. */ if (name) { if (resource_with_name_exists (name, "filter", filter)) { sql_rollback (); return 2; } } quoted_name = sql_quote(name ?: ""); clean_term = manage_clean_filter (term ? term : ""); quoted_term = sql_quote (clean_term); g_free (clean_term); quoted_comment = sql_quote (comment ? comment : ""); quoted_type = sql_quote (type ? type : ""); sql ("UPDATE filters SET" " name = '%s'," " comment = '%s'," " term = '%s'," " type = lower ('%s')," " modification_time = m_now ()" " WHERE id = %llu;", quoted_name, quoted_comment, quoted_term, quoted_type, filter); g_free (quoted_comment); g_free (quoted_name); g_free (quoted_term); g_free (quoted_type); sql_commit (); return 0; } /* Schema. */ /** * @brief Generate the GMP schema. * * @param[in] format Name of schema format, "XML" or NULL for XML. * @param[out] output_return NULL or location for output. * @param[out] output_length NULL or location for length of output. * @param[out] extension NULL or location for schema extension. * @param[out] content_type NULL or location for schema content type. * * @return 0 success, 1 failed to find schema format, -1 error. */ int manage_schema (gchar *format, gchar **output_return, gsize *output_length, gchar **extension, gchar **content_type) { /* Pass the XML file to the schema generate script, sending the output * to a file. */ { gchar *script, *script_dir; gchar *uuid_format; char output_dir[] = "/tmp/gvmd_schema_XXXXXX"; if (mkdtemp (output_dir) == NULL) { g_warning ("%s: mkdtemp failed", __func__); return -1; } /* Setup file names. */ if (format == NULL) { if (extension) *extension = g_strdup ("xml"); if (content_type) *content_type = g_strdup ("text/xml"); uuid_format = "18e826fc-dab6-11df-b913-002264764cea"; } else if (strcasecmp (format, "HTML") == 0) { if (extension) *extension = g_strdup ("html"); if (content_type) *content_type = g_strdup ("text/html"); uuid_format = "02052818-dab6-11df-9be4-002264764cea"; } else if (strcasecmp (format, "RNC") == 0) { if (extension) *extension = g_strdup ("rnc"); if (content_type) *content_type = g_strdup ("text/x-rnc"); uuid_format = "787a4a18-dabc-11df-9486-002264764cea"; } else if (strcasecmp (format, "XML") == 0) { if (extension) *extension = g_strdup ("xml"); if (content_type) *content_type = g_strdup ("text/xml"); uuid_format = "18e826fc-dab6-11df-b913-002264764cea"; } else return 1; script_dir = g_build_filename (GVMD_DATA_DIR, "global_schema_formats", uuid_format, NULL); script = g_build_filename (script_dir, "generate", NULL); if (!gvm_file_is_readable (script)) { g_free (script); g_free (script_dir); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } { gchar *output_file, *command; char *previous_dir; int ret; /* Change into the script directory. */ previous_dir = getcwd (NULL, 0); if (previous_dir == NULL) { g_warning ("%s: Failed to getcwd: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (script); g_free (script_dir); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } if (chdir (script_dir)) { g_warning ("%s: Failed to chdir: %s", __func__, strerror (errno)); g_free (previous_dir); g_free (script); g_free (script_dir); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } g_free (script_dir); output_file = g_strdup_printf ("%s/report.out", output_dir); /* Call the script. */ command = g_strdup_printf ("%s " GVMD_DATA_DIR "/global_schema_formats" "/18e826fc-dab6-11df-b913-002264764cea/GMP.xml" " > %s" " 2> /dev/null", script, output_file); g_free (script); g_debug (" command: %s", command); ret = system (command); if ((ret == -1) /* The schema "generate" script must exit with 0. */ || WEXITSTATUS (ret)) { g_warning ("%s: system failed with ret %i, %i, %s", __func__, ret, WEXITSTATUS (ret), command); if (chdir (previous_dir)) g_warning ("%s: and chdir failed", __func__); g_free (previous_dir); g_free (command); g_free (output_file); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } { GError *get_error; gchar *output; gsize output_len; g_free (command); /* Change back to the previous directory. */ if (chdir (previous_dir)) { g_warning ("%s: Failed to chdir back: %s", __func__, strerror (errno)); g_free (previous_dir); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } g_free (previous_dir); /* Read the script output from file. */ get_error = NULL; g_file_get_contents (output_file, &output, &output_len, &get_error); g_free (output_file); if (get_error) { g_warning ("%s: Failed to get output: %s", __func__, get_error->message); g_error_free (get_error); if (extension) g_free (*extension); if (content_type) g_free (*content_type); return -1; } /* Remove the output directory. */ gvm_file_remove_recurse (output_dir); /* Return the output. */ if (output_length) *output_length = output_len; if (output_return) *output_return = output; return 0; } } } } /* Trashcan. */ /** * @brief Restore a resource from the trashcan. * * @param[in] id UUID of resource. * * @return 0 success, 1 fail because the resource refers to another resource * in the trashcan, 2 failed to find resource in trashcan, 3 fail * because resource with such name exists already, 4 fail because * resource with UUID exists already, 99 permission denied, -1 error. */ int manage_restore (const char *id) { resource_t resource = 0; int ret; assert (current_credentials.uuid); sql_begin_immediate (); if (acl_user_may ("restore") == 0) { sql_rollback (); return 99; } /* Port List. */ ret = restore_port_list (id); if (ret != 2) return ret; /* Report Format. */ ret = restore_report_format (id); if (ret != 2) return ret; /* Ticket. */ ret = restore_ticket (id); if (ret != 2) return ret; /* Config. */ if (find_trash ("config", id, &resource)) { sql_rollback (); return -1; } if (resource) { config_t config; if (sql_int ("SELECT count(*) FROM configs" " WHERE name =" " (SELECT name FROM configs_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } /* Check if it uses a scanner in the trashcan. */ if (sql_int ("SELECT scanner_location = " G_STRINGIFY (LOCATION_TRASH) " FROM configs_trash WHERE id = %llu;", resource)) { sql_rollback (); return 1; } sql ("INSERT INTO configs" " (uuid, owner, name, nvt_selector, comment, family_count," " nvt_count, families_growing, nvts_growing, type, scanner," " predefined, creation_time, modification_time, usage_type)" " SELECT uuid, owner, name, nvt_selector, comment, family_count," " nvt_count, families_growing, nvts_growing, type, scanner," " predefined, creation_time, modification_time, usage_type" " FROM configs_trash WHERE id = %llu;", resource); config = sql_last_insert_id (); sql ("INSERT INTO config_preferences" " (config, type, name, value, default_value, hr_name)" " SELECT %llu, type, name, value, default_value, hr_name" " FROM config_preferences_trash WHERE config = %llu;", config, resource); /* Update the config in any trashcan tasks. */ sql ("UPDATE tasks" " SET config = %llu," " config_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE config = %llu" " AND config_location = " G_STRINGIFY (LOCATION_TRASH), config, resource); permissions_set_locations ("config", resource, config, LOCATION_TABLE); tags_set_locations ("config", resource, config, LOCATION_TABLE); sql ("DELETE FROM config_preferences_trash WHERE config = %llu;", resource); sql ("DELETE FROM configs_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Alert. */ if (find_trash ("alert", id, &resource)) { sql_rollback (); return -1; } if (resource) { alert_t alert; if (sql_int ("SELECT count(*) FROM alerts" " WHERE name =" " (SELECT name FROM alerts_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } /* Check if it uses a filter in the trashcan. */ if (sql_int ("SELECT filter_location = " G_STRINGIFY (LOCATION_TRASH) " FROM alerts_trash WHERE id = %llu;", resource)) { sql_rollback (); return 1; } sql ("INSERT INTO alerts" " (uuid, owner, name, comment, event, condition, method, filter," " active, creation_time, modification_time)" " SELECT uuid, owner, name, comment, event, condition, method," " filter, active, creation_time, modification_time" " FROM alerts_trash WHERE id = %llu;", resource); alert = sql_last_insert_id (); sql ("INSERT INTO alert_condition_data" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_condition_data_trash WHERE alert = %llu;", alert, resource); sql ("INSERT INTO alert_event_data" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_event_data_trash WHERE alert = %llu;", alert, resource); sql ("INSERT INTO alert_method_data" " (alert, name, data)" " SELECT %llu, name, data" " FROM alert_method_data_trash WHERE alert = %llu;", alert, resource); /* Update the alert in any trashcan tasks. */ sql ("UPDATE task_alerts" " SET alert = %llu," " alert_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE alert = %llu" " AND alert_location = " G_STRINGIFY (LOCATION_TRASH), alert, resource); permissions_set_locations ("alert", resource, alert, LOCATION_TABLE); tags_set_locations ("alert", resource, alert, LOCATION_TABLE); sql ("DELETE FROM alert_condition_data_trash WHERE alert = %llu;", resource); sql ("DELETE FROM alert_event_data_trash WHERE alert = %llu;", resource); sql ("DELETE FROM alert_method_data_trash WHERE alert = %llu;", resource); sql ("DELETE FROM alerts_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Filter. */ if (find_trash ("filter", id, &resource)) { sql_rollback (); return -1; } if (resource) { if (sql_int ("SELECT count(*) FROM filters" " WHERE name =" " (SELECT name FROM filters_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } sql ("INSERT INTO filters" " (uuid, owner, name, comment, type, term, creation_time," " modification_time)" " SELECT uuid, owner, name, comment, type, term, creation_time," " modification_time" " FROM filters_trash WHERE id = %llu;", resource); /* Update the filter in any trashcan alerts. */ sql ("UPDATE alerts_trash" " SET filter = %llu," " filter_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE filter = %llu" " AND filter_location = " G_STRINGIFY (LOCATION_TRASH), sql_last_insert_id (), resource); permissions_set_locations ("filter", resource, sql_last_insert_id (), LOCATION_TABLE); tags_set_locations ("filter", resource, sql_last_insert_id (), LOCATION_TABLE); sql ("DELETE FROM filters_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Group. */ if (find_trash ("group", id, &resource)) { sql_rollback (); return -1; } if (resource) { group_t group; GArray *affected_users; iterator_t users_iter; if (sql_int ("SELECT count(*) FROM groups" " WHERE name =" " (SELECT name FROM groups_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } sql ("INSERT INTO groups" " (uuid, owner, name, comment, creation_time," " modification_time)" " SELECT uuid, owner, name, comment, creation_time," " modification_time" " FROM groups_trash WHERE id = %llu;", resource); group = sql_last_insert_id (); sql ("INSERT INTO group_users" " (\"group\", \"user\")" " SELECT %llu, \"user\"" " FROM group_users_trash WHERE \"group\" = %llu;", group, resource); permissions_set_locations ("group", resource, group, LOCATION_TABLE); tags_set_locations ("group", resource, group, LOCATION_TABLE); permissions_set_subjects ("group", resource, group, LOCATION_TABLE); sql ("DELETE FROM group_users_trash WHERE \"group\" = %llu;", resource); sql ("DELETE FROM groups_trash WHERE id = %llu;", resource); affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM group_users" " WHERE \"group\" = %llu", group); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); sql_commit (); return 0; } /* Credential. */ if (find_trash ("credential", id, &resource)) { sql_rollback (); return -1; } if (resource) { credential_t credential; if (sql_int ("SELECT count(*) FROM credentials" " WHERE name =" " (SELECT name FROM credentials_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } sql ("INSERT INTO credentials" " (uuid, owner, name, comment, creation_time," " modification_time, type)" " SELECT uuid, owner, name, comment, creation_time," " modification_time, type" " FROM credentials_trash WHERE id = %llu;", resource); credential = sql_last_insert_id (); sql ("INSERT INTO credentials_data" " (credential, type, value)" " SELECT %llu, type, value" " FROM credentials_trash_data" " WHERE credential = %llu", credential, resource); /* Update the credentials in any trashcan targets. */ sql ("UPDATE targets_trash_login_data" " SET credential_location = " G_STRINGIFY (LOCATION_TABLE) "," " credential = %llu" " WHERE credential = %llu" " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) ";", credential, resource); sql ("UPDATE scanners_trash" " SET credential_location = " G_STRINGIFY (LOCATION_TABLE) "," " credential = %llu" " WHERE credential = %llu" " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) ";", credential, resource); permissions_set_locations ("credential", resource, credential, LOCATION_TABLE); tags_set_locations ("credential", resource, credential, LOCATION_TABLE); sql ("DELETE FROM credentials_trash_data WHERE credential = %llu;", resource); sql ("DELETE FROM credentials_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Note. */ if (find_trash ("note", id, &resource)) { sql_rollback (); return -1; } if (resource) { sql ("INSERT INTO notes" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, task, result, end_time)" " SELECT uuid, owner, nvt, creation_time, modification_time, text," " hosts, port, severity, task, result, end_time" " FROM notes_trash WHERE id = %llu;", resource); permissions_set_locations ("note", resource, sql_last_insert_id (), LOCATION_TABLE); tags_set_locations ("note", resource, sql_last_insert_id (), LOCATION_TABLE); sql ("DELETE FROM notes_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Override. */ if (find_trash ("override", id, &resource)) { sql_rollback (); return -1; } if (resource) { override_t override; GHashTable *reports; GHashTableIter reports_iter; report_t *reports_ptr; gchar *users_where; int auto_cache_rebuild; sql ("INSERT INTO overrides" " (uuid, owner, nvt, creation_time, modification_time, text, hosts," " port, severity, new_severity, task, result, end_time, result_nvt)" " SELECT uuid, owner, nvt, creation_time, modification_time, text," " hosts, port, severity, new_severity, task," " result, end_time, result_nvt" " FROM overrides_trash WHERE id = %llu;", resource); override = sql_last_insert_id (); permissions_set_locations ("override", resource, override, LOCATION_TABLE); tags_set_locations ("override", resource, override, LOCATION_TABLE); users_where = acl_users_with_access_where ("override", id, NULL, "id"); reports = reports_for_override (override); g_hash_table_iter_init (&reports_iter, reports); reports_ptr = NULL; auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, 0, 1, users_where); else report_clear_count_cache (*reports_ptr, 0, 1, users_where); } g_hash_table_destroy (reports); g_free (users_where); sql ("DELETE FROM overrides_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Permission. */ if (find_trash ("permission", id, &resource)) { sql_rollback (); return -1; } if (resource) { permission_t permission; char *name, *resource_type; resource_t perm_resource; GHashTable *reports = NULL; int clear_original = 0; char *subject_type; resource_t subject; gchar *subject_where; sql ("INSERT INTO permissions" " (uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time)" " SELECT uuid, owner, name, comment, resource_type, resource," " resource_uuid, resource_location, subject_type, subject," " subject_location, creation_time, modification_time" " FROM permissions_trash" " WHERE id = %llu;", resource); permission = sql_last_insert_id (); name = permission_name (permission); resource_type = permission_resource_type (permission); perm_resource = permission_resource (permission); subject_type = permission_subject_type (permission); subject = permission_subject (permission); subject_where = subject_where_clause (subject_type, subject); free (subject_type); tags_set_locations ("permission", resource, permission, LOCATION_TABLE); if (strcasecmp (name, "super") == 0) cache_all_permissions_for_users (NULL); else if (perm_resource != 0 && resource_type && strcmp (resource_type, "")) cache_permissions_for_resource (resource_type, perm_resource, NULL); /* Update Reports cache */ if (resource_type && perm_resource && strcmp (resource_type, "override") == 0) { reports = reports_for_override (perm_resource); } else if (strcasecmp (name, "super") == 0) { reports = reports_hashtable (); clear_original = 1; } if (reports && g_hash_table_size (reports)) { GHashTableIter reports_iter; report_t *reports_ptr; int auto_cache_rebuild; reports_ptr = NULL; g_hash_table_iter_init (&reports_iter, reports); auto_cache_rebuild = setting_auto_cache_rebuild_int (); while (g_hash_table_iter_next (&reports_iter, ((gpointer*)&reports_ptr), NULL)) { if (auto_cache_rebuild) report_cache_counts (*reports_ptr, clear_original, 1, subject_where); else report_clear_count_cache (*reports_ptr, clear_original, 1, subject_where); } } g_free (subject_where); if (reports) g_hash_table_destroy (reports); free (name); free (resource_type); sql ("DELETE FROM permissions_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Role. */ if (find_trash ("role", id, &resource)) { sql_rollback (); return -1; } if (resource) { role_t role; GArray *affected_users; iterator_t users_iter; if (sql_int ("SELECT count(*) FROM roles" " WHERE name =" " (SELECT name FROM roles_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } sql ("INSERT INTO roles" " (uuid, owner, name, comment, creation_time," " modification_time)" " SELECT uuid, owner, name, comment, creation_time," " modification_time" " FROM roles_trash WHERE id = %llu;", resource); role = sql_last_insert_id (); sql ("INSERT INTO role_users" " (\"role\", \"user\")" " SELECT %llu, \"user\"" " FROM role_users_trash WHERE role = %llu;", role, resource); permissions_set_locations ("role", resource, role, LOCATION_TABLE); tags_set_locations ("role", resource, role, LOCATION_TABLE); permissions_set_subjects ("role", resource, role, LOCATION_TABLE); sql ("DELETE FROM role_users_trash WHERE role = %llu;", resource); sql ("DELETE FROM roles_trash WHERE id = %llu;", resource); affected_users = g_array_new (TRUE, TRUE, sizeof (user_t)); init_iterator (&users_iter, "SELECT \"user\" FROM role_users" " WHERE \"role\" = %llu", role); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (affected_users, user); } cleanup_iterator (&users_iter); cache_all_permissions_for_users (affected_users); g_array_free (affected_users, TRUE); sql_commit (); return 0; } /* Scanner. */ if (find_trash ("scanner", id, &resource)) { sql_rollback (); return -1; } if (resource) { scanner_t scanner; if (sql_int ("SELECT count(*) FROM scanners" " WHERE name =" " (SELECT name FROM scanners_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } if (sql_int ("SELECT credential_location = " G_STRINGIFY (LOCATION_TRASH) " FROM scanners_trash WHERE id = %llu;", resource)) { sql_rollback (); return 1; } sql ("INSERT INTO scanners" " (uuid, owner, name, comment, host, port, type, ca_pub," " credential, creation_time, modification_time)" " SELECT uuid, owner, name, comment, host, port, type, ca_pub," " credential, creation_time, modification_time" " FROM scanners_trash WHERE id = %llu;", resource); /* Update the scanner in any trashcan configs and tasks. */ scanner = sql_last_insert_id (); sql ("UPDATE configs_trash" " SET scanner = %llu," " scanner_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH), scanner, resource); sql ("UPDATE tasks" " SET scanner = %llu," " scanner_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE scanner = %llu" " AND scanner_location = " G_STRINGIFY (LOCATION_TRASH), scanner, resource); permissions_set_locations ("scanner", resource, sql_last_insert_id (), LOCATION_TABLE); tags_set_locations ("scanner", resource, sql_last_insert_id (), LOCATION_TABLE); sql ("DELETE FROM scanners_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Schedule. */ if (find_trash ("schedule", id, &resource)) { sql_rollback (); return -1; } if (resource) { if (sql_int ("SELECT count(*) FROM schedules" " WHERE name =" " (SELECT name FROM schedules_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } sql ("INSERT INTO schedules" " (uuid, owner, name, comment, first_time, period, period_months," " byday, duration, timezone, creation_time," " modification_time, icalendar)" " SELECT uuid, owner, name, comment, first_time, period," " period_months, byday, duration, timezone," " creation_time, modification_time, icalendar" " FROM schedules_trash WHERE id = %llu;", resource); /* Update the schedule in any trashcan tasks. */ sql ("UPDATE tasks" " SET schedule = %llu," " schedule_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE schedule = %llu" " AND schedule_location = " G_STRINGIFY (LOCATION_TRASH), sql_last_insert_id (), resource); permissions_set_locations ("schedule", resource, sql_last_insert_id (), LOCATION_TABLE); tags_set_locations ("schedule", resource, sql_last_insert_id (), LOCATION_TABLE); sql ("DELETE FROM schedules_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Tag */ if (find_trash ("tag", id, &resource)) { sql_rollback (); return -1; } if (resource) { tag_t restored_tag; sql ("INSERT INTO tags" " (uuid, owner, name, comment, creation_time," " modification_time, resource_type, active, value)" " SELECT uuid, owner, name, comment, creation_time," " modification_time, resource_type, active, value" " FROM tags_trash WHERE id = %llu;", resource); restored_tag = sql_last_insert_id (); sql ("INSERT INTO tag_resources" " (tag, resource_type, resource, resource_uuid, resource_location)" " SELECT" " %llu, resource_type, resource, resource_uuid, resource_location" " FROM tag_resources_trash WHERE tag = %llu;", restored_tag, resource); sql ("DELETE FROM tag_resources_trash WHERE tag = %llu", resource); permissions_set_locations ("tag", resource, restored_tag, LOCATION_TABLE); sql ("DELETE FROM tags_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Target. */ if (find_trash ("target", id, &resource)) { sql_rollback (); return -1; } if (resource) { target_t restored_target; if (sql_int ("SELECT count(*) FROM targets" " WHERE name =" " (SELECT name FROM targets_trash WHERE id = %llu)" " AND " ACL_USER_OWNS () ";", resource, current_credentials.uuid)) { sql_rollback (); return 3; } /* Check if it uses a credential or port list in the trashcan. */ if (sql_int ("SELECT port_list_location = " G_STRINGIFY (LOCATION_TRASH) " OR EXISTS (" " SELECT *" " FROM targets_trash_login_data WHERE target = %llu" " AND credential_location = " G_STRINGIFY (LOCATION_TRASH) " )" " FROM targets_trash WHERE id = %llu;", resource, resource)) { sql_rollback (); return 1; } sql ("INSERT INTO targets" " (uuid, owner, name, hosts, exclude_hosts, comment," " port_list, reverse_lookup_only, reverse_lookup_unify," " alive_test, allow_simultaneous_ips," " creation_time, modification_time)" " SELECT uuid, owner, name, hosts, exclude_hosts, comment," " port_list, reverse_lookup_only, reverse_lookup_unify," " alive_test, allow_simultaneous_ips," " creation_time, modification_time" " FROM targets_trash WHERE id = %llu;", resource); restored_target = sql_last_insert_id (); /* Copy login data */ sql ("INSERT INTO targets_login_data" " (target, type, credential, port)" " SELECT %llu, type, credential, port" " FROM targets_trash_login_data WHERE target = %llu;", restored_target, resource); sql ("DELETE FROM targets_trash_login_data" " WHERE target = %llu;", resource); /* Update the target in any trashcan tasks. */ sql ("UPDATE tasks" " SET target = %llu," " target_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE target = %llu" " AND target_location = " G_STRINGIFY (LOCATION_TRASH), restored_target, resource); permissions_set_locations ("target", resource, sql_last_insert_id (), LOCATION_TABLE); tags_set_locations ("target", resource, sql_last_insert_id (), LOCATION_TABLE); sql ("DELETE FROM targets_trash WHERE id = %llu;", resource); sql_commit (); return 0; } /* Task. */ if (find_trash_task (id, &resource)) { sql_rollback (); return -1; } if (resource) { /* Check if it's in use by a resource in the trashcan. */ if (sql_int ("SELECT (target_location = " G_STRINGIFY (LOCATION_TRASH) ")" " OR (config_location = " G_STRINGIFY (LOCATION_TRASH) ")" " OR (schedule_location = " G_STRINGIFY (LOCATION_TRASH) ")" " OR (scanner_location = " G_STRINGIFY (LOCATION_TRASH) ")" " OR (SELECT count(*) > 0 FROM task_alerts" " WHERE task = tasks.id" " AND alert_location = " G_STRINGIFY (LOCATION_TRASH) ")" " FROM tasks WHERE id = %llu;", resource)) { sql_rollback (); return 1; } permissions_set_locations ("task", resource, resource, LOCATION_TABLE); tags_set_locations ("task", resource, resource, LOCATION_TABLE); sql ("UPDATE tag_resources SET resource_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE resource_type = 'report'" " AND resource IN (SELECT id FROM reports" " WHERE reports.task = %llu);", resource); sql ("UPDATE tag_resources SET resource_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE resource_type = 'result'" " AND resource IN (SELECT results.id FROM results" " WHERE results.task = %llu);", resource); sql ("UPDATE tag_resources_trash SET resource_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE resource_type = 'report'" " AND resource IN (SELECT id FROM reports" " WHERE reports.task = %llu);", resource); sql ("UPDATE tag_resources_trash SET resource_location = " G_STRINGIFY (LOCATION_TABLE) " WHERE resource_type = 'result'" " AND resource IN (SELECT results.id FROM results" " WHERE results.task = %llu);", resource); sql ("INSERT INTO results" " (uuid, task, host, port, nvt, result_nvt, type, description," " report, nvt_version, severity, qod, qod_type, owner, date," " hostname, path)" " SELECT uuid, task, host, port, nvt, result_nvt, type," " description, report, nvt_version, severity, qod," " qod_type, owner, date, hostname, path" " FROM results_trash" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", resource); tickets_restore_task (resource); sql ("DELETE FROM results_trash" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", resource); /* For safety, in case anything recounted this report while it was in * the trash (which would have resulted in the wrong counts, because * the counting does not know about results_trash). */ sql ("DELETE FROM report_counts" " WHERE report IN (SELECT id FROM reports WHERE task = %llu);", resource); sql ("UPDATE tasks SET hidden = 0 WHERE id = %llu;", resource); cache_permissions_for_resource ("task", resource, NULL); sql_commit (); return 0; } sql_rollback (); return 2; } /** * @brief Owner SQL for manage_empty_trash. */ #define WHERE_OWNER \ " WHERE owner = (SELECT id FROM users WHERE uuid = '%s')", \ current_credentials.uuid /** * @brief Empty the trashcan. * * @return 0 success, 99 permission denied, -1 error. */ int manage_empty_trashcan () { sql_begin_immediate (); if (acl_user_may ("empty_trashcan") == 0) { sql_rollback (); return 99; } sql ("DELETE FROM nvt_selectors" " WHERE name != '" MANAGE_NVT_SELECTOR_UUID_ALL "'" " AND name IN (SELECT nvt_selector FROM configs_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM config_preferences_trash" " WHERE config IN (SELECT id FROM configs_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM configs_trash" WHERE_OWNER); empty_trashcan_tickets (); sql ("DELETE FROM permissions" " WHERE subject_type = 'group'" " AND subject IN (SELECT id from groups_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", current_credentials.uuid); sql ("DELETE FROM group_users_trash" " WHERE \"group\" IN (SELECT id from groups_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM groups_trash" WHERE_OWNER); sql ("DELETE FROM alert_condition_data_trash" " WHERE alert IN (SELECT id from alerts_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM alert_event_data_trash" " WHERE alert IN (SELECT id from alerts_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM alert_method_data_trash" " WHERE alert IN (SELECT id from alerts_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM alerts_trash" WHERE_OWNER); sql ("DELETE FROM credentials_trash_data" " WHERE credential IN (SELECT id from credentials_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM credentials_trash" WHERE_OWNER); sql ("DELETE FROM filters_trash" WHERE_OWNER); sql ("DELETE FROM notes_trash" WHERE_OWNER); sql ("DELETE FROM overrides_trash" WHERE_OWNER); sql ("DELETE FROM permissions_trash" WHERE_OWNER); empty_trashcan_port_lists (); sql ("DELETE FROM permissions" " WHERE subject_type = 'role'" " AND subject IN (SELECT id from roles_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))" " AND subject_location = " G_STRINGIFY (LOCATION_TRASH) ";", current_credentials.uuid); sql ("DELETE FROM role_users_trash" " WHERE role IN (SELECT id from roles_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM roles_trash" WHERE_OWNER); sql ("DELETE FROM scanners_trash" WHERE_OWNER); sql ("DELETE FROM schedules_trash" WHERE_OWNER); // Remove resource data of all trash tags of the current user. sql ("DELETE FROM tag_resources_trash" " WHERE tag IN (SELECT id FROM tags_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM tags_trash" WHERE_OWNER); sql ("DELETE FROM targets_trash_login_data" " WHERE target IN (SELECT id from targets_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'));", current_credentials.uuid); sql ("DELETE FROM targets_trash" WHERE_OWNER); if (delete_trash_tasks ()) { sql_rollback (); return -1; } sql ("UPDATE permissions" " SET resource = -1" " WHERE resource > 0" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) " AND owner = (SELECT id FROM users WHERE uuid = '%s');", current_credentials.uuid); // Remove all trash resources from all tags of the current user. sql ("DELETE FROM tag_resources" " WHERE tag IN (SELECT id FROM tags_trash" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))" " AND resource_location = " G_STRINGIFY (LOCATION_TRASH) ";", current_credentials.uuid); /* Remove report formats last, because dir deletion can't be rolled back. */ if (empty_trashcan_report_formats ()) { sql_rollback (); return -1; } sql_commit (); return 0; } /* Assets. */ /** * \page asset_rules Ruleset for updating assets from scan detections * * During a scan various assets are identfied. The findings are by default * used to update the asset database. Since assets may already be present in * the database or even be present with contradictive properties, a ruleset * defines how the asset database is updated upon findings. * * Hosts * ----- * * When a host is detected, and there is at least one asset host that has the * same name and owner as the detected host, and whose identifiers all have * the same values as the identifiers of the detected host, then the most * recent such asset host is used. Otherwise a new asset host is created. * Either way the identifiers are added to the asset host. It does not matter * if the asset host has fewer identifiers than detected, as long as the * existing identifiers match. * * At the beginning of a scan, when a host is first detected, the decision * about which asset host to use is made by \ref host_notice. At the end * of the scan, if the host has identifiers, then this decision is revised * by \ref hosts_set_identifiers to take the identifiers into account. * * Host identifiers can be ip, hostname, MAC, OS or ssh-key. * * This documentation includes some pseudo-code and tabular definition. * Eventually one of them will repalce the other. * * Name : The assigned name (usually the IP) * IP : The detected IP * Hostname: The detected Hostname * OS: : The detected OS * * If IP And Not Hostname: * If Not Assets.Host(id=Name) And Not Assets.Host(attrib=IP, IP): * Assets.Host.New(id=Name, ip=IP) * If Assets.Host(id=Name) == 1: * Assets.Host.Add(id=Name, ip=IP) * * This pseudo-code is equivalent to the first two rows of: * * Detection | Asset State | Asset Update * ----------------------------- | --------------------------------------------------------------------------- | ----------------------------- * IP address X. | No host with Name=X or any ip=X. | Create host with Name=X and ip=X. * IP address X. | Host A with Name=X. | Add ip=X to host A. * IP address X. | (Host A with Name=X and ip=X) and (Host B with Name=X and ip=X). | Add ip=X to host (Newest(A,B)). * IP address X with Hostname Y. | Host A with Name=X and ip=X. | Add ip=X and hostname=Y to host A. * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y. | Add ip=X and hostname=Y to host A. * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname<>Y. | Create host with Name=X, ip=X and hostname=Y. * IP address X with Hostname Y. | Host A with Name=X and ip=X and hostname=Y and host B with Name=X and ip=X. | Add ip=X and hostname=Y to host (Newst(A,B)). * * Follow up action: If a MAC, OS or ssh-key was detected, then the respective * identifiers are added to the asset host selected during asset update. * * Operating Systems * ----------------- * * If OS: * If Not Assets.OS(id=OS): * Assets.OS.New(id=OS) * * This pseudo-code is equivalent to: * * Detection | Asset State | Asset Update * --------- | ------------------ | ------------------------ * OS X. | No OS with Name=X. | Create OS with Name=X. * OS X. | OS with Name=X. | No action. */ /** * @brief Return the UUID of the asset associated with a result host. * * @param[in] host Host value from result. * @param[in] result Result. * * @return Asset UUID. */ char * result_host_asset_id (const char *host, result_t result) { gchar *quoted_host; char *asset_id; quoted_host = sql_quote (host); asset_id = sql_string ("SELECT uuid FROM hosts" " WHERE id = (SELECT host FROM host_identifiers" " WHERE source_type = 'Report Host'" " AND name = 'ip'" " AND source_id" " = (SELECT uuid" " FROM reports" " WHERE id = (SELECT report" " FROM results" " WHERE id = %llu))" " AND value = '%s'" " LIMIT 1);", result, quoted_host); g_free (quoted_host); return asset_id; } /** * @brief Return the UUID of a host. * * @param[in] host Host. * * @return Host UUID. */ char* host_uuid (resource_t host) { return sql_string ("SELECT uuid FROM hosts WHERE id = %llu;", host); } /** * @brief Add a report host. * * @param[in] report UUID of resource. * @param[in] host Host. * @param[in] start Start time. * @param[in] end End time. * * @return Report host. */ report_host_t manage_report_host_add (report_t report, const char *host, time_t start, time_t end) { char *quoted_host = sql_quote (host); sql ("INSERT INTO report_hosts" " (report, host, start_time, end_time, current_port, max_port)" " SELECT %llu, '%s', %lld, %lld, 0, 0" " WHERE NOT EXISTS (SELECT 1 FROM report_hosts WHERE report = %llu" " AND host = '%s');", report, quoted_host, (long long) start, (long long) end, report, quoted_host); g_free (quoted_host); return sql_last_insert_id (); } /** * @brief Tests if a report host is marked as dead. * * @param[in] report_host Report host. * * @return 1 if the host is marked as dead, 0 otherwise. */ static int report_host_dead (report_host_t report_host) { return sql_int ("SELECT count(*) != 0 FROM report_host_details" " WHERE report_host = %llu" " AND name = 'Host dead'" " AND value != '0';", report_host); } /** * @brief Counts. * * @param[in] report_host Report host. * * @return 1 if the host is marked as dead, 0 otherwise. */ static int report_host_result_count (report_host_t report_host) { return sql_int ("SELECT count(*) FROM report_hosts, results" " WHERE report_hosts.id = %llu" " AND results.report = report_hosts.report" " AND report_hosts.host = results.host;", report_host); } /** * @brief Set end time of a report host. * * @param[in] report_host Report host. * @param[in] end_time End time. */ void report_host_set_end_time (report_host_t report_host, time_t end_time) { sql ("UPDATE report_hosts SET end_time = %lld WHERE id = %llu;", end_time, report_host); } /** * @brief Host identifiers for the current scan. */ array_t *identifiers = NULL; /** * @brief Unique hosts listed in host_identifiers. */ static array_t *identifier_hosts = NULL; /** * @brief Host identifier type. */ typedef struct { gchar *ip; ///< IP of host. gchar *name; ///< Name of identifier, like "hostname". gchar *value; ///< Value of identifier. gchar *source_type; ///< Type of identifier source, like "Report Host". gchar *source_id; ///< ID of source. gchar *source_data; ///< Extra data for source. } identifier_t; /** * @brief Free an identifier. * * @param[in] identifier Identifier. */ static void identifier_free (identifier_t *identifier) { if (identifier) { g_free (identifier->ip); g_free (identifier->name); g_free (identifier->value); g_free (identifier->source_type); g_free (identifier->source_id); g_free (identifier->source_data); } } /** * @brief Setup hosts and their identifiers after a scan, from host details. * * At the end of a scan this revises the decision about which asset host to use * for each host that has identifiers. The rules for this decision are described * in \ref asset_rules. (The initial decision is made by \ref host_notice.) * * @param[in] report Report that the identifiers come from. */ void hosts_set_identifiers (report_t report) { if (identifier_hosts) { int host_index, index; gchar *ip; array_terminate (identifiers); array_terminate (identifier_hosts); host_index = 0; while ((ip = (gchar*) g_ptr_array_index (identifier_hosts, host_index))) { host_t host, host_new; gchar *quoted_host_name; identifier_t *identifier; GString *select; if (report_host_noticeable (report, ip) == 0) continue; quoted_host_name = sql_quote (ip); select = g_string_new (""); /* Select the most recent host whose identifiers all match the given * identifiers, even if the host has fewer identifiers than given. */ g_string_append_printf (select, "SELECT id FROM hosts" " WHERE name = '%s'" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s')", quoted_host_name, current_credentials.uuid); index = 0; while ((identifier = (identifier_t*) g_ptr_array_index (identifiers, index))) { gchar *quoted_identifier_name, *quoted_identifier_value; if (strcmp (identifier->ip, ip)) { index++; continue; } quoted_identifier_name = sql_quote (identifier->name); quoted_identifier_value = sql_quote (identifier->value); g_string_append_printf (select, " AND (EXISTS (SELECT * FROM host_identifiers" " WHERE host = hosts.id" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND name = '%s'" " AND value = '%s')" " OR NOT EXISTS (SELECT * FROM host_identifiers" " WHERE host = hosts.id" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s')" " AND name = '%s'))", current_credentials.uuid, quoted_identifier_name, quoted_identifier_value, current_credentials.uuid, quoted_identifier_name); g_free (quoted_identifier_name); g_free (quoted_identifier_value); index++; } g_string_append_printf (select, " ORDER BY creation_time DESC LIMIT 1;"); switch (sql_int64 (&host, select->str)) { case 0: break; case 1: /* Too few rows in result of query. */ host = 0; break; default: /* Programming error. */ assert (0); case -1: host = 0; break; } g_string_free (select, TRUE); if (host == 0) { /* Add the host. */ sql ("INSERT into hosts" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', ''," " m_now (), m_now ());", current_credentials.uuid, quoted_host_name); host_new = host = sql_last_insert_id (); /* Make sure the Report Host identifiers added when the host was * first noticed now refer to the new host. */ sql ("UPDATE host_identifiers SET host = %llu" " WHERE source_id = (SELECT uuid FROM reports" " WHERE id = %llu)" " AND name = 'ip'" " AND value = '%s';", host_new, report, quoted_host_name); } else { /* Use the existing host. */ host_new = 0; } /* Add the host identifiers. */ index = 0; while ((identifier = (identifier_t*) g_ptr_array_index (identifiers, index))) { gchar *quoted_identifier_name, *quoted_identifier_value; gchar *quoted_source_id, *quoted_source_type, *quoted_source_data; if (strcmp (identifier->ip, ip)) { index++; continue; } quoted_identifier_name = sql_quote (identifier->name); quoted_identifier_value = sql_quote (identifier->value); quoted_source_id = sql_quote (identifier->source_id); quoted_source_data = sql_quote (identifier->source_data); quoted_source_type = sql_quote (identifier->source_type); if (strcmp (identifier->name, "OS") == 0) { resource_t os; switch (sql_int64 (&os, "SELECT id FROM oss" " WHERE name = '%s'" " AND owner = (SELECT id FROM users" " WHERE uuid = '%s');", quoted_identifier_value, current_credentials.uuid)) { case 0: break; default: /* Programming error. */ assert (0); case -1: case 1: /* Too few rows in result of query. */ sql ("INSERT into oss" " (uuid, owner, name, comment, creation_time," " modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE uuid = '%s')," " '%s', '', m_now (), m_now ());", current_credentials.uuid, quoted_identifier_value); os = sql_last_insert_id (); break; } sql ("INSERT into host_oss" " (uuid, host, owner, name, comment, os, source_type," " source_id, source_data, creation_time, modification_time)" " VALUES" " (make_uuid (), %llu," " (SELECT id FROM users WHERE uuid = '%s')," " '%s', '', %llu, '%s', '%s', '%s', m_now (), m_now ());", host, current_credentials.uuid, quoted_identifier_name, os, quoted_source_type, quoted_source_id, quoted_source_data); if (host_new == 0) { sql ("UPDATE hosts" " SET modification_time = (SELECT modification_time" " FROM host_oss" " WHERE id = %llu)" " WHERE id = %llu;", sql_last_insert_id (), host); sql ("UPDATE oss" " SET modification_time = (SELECT modification_time" " FROM host_oss" " WHERE id = %llu)" " WHERE id = %llu;", sql_last_insert_id (), os); } } else { sql ("INSERT into host_identifiers" " (uuid, host, owner, name, comment, value, source_type," " source_id, source_data, creation_time, modification_time)" " VALUES" " (make_uuid (), %llu," " (SELECT id FROM users WHERE uuid = '%s')," " '%s', '', '%s', '%s', '%s', '%s', m_now (), m_now ());", host, current_credentials.uuid, quoted_identifier_name, quoted_identifier_value, quoted_source_type, quoted_source_id, quoted_source_data); if (host_new == 0) sql ("UPDATE hosts" " SET modification_time = (SELECT modification_time" " FROM host_identifiers" " WHERE id = %llu)" " WHERE id = %llu;", sql_last_insert_id (), host); } g_free (quoted_source_type); g_free (quoted_source_id); g_free (quoted_source_data); g_free (quoted_identifier_name); g_free (quoted_identifier_value); index++; } g_free (quoted_host_name); host_index++; } index = 0; while (identifiers && (index < identifiers->len)) identifier_free (g_ptr_array_index (identifiers, index++)); array_free (identifiers); identifiers = NULL; array_free (identifier_hosts); identifier_hosts = NULL; } } /** * @brief Set the maximum severity of each host in a scan. * * @param[in] report The report associated with the scan. * @param[in] overrides_arg Whether override should be applied. * @param[in] min_qod_arg Min QOD to use. */ void hosts_set_max_severity (report_t report, int *overrides_arg, int *min_qod_arg) { gchar *new_severity_sql; int dynamic_severity, overrides, min_qod; if (overrides_arg) overrides = *overrides_arg; else { task_t task; /* Get "Assets Apply Overrides" task preference. */ overrides = 1; if (report_task (report, &task) == 0) { char *value; value = task_preference_value (task, "assets_apply_overrides"); if (value && (strcmp (value, "yes") == 0)) overrides = 1; else overrides = 0; free (value); } } if (min_qod_arg) min_qod = *min_qod_arg; else { task_t task; /* Get "Assets Min QOD". */ min_qod = MIN_QOD_DEFAULT; if (report_task (report, &task) == 0) { char *value; value = task_preference_value (task, "assets_min_qod"); if (value) min_qod = atoi (value); free (value); } } dynamic_severity = setting_dynamic_severity_int (); new_severity_sql = new_severity_clause (overrides, dynamic_severity); sql ("INSERT INTO host_max_severities" " (host, severity, source_type, source_id, creation_time)" " SELECT asset_host," " coalesce ((SELECT max (%s) FROM results" " WHERE report = %llu" " AND qod >= %i" " AND host = (SELECT name FROM hosts" " WHERE id = asset_host))," " 0.0)," " 'Report'," " (SELECT uuid FROM reports WHERE id = %llu)," " m_now ()" " FROM (SELECT host AS asset_host" " FROM host_identifiers" " WHERE source_id = (SELECT uuid FROM reports WHERE id = %llu))" " AS subquery;", new_severity_sql, report, min_qod, report, report); g_free (new_severity_sql); } /** * @brief Store certain host details in the assets after a scan. * * @param[in] report The report associated with the scan. */ void hosts_set_details (report_t report) { sql ("INSERT INTO host_details" " (detail_source_type, detail_source_name, detail_source_description," " name, value, source_type, source_id, host)" " SELECT source_type," " source_name," " source_description," " name," " value," " 'Report'," " (SELECT uuid FROM reports WHERE id = %llu)," " (SELECT host" " FROM host_identifiers" " WHERE source_id = (SELECT uuid FROM reports" " WHERE id = %llu)" " AND (SELECT name FROM hosts WHERE id = host)" " = (SELECT host FROM report_hosts" " WHERE id = report_host_details.report_host)" " LIMIT 1)" " FROM report_host_details" " WHERE (SELECT report FROM report_hosts" " WHERE id = report_host)" " = %llu" /* Only if the task is included in the assets. */ " AND (SELECT value = 'yes' FROM task_preferences" " WHERE task = (SELECT task FROM reports WHERE id = %llu)" " AND name = 'in_assets')" /* Ensure that every report host detail has a corresponding host * in the assets. */ " AND EXISTS (SELECT *" " FROM host_identifiers" " WHERE source_id = (SELECT uuid FROM reports" " WHERE id = %llu)" " AND (SELECT name FROM hosts WHERE id = host)" " = (SELECT host FROM report_hosts" " WHERE id = report_host_details.report_host))" " AND (name IN ('best_os_cpe', 'best_os_txt', 'traceroute'));", report, report, report, report, report); } /** * @brief Get XML of a detailed host route. * * @param[in] host The host. * * @return XML. */ gchar* host_routes_xml (host_t host) { iterator_t routes; GString* buffer; gchar *owned_clause, *with_clause; owned_clause = acl_where_owned_for_get ("host", NULL, NULL, &with_clause); buffer = g_string_new (""); init_iterator (&routes, "SELECT outer_details.value," " outer_details.source_type," " outer_details.source_id," " outer_identifiers.modification_time" " FROM host_details AS outer_details" " JOIN host_identifiers AS outer_identifiers" " ON outer_identifiers.host = outer_details.host" " WHERE outer_details.host = %llu" " AND outer_details.name = 'traceroute'" " AND outer_details.source_id = outer_identifiers.source_id" " AND outer_identifiers.name='ip'" " AND outer_identifiers.modification_time" " = (SELECT max (modification_time)" " FROM host_identifiers" " WHERE host_identifiers.host = %llu" " AND host_identifiers.source_id IN" " (SELECT source_id FROM host_details" " WHERE host = %llu" " AND value = outer_details.value)" " AND host_identifiers.name='ip')" " ORDER BY outer_identifiers.modification_time DESC;", host, host, host); while (next (&routes)) { const char *traceroute; const char *source_id; time_t modified; gchar **hop_ips, **hop_ip; int distance; g_string_append (buffer, ""); traceroute = iterator_string (&routes, 0); source_id = iterator_string (&routes, 2); modified = iterator_int64 (&routes, 3); hop_ips = g_strsplit (traceroute, ",", 0); hop_ip = hop_ips; distance = 0; while (*hop_ip != NULL) { iterator_t best_host_iterator; const char *best_host_id; int same_source; init_iterator (&best_host_iterator, "%s" " SELECT hosts.uuid," " (source_id='%s')" " AS same_source" " FROM hosts, host_identifiers" " WHERE hosts.id = host_identifiers.host" " AND host_identifiers.name = 'ip'" " AND host_identifiers.value='%s'" " AND %s" " ORDER BY same_source DESC," " abs(host_identifiers.modification_time" " - %llu) ASC" " LIMIT 1;", with_clause ? with_clause : "", source_id, *hop_ip, owned_clause, modified); if (next (&best_host_iterator)) { best_host_id = iterator_string (&best_host_iterator, 0); same_source = iterator_int (&best_host_iterator, 1); } else { best_host_id = NULL; same_source = 0; } g_string_append_printf (buffer, "" "%s" "", best_host_id ? best_host_id : "", distance, same_source, *hop_ip); cleanup_iterator (&best_host_iterator); distance++; hop_ip++; } g_string_append (buffer, ""); g_strfreev(hop_ips); } g_free (with_clause); g_free (owned_clause); cleanup_iterator (&routes); g_string_append (buffer, ""); return g_string_free (buffer, FALSE); } /** * @brief Add host details to a report host. * * @param[in] report UUID of resource. * @param[in] ip Host. * @param[in] entity XML entity containing details. * * @return 0 success, -1 failed to parse XML. */ int manage_report_host_details (report_t report, const char *ip, entity_t entity) { int in_assets; entities_t details; entity_t detail; char *uuid; in_assets = sql_int ("SELECT not(value = 'no') FROM task_preferences" " WHERE task = (SELECT task FROM reports" " WHERE id = %llu)" " AND name = 'in_assets';", report); details = entity->entities; if (identifiers == NULL) identifiers = make_array (); if (identifier_hosts == NULL) identifier_hosts = make_array (); uuid = report_uuid (report); while ((detail = first_entity (details))) { if (strcmp (entity_name (detail), "detail") == 0) { entity_t source, source_type, source_name, source_desc, name, value; /* Parse host detail and add to report */ source = entity_child (detail, "source"); if (source == NULL) goto error; source_type = entity_child (source, "type"); if (source_type == NULL) goto error; source_name = entity_child (source, "name"); if (source_name == NULL) goto error; source_desc = entity_child (source, "description"); if (source_desc == NULL) goto error; name = entity_child (detail, "name"); if (name == NULL) goto error; value = entity_child (detail, "value"); if (value == NULL) goto error; insert_report_host_detail (report, ip, entity_text (source_type), entity_text (source_name), entity_text (source_desc), entity_text (name), entity_text (value)); /* Only add to assets if "Add to Assets" is set on the task. */ if (in_assets) { if (strcmp (entity_text (name), "hostname") == 0) { identifier_t *identifier; identifier = g_malloc (sizeof (identifier_t)); identifier->ip = g_strdup (ip); identifier->name = g_strdup ("hostname"); identifier->value = g_strdup (entity_text (value)); identifier->source_id = g_strdup (uuid); identifier->source_type = g_strdup ("Report Host Detail"); identifier->source_data = g_strdup (entity_text (source_name)); array_add (identifiers, identifier); array_add_new_string (identifier_hosts, g_strdup (ip)); } if (strcmp (entity_text (name), "MAC") == 0) { identifier_t *identifier; identifier = g_malloc (sizeof (identifier_t)); identifier->ip = g_strdup (ip); identifier->name = g_strdup ("MAC"); identifier->value = g_strdup (entity_text (value)); identifier->source_id = g_strdup (uuid); identifier->source_type = g_strdup ("Report Host Detail"); identifier->source_data = g_strdup (entity_text (source_name)); array_add (identifiers, identifier); array_add_new_string (identifier_hosts, g_strdup (ip)); } if (strcmp (entity_text (name), "OS") == 0 && g_str_has_prefix (entity_text (value), "cpe:")) { identifier_t *identifier; identifier = g_malloc (sizeof (identifier_t)); identifier->ip = g_strdup (ip); identifier->name = g_strdup ("OS"); identifier->value = g_strdup (entity_text (value)); identifier->source_id = g_strdup (uuid); identifier->source_type = g_strdup ("Report Host Detail"); identifier->source_data = g_strdup (entity_text (source_name)); array_add (identifiers, identifier); array_add_new_string (identifier_hosts, g_strdup (ip)); } if (strcmp (entity_text (name), "ssh-key") == 0) { identifier_t *identifier; identifier = g_malloc (sizeof (identifier_t)); identifier->ip = g_strdup (ip); identifier->name = g_strdup ("ssh-key"); identifier->value = g_strdup (entity_text (value)); identifier->source_id = g_strdup (uuid); identifier->source_type = g_strdup ("Report Host Detail"); identifier->source_data = g_strdup (entity_text (source_name)); array_add (identifiers, identifier); array_add_new_string (identifier_hosts, g_strdup (ip)); } } } details = next_entities (details); } free (uuid); return 0; error: free (uuid); return -1; } /** * @brief Add a host detail to a report host. * * @param[in] report UUID of resource. * @param[in] host Host. * @param[in] xml Report host detail XML. * * @return 0 success, -1 failed to parse XML, -2 host was NULL. */ int manage_report_host_detail (report_t report, const char *host, const char *xml) { int ret; entity_t entity; if (host == NULL) return -2; entity = NULL; if (parse_entity (xml, &entity)) return -1; ret = manage_report_host_details (report, host, entity); free_entity (entity); return ret; } /** * @brief Initialise a host identifier iterator. * * @param[in] iterator Iterator. * @param[in] host Host. * @param[in] ascending Whether to sort ascending or descending. * @param[in] sort_field Field to sort on, or NULL for type then start. */ void init_host_identifier_iterator (iterator_t* iterator, host_t host, int ascending, const char* sort_field) { assert (current_credentials.uuid); if (host) init_iterator (iterator, "SELECT id, uuid, name, comment, iso_time (creation_time)," " iso_time (modification_time), creation_time," " modification_time, owner, owner, value," " source_type, source_id, source_data," " (CASE WHEN source_type LIKE 'Report%%'" " THEN NOT EXISTS (SELECT * FROM reports" " WHERE uuid = source_id)" " ELSE CAST (0 AS boolean)" " END)," " '', ''" " FROM host_identifiers" " WHERE host = %llu" " UNION" " SELECT id, uuid, name, comment, iso_time (creation_time)," " iso_time (modification_time), creation_time," " modification_time, owner, owner," " (SELECT name FROM oss WHERE id = os)," " source_type, source_id, source_data," " (CASE WHEN source_type LIKE 'Report%%'" " THEN NOT EXISTS (SELECT * FROM reports" " WHERE uuid = source_id)" " ELSE CAST (0 AS boolean)" " END)," " (SELECT uuid FROM oss WHERE id = os)," " cpe_title ((SELECT name FROM oss WHERE id = os))" " FROM host_oss" " WHERE host = %llu" " ORDER BY %s %s;", host, host, sort_field ? sort_field : "creation_time", ascending ? "ASC" : "DESC"); else init_iterator (iterator, "SELECT id, uuid, name, comment, iso_time (creation_time)," " iso_time (modification_time), creation_time," " modification_time, owner, owner, value," " source_type, source_id, source_data, 0, '', ''" " FROM host_identifiers" " ORDER BY %s %s;", sort_field ? sort_field : "creation_time", ascending ? "ASC" : "DESC"); } /** * @brief Get the value from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The value of the host identifier, or NULL if iteration is complete. * Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_value, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the source type from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The source type of the host identifier, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_source_type, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the source from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The source of the host identifier, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_source_id, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the source data from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The source data of the host identifier, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_source_data, GET_ITERATOR_COLUMN_COUNT + 3); /** * @brief Get the source orphan state from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The source orphan state of the host identifier, or 0 if iteration is * complete. Freed by cleanup_iterator. */ int host_identifier_iterator_source_orphan (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the OS UUID from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The OS UUID of the host identifier, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_os_id, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the OS title from a host identifier iterator. * * @param[in] iterator Iterator. * * @return The OS title of the host identifier, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_identifier_iterator_os_title, GET_ITERATOR_COLUMN_COUNT + 6); /** * @brief Filter columns for host iterator. */ #define HOST_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "severity", "os", "oss", "hostname", "ip", \ "severity_level", "updated", NULL } /** * @brief Host iterator columns. */ #define HOST_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (hosts), \ { \ "1", \ "writable", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "0", \ "in_use", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT round (CAST (severity AS numeric), 1)" \ " FROM host_max_severities" \ " WHERE host = hosts.id" \ " ORDER by creation_time DESC" \ " LIMIT 1)", \ "severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "(SELECT CASE" \ " WHEN best_os_text LIKE '%[possible conflict]%'" \ " THEN best_os_text" \ " WHEN best_os_cpe IS NULL" \ " THEN '[unknown]'" \ " ELSE best_os_cpe" \ " END" \ " FROM (SELECT (SELECT value" \ " FROM (SELECT max (id) AS id" \ " FROM host_details" \ " WHERE host = hosts.id" \ " AND name = 'best_os_cpe')" \ " AS sub," \ " host_details" \ " WHERE sub.id = host_details.id)" \ " AS best_os_cpe," \ " (SELECT value" \ " FROM (SELECT max (id) AS id" \ " FROM host_details" \ " WHERE host = hosts.id" \ " AND name = 'best_os_text')" \ " AS sub," \ " host_details" \ " WHERE sub.id = host_details.id)" \ " AS best_os_text)" \ " AS vars)", \ "os", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT group_concat (name, ', ') FROM oss" \ " WHERE id IN (SELECT distinct os FROM host_oss" \ " WHERE host = hosts.id))", \ "oss", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT value" \ " FROM host_identifiers" \ " WHERE host = hosts.id" \ " AND name = 'hostname'" \ " ORDER by creation_time DESC" \ " LIMIT 1)", \ "hostname", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT value" \ " FROM host_identifiers" \ " WHERE host = hosts.id" \ " AND name = 'ip'" \ " ORDER by creation_time DESC" \ " LIMIT 1)", \ "ip", \ KEYWORD_TYPE_STRING \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Host iterator WHERE columns. */ #define HOST_ITERATOR_WHERE_COLUMNS \ { \ { \ "(SELECT severity_to_level (CAST (severity AS numeric), 0)" \ " FROM host_max_severities" \ " WHERE host = hosts.id" \ " ORDER by creation_time DESC" \ " LIMIT 1)", \ "severity_level", \ KEYWORD_TYPE_STRING \ }, \ { \ "modification_time", "updated", KEYWORD_TYPE_INTEGER \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Initialise a host iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find host, 2 failed to find filter, * -1 error. */ int init_asset_host_iterator (iterator_t *iterator, const get_data_t *get) { static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS; static column_t columns[] = HOST_ITERATOR_COLUMNS; static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS; return init_get_iterator2 (iterator, "host", get, /* Columns. */ columns, /* Columns for trashcan. */ NULL, /* WHERE Columns. */ where_columns, /* WHERE Columns for trashcan. */ NULL, filter_columns, 0, NULL, NULL, NULL, TRUE, FALSE, NULL); } /** * @brief Get the writable status from an asset iterator. * * @param[in] iterator Iterator. * * @return 1 if writable, else 0. */ int asset_iterator_writable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT); } /** * @brief Get the "in use" status from an asset iterator. * * @param[in] iterator Iterator. * * @return 1 if in use, else 0. */ int asset_iterator_in_use (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 1); } /** * @brief Get the max severity from an asset host iterator. * * @param[in] iterator Iterator. * * @return The maximum severity of the host, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (asset_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Count number of hosts. * * @param[in] get GET params. * * @return Total number of hosts in filtered set. */ int asset_host_count (const get_data_t *get) { static const char *filter_columns[] = HOST_ITERATOR_FILTER_COLUMNS; static column_t columns[] = HOST_ITERATOR_COLUMNS; static column_t where_columns[] = HOST_ITERATOR_WHERE_COLUMNS; return count2 ("host", get, columns, NULL, where_columns, NULL, filter_columns, 0, NULL, NULL, NULL, TRUE); } /** * @brief Filter columns for os iterator. */ #define OS_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "title", "hosts", "latest_severity", \ "highest_severity", "average_severity", "average_severity_score", \ "severity", NULL } /** * @brief OS iterator columns. */ #define OS_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (oss), \ { \ "0", \ "writable", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT count (*) > 0 FROM host_oss WHERE os = oss.id)", \ "in_use", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT coalesce (cpe_title (oss.name), ''))", \ "title", \ KEYWORD_TYPE_STRING \ }, \ { \ "(SELECT count(*)" \ " FROM (SELECT inner_cpes[1] AS cpe, host" \ " FROM (SELECT array_agg (host_details.value" \ " ORDER BY host_details.id DESC)" \ " AS inner_cpes," \ " host" \ " FROM host_details, hosts" \ " WHERE host_details.name = 'best_os_cpe'" \ " AND hosts.id = host_details.host" \ " AND (" ACL_USER_MAY_OPTS ("hosts") ")" \ " GROUP BY host)" \ " AS host_details_subselect)" \ " AS array_removal_subselect" \ " WHERE cpe = oss.name)", \ "hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT round (CAST (severity AS numeric), 1) FROM host_max_severities" \ " WHERE host = (SELECT host FROM host_oss" \ " WHERE os = oss.id" \ " ORDER BY creation_time DESC LIMIT 1)" \ " ORDER BY creation_time DESC LIMIT 1)", \ "latest_severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "(SELECT round (max (CAST (severity AS numeric)), 1)" \ " FROM host_max_severities" \ " WHERE host IN (SELECT DISTINCT host FROM host_oss" \ " WHERE os = oss.id))", \ "highest_severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "(SELECT round (CAST (avg (severity) AS numeric), 2)" \ " FROM (SELECT (SELECT severity FROM host_max_severities" \ " WHERE host = hosts.host" \ " ORDER BY creation_time DESC LIMIT 1)" \ " AS severity" \ " FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)" \ " AS hosts)" \ " AS severities)", \ "average_severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief OS iterator optional filtering columns. */ #define OS_ITERATOR_WHERE_COLUMNS \ { \ { \ "(SELECT round (CAST (avg (severity) AS numeric)" \ " * (SELECT count (distinct host)" \ " FROM host_oss WHERE os = oss.id), 2)" \ " FROM (SELECT (SELECT severity FROM host_max_severities" \ " WHERE host = hosts.host" \ " ORDER BY creation_time DESC LIMIT 1)" \ " AS severity" \ " FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)" \ " AS hosts)" \ " AS severities)", \ "average_severity_score", \ KEYWORD_TYPE_DOUBLE \ }, \ { \ "(SELECT round (CAST (avg (severity) AS numeric), 2)" \ " FROM (SELECT (SELECT severity FROM host_max_severities" \ " WHERE host = hosts.host" \ " ORDER BY creation_time DESC LIMIT 1)" \ " AS severity" \ " FROM (SELECT distinct host FROM host_oss WHERE os = oss.id)" \ " AS hosts)" \ " AS severities)", \ "severity", \ KEYWORD_TYPE_DOUBLE \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Generate the extra_tables string for an OS iterator. * * @return Newly allocated string. */ static gchar * asset_os_iterator_opts_table () { assert (current_credentials.uuid); return g_strdup_printf (", (SELECT" " (SELECT id FROM users" " WHERE users.uuid = '%s')" " AS user_id," " 'host' AS type)" " AS opts", current_credentials.uuid); } /** * @brief Initialise an OS iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find os, 2 failed to find filter, * -1 error. */ int init_asset_os_iterator (iterator_t *iterator, const get_data_t *get) { int ret; static const char *filter_columns[] = OS_ITERATOR_FILTER_COLUMNS; static column_t columns[] = OS_ITERATOR_COLUMNS; static column_t where_columns[] = OS_ITERATOR_WHERE_COLUMNS; gchar *extra_tables; extra_tables = asset_os_iterator_opts_table (); ret = init_get_iterator2_with (iterator, "os", get, /* Columns. */ columns, /* Columns for trashcan. */ NULL, /* WHERE Columns. */ where_columns, /* WHERE Columns for trashcan. */ NULL, filter_columns, 0, extra_tables, NULL, NULL, TRUE, FALSE, NULL, NULL, 0, 0); g_free (extra_tables); return ret; } /** * @brief Get the title from an OS iterator. * * @param[in] iterator Iterator. * * @return The title of the OS, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (asset_os_iterator_title, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get the number of installs from an asset OS iterator. * * @param[in] iterator Iterator. * * @return Number of hosts that have the OS. */ int asset_os_iterator_installs (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3); } /** * @brief Get the latest severity from an OS iterator. * * @param[in] iterator Iterator. * * @return The severity of the OS, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (asset_os_iterator_latest_severity, GET_ITERATOR_COLUMN_COUNT + 4); /** * @brief Get the highest severity from an OS iterator. * * @param[in] iterator Iterator. * * @return The severity of the OS, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (asset_os_iterator_highest_severity, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the average severity from an OS iterator. * * @param[in] iterator Iterator. * * @return The severity of the OS, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (asset_os_iterator_average_severity, GET_ITERATOR_COLUMN_COUNT + 6); /** * @brief Count number of oss. * * @param[in] get GET params. * * @return Total number of oss in filtered set. */ int asset_os_count (const get_data_t *get) { static const char *extra_columns[] = OS_ITERATOR_FILTER_COLUMNS; static column_t columns[] = OS_ITERATOR_COLUMNS; static column_t where_columns[] = OS_ITERATOR_WHERE_COLUMNS; int ret; ret = count2 ("os", get, columns, NULL, where_columns, NULL, extra_columns, 0, 0, 0, NULL, TRUE); return ret; } /** * @brief Initialise an OS host iterator. * * @param[in] iterator Iterator. * @param[in] os OS. */ void init_os_host_iterator (iterator_t* iterator, resource_t os) { assert (os); init_iterator (iterator, "SELECT id, uuid, name, comment, iso_time (creation_time)," " iso_time (modification_time), creation_time," " modification_time, owner, owner," " (SELECT round (CAST (severity AS numeric), 1)" " FROM host_max_severities" " WHERE host = hosts.id" " ORDER by creation_time DESC" " LIMIT 1)" " FROM hosts" " WHERE id IN (SELECT DISTINCT host FROM host_oss" " WHERE os = %llu)" " ORDER BY modification_time DESC;", os); } /** * @brief Get the severity from an OS host detail iterator. * * @param[in] iterator Iterator. * * @return The severity of the OS host, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (os_host_iterator_severity, GET_ITERATOR_COLUMN_COUNT); /** * @brief Initialise an asset host detail iterator. * * @param[in] iterator Iterator. * @param[in] host Host. */ void init_host_detail_iterator (iterator_t* iterator, resource_t host) { assert (host); init_iterator (iterator, "SELECT sub.id, name, value, source_type, source_id" " FROM (SELECT max (id) AS id FROM host_details" " WHERE host = %llu" " GROUP BY name)" " AS sub," " host_details" " WHERE sub.id = host_details.id" " ORDER BY name ASC;", host); } /** * @brief Get the name from an asset host detail iterator. * * @param[in] iterator Iterator. * * @return The name of the host detail, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_detail_iterator_name, 1); /** * @brief Get the name from an asset host detail iterator. * * @param[in] iterator Iterator. * * @return The name of the host detail, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_detail_iterator_value, 2); /** * @brief Get the source type from an asset host detail iterator. * * @param[in] iterator Iterator. * * @return The source type of the host detail, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_detail_iterator_source_type, 3); /** * @brief Get the source ID from an asset host detail iterator. * * @param[in] iterator Iterator. * * @return The source ID of the host detail, or NULL if iteration is * complete. Freed by cleanup_iterator. */ DEF_ACCESS (host_detail_iterator_source_id, 4); /** * @brief Find a host for a specific permission, given a UUID. * * @param[in] uuid UUID of host. * @param[out] host Host return, 0 if successfully failed to find host. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find host), TRUE on error. */ static gboolean find_host_with_permission (const char* uuid, host_t* host, const char *permission) { return find_resource_with_permission ("host", uuid, host, permission, 0); } /** * @brief Check whether a string is an identifier name. * * @param[in] name Possible identifier name. * * @return 1 if an identifier name, else 0. */ static int identifier_name (const char *name) { return (strcmp ("hostname", name) == 0) || (strcmp ("MAC", name) == 0) || (strcmp ("OS", name) == 0) || (strcmp ("ssh-key", name) == 0); } /** * @brief Create a host asset. * * @param[in] host_name Host Name. * @param[in] comment Comment. * @param[out] host_return Created asset. * * @return 0 success, 1 failed to find report, 2 host not an IP address, * 99 permission denied, -1 error. */ int create_asset_host (const char *host_name, const char *comment, resource_t* host_return) { int host_type; resource_t host; gchar *quoted_host_name, *quoted_comment; if (host_name == NULL) return -1; sql_begin_immediate (); if (acl_user_may ("create_asset") == 0) { sql_rollback (); return 99; } host_type = gvm_get_host_type (host_name); if (host_type != HOST_TYPE_IPV4 && host_type != HOST_TYPE_IPV6) { sql_rollback (); return 2; } quoted_host_name = sql_quote (host_name); quoted_comment = sql_quote (comment ? comment : ""); sql ("INSERT into hosts" " (uuid, owner, name, comment, creation_time, modification_time)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE uuid = '%s'), '%s', '%s'," " m_now (), m_now ());", current_credentials.uuid, quoted_host_name, quoted_comment); g_free (quoted_comment); host = sql_last_insert_id (); sql ("INSERT into host_identifiers" " (uuid, host, owner, name, comment, value, source_type, source_id," " source_data, creation_time, modification_time)" " VALUES" " (make_uuid (), %llu, (SELECT id FROM users WHERE uuid = '%s'), 'ip'," " '', '%s', 'User', '%s', '', m_now (), m_now ());", host, current_credentials.uuid, quoted_host_name, current_credentials.uuid); g_free (quoted_host_name); if (host_return) *host_return = host; sql_commit (); return 0; } /** * @brief Create all available assets from a report. * * @param[in] report_id UUID of report. * @param[in] term Filter term, for min_qod and apply_overrides. * * @return 0 success, 1 failed to find report, 99 permission denied, -1 error. */ int create_asset_report (const char *report_id, const char *term) { resource_t report; iterator_t hosts; gchar *quoted_report_id; int apply_overrides, min_qod; if (report_id == NULL) return -1; sql_begin_immediate (); if (acl_user_may ("create_asset") == 0) { sql_rollback (); return 99; } report = 0; if (find_report_with_permission (report_id, &report, "get_reports")) { sql_rollback (); return -1; } if (report == 0) { sql_rollback (); return 1; } /* These are freed by hosts_set_identifiers. */ if (identifiers == NULL) identifiers = make_array (); if (identifier_hosts == NULL) identifier_hosts = make_array (); quoted_report_id = sql_quote (report_id); sql ("DELETE FROM host_identifiers WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_oss WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_max_severities WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_details WHERE source_id = '%s';", quoted_report_id); g_free (quoted_report_id); init_report_host_iterator (&hosts, report, NULL, 0); while (next (&hosts)) { const char *host; report_host_t report_host; iterator_t details; host = host_iterator_host (&hosts); report_host = host_iterator_report_host (&hosts); if (report_host_dead (report_host) || report_host_result_count (report_host) == 0) continue; host_notice (host, "ip", host, "Report Host", report_id, 0, 0); init_report_host_details_iterator (&details, report_host); while (next (&details)) { const char *name; name = report_host_details_iterator_name (&details); if (identifier_name (name)) { const char *value; identifier_t *identifier; value = report_host_details_iterator_value (&details); if ((strcmp (name, "OS") == 0) && (g_str_has_prefix (value, "cpe:") == 0)) continue; identifier = g_malloc (sizeof (identifier_t)); identifier->ip = g_strdup (host); identifier->name = g_strdup (name); identifier->value = g_strdup (value); identifier->source_id = g_strdup (report_id); identifier->source_type = g_strdup ("Report Host Detail"); identifier->source_data = g_strdup (report_host_details_iterator_source_name (&details)); array_add (identifiers, identifier); array_add_new_string (identifier_hosts, g_strdup (host)); } } cleanup_iterator (&details); } cleanup_iterator (&hosts); hosts_set_identifiers (report); apply_overrides = filter_term_apply_overrides (term); min_qod = filter_term_min_qod (term); hosts_set_max_severity (report, &apply_overrides, &min_qod); hosts_set_details (report); sql_commit (); return 0; } /** * @brief Modify an asset. * * @param[in] asset_id UUID of asset. * @param[in] comment Comment on asset. * * @return 0 success, 1 failed to find asset, 3 asset_id required, * 99 permission denied, -1 internal error. */ int modify_asset (const char *asset_id, const char *comment) { gchar *quoted_asset_id, *quoted_comment; resource_t asset; if (asset_id == NULL) return 3; sql_begin_immediate (); if (acl_user_may ("modify_asset") == 0) { sql_rollback (); return 99; } /* Host. */ quoted_asset_id = sql_quote (asset_id); switch (sql_int64 (&asset, "SELECT id FROM hosts WHERE uuid = '%s';", quoted_asset_id)) { case 0: break; case 1: /* Too few rows in result of query. */ asset = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_asset_id); sql_rollback (); return -1; break; } g_free (quoted_asset_id); if (asset == 0) { sql_rollback (); return 1; } quoted_comment = sql_quote (comment ?: ""); sql ("UPDATE hosts SET" " comment = '%s'," " modification_time = m_now ()" " WHERE id = %llu;", quoted_comment, asset); g_free (quoted_comment); sql_commit (); return 0; } /** * @brief Delete all asset that came from a report. * * Assume caller started a transaction. * * @param[in] report_id UUID of report. * * @return 0 success, 2 failed to find report, 4 UUID * required, 99 permission denied, -1 error. */ static int delete_report_assets (const char *report_id) { resource_t report; gchar *quoted_report_id; report = 0; if (find_report_with_permission (report_id, &report, "delete_report")) { sql_rollback (); return -1; } if (report == 0) { sql_rollback (); return 1; } quoted_report_id = sql_quote (report_id); /* Delete the hosts and OSs identified by this report if they were only * identified by this report. */ sql ("CREATE TEMPORARY TABLE delete_report_assets_hosts (host INTEGER);"); /* Collect hosts that were only identified by the given source. */ sql ("INSERT into delete_report_assets_hosts" " (host)" " SELECT id FROM hosts" " WHERE (EXISTS (SELECT * FROM host_identifiers" " WHERE host = hosts.id" " AND source_id = '%s')" " OR EXISTS (SELECT * FROM host_oss" " WHERE host = hosts.id" " AND source_id = '%s'))" " AND NOT EXISTS (SELECT * FROM host_identifiers" " WHERE host = hosts.id" " AND source_id != '%s')" " AND NOT EXISTS (SELECT * FROM host_oss" " WHERE host = hosts.id" " AND source_id != '%s');", quoted_report_id, quoted_report_id, quoted_report_id, quoted_report_id); sql ("DELETE FROM host_identifiers WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_oss WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_max_severities WHERE source_id = '%s';", quoted_report_id); sql ("DELETE FROM host_details WHERE source_id = '%s';", quoted_report_id); g_free (quoted_report_id); /* The host may have details from sources that did not identify the host. */ sql ("DELETE FROM host_details" " WHERE host in (SELECT host FROM delete_report_assets_hosts);"); /* The host may have severities from sources that did not identify the * host. */ sql ("DELETE FROM host_max_severities" " WHERE host in (SELECT host FROM delete_report_assets_hosts);"); sql ("DELETE FROM hosts" " WHERE id in (SELECT host FROM delete_report_assets_hosts);"); sql ("DROP TABLE delete_report_assets_hosts;"); sql_commit (); return 0; } /** * @brief Delete an asset. * * @param[in] asset_id UUID of asset. * @param[in] report_id UUID of report from which to delete assets. * Overridden by asset_id. * @param[in] dummy Dummy arg to match other delete functions. * * @return 0 success, 1 asset is in use, 2 failed to find asset, 4 UUID * required, 99 permission denied, -1 error. */ int delete_asset (const char *asset_id, const char *report_id, int dummy) { resource_t asset, parent; gchar *quoted_asset_id, *parent_id; asset = parent = 0; sql_begin_immediate (); if (acl_user_may ("delete_asset") == 0) { sql_rollback (); return 99; } if (asset_id == NULL) { if (report_id == NULL) { sql_rollback (); return 3; } return delete_report_assets (report_id); } /* Host identifier. */ quoted_asset_id = sql_quote (asset_id); switch (sql_int64 (&asset, "SELECT id FROM host_identifiers WHERE uuid = '%s';", quoted_asset_id)) { case 0: break; case 1: /* Too few rows in result of query. */ asset = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_asset_id); sql_rollback (); return -1; break; } g_free (quoted_asset_id); if (asset) { parent_id = sql_string ("SELECT uuid FROM hosts" " WHERE id = (SELECT host FROM host_identifiers" " WHERE id = %llu);", asset); parent = 0; if (find_host_with_permission (parent_id, &parent, "delete_asset")) { sql_rollback (); return -1; } if (parent == 0) { sql_rollback (); return 99; } sql ("DELETE FROM host_identifiers WHERE id = %llu;", asset); sql_commit (); return 0; } /* Host OS. */ quoted_asset_id = sql_quote (asset_id); switch (sql_int64 (&asset, "SELECT id FROM host_oss WHERE uuid = '%s';", quoted_asset_id)) { case 0: break; case 1: /* Too few rows in result of query. */ asset = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_asset_id); sql_rollback (); return -1; break; } g_free (quoted_asset_id); if (asset) { parent_id = sql_string ("SELECT uuid FROM hosts" " WHERE id = (SELECT host FROM host_oss" " WHERE id = %llu);", asset); parent = 0; if (find_host_with_permission (parent_id, &parent, "delete_asset")) { sql_rollback (); return -1; } if (parent == 0) { sql_rollback (); return 99; } sql ("DELETE FROM host_oss WHERE id = %llu;", asset); sql_commit (); return 0; } /* OS. */ quoted_asset_id = sql_quote (asset_id); switch (sql_int64 (&asset, "SELECT id FROM oss WHERE uuid = '%s';", quoted_asset_id)) { case 0: break; case 1: /* Too few rows in result of query. */ asset = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_asset_id); sql_rollback (); return -1; break; } g_free (quoted_asset_id); if (asset) { if (sql_int ("SELECT count (*) FROM host_oss" " WHERE os = %llu;", asset)) { sql_rollback (); return 1; } sql ("DELETE FROM oss WHERE id = %llu;", asset); permissions_set_orphans ("os", asset, LOCATION_TABLE); tags_remove_resource ("os", asset, LOCATION_TABLE); sql_commit (); return 0; } /* Host. */ quoted_asset_id = sql_quote (asset_id); switch (sql_int64 (&asset, "SELECT id FROM hosts WHERE uuid = '%s';", quoted_asset_id)) { case 0: break; case 1: /* Too few rows in result of query. */ asset = 0; break; default: /* Programming error. */ assert (0); case -1: g_free (quoted_asset_id); sql_rollback (); return -1; break; } g_free (quoted_asset_id); if (asset) { sql ("DELETE FROM host_identifiers WHERE host = %llu;", asset); sql ("DELETE FROM host_oss WHERE host = %llu;", asset); sql ("DELETE FROM host_max_severities WHERE host = %llu;", asset); sql ("DELETE FROM host_details WHERE host = %llu;", asset); sql ("DELETE FROM hosts WHERE id = %llu;", asset); permissions_set_orphans ("host", asset, LOCATION_TABLE); tags_remove_resource ("host", asset, LOCATION_TABLE); sql_commit (); return 0; } sql_rollback (); return 2; } /** * @brief Generates and adds assets from report host details * * @param[in] report The report to get host details from. * @param[in] host_ip IP address of the host to get details from. * * @return 0 success, -1 error. */ int add_assets_from_host_in_report (report_t report, const char *host_ip) { int ret; gchar *quoted_host; char *report_id; report_host_t report_host = 0; /* Get report UUID */ report_id = report_uuid (report); if (report_id == NULL) { g_warning ("%s: report %llu not found.", __func__, report); return -1; } /* Find report_host */ quoted_host = sql_quote (host_ip); sql_int64 (&report_host, "SELECT id FROM report_hosts" " WHERE host = '%s' AND report = %llu", quoted_host, report); g_free (quoted_host); if (report_host == 0) { g_warning ("%s: report_host for host '%s' and report '%s' not found.", __func__, host_ip, report_id); free (report_id); return -1; } /* Create assets */ if (report_host_noticeable (report, host_ip)) { host_notice (host_ip, "ip", host_ip, "Report Host", report_id, 1, 1); } ret = add_tls_certificates_from_report_host (report_host, report_id, host_ip); if (ret) { free (report_id); return ret; } return 0; } /* Settings. */ /** * @brief Filter columns for setting iterator. */ #define SETTING_ITERATOR_FILTER_COLUMNS \ { "name", "comment", "value", NULL } /** * @brief Setting iterator columns. */ #define SETTING_ITERATOR_COLUMNS \ { \ { "id" , NULL, KEYWORD_TYPE_INTEGER }, \ { "uuid", NULL, KEYWORD_TYPE_STRING }, \ { "name", NULL, KEYWORD_TYPE_STRING }, \ { "comment", NULL, KEYWORD_TYPE_STRING }, \ { "value", NULL, KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of settings. * * @param[in] filter Filter term. * * @return Total number of settings in filtered set. */ int setting_count (const char *filter) { static const char *filter_columns[] = SETTING_ITERATOR_FILTER_COLUMNS; static column_t select_columns[] = SETTING_ITERATOR_COLUMNS; gchar *clause; int ret; assert (current_credentials.uuid); clause = filter_clause ("setting", filter, filter_columns, select_columns, NULL, 0, NULL, NULL, NULL, NULL, NULL); ret = sql_int ("SELECT count (*)" " FROM settings" " WHERE" " (owner = (SELECT id FROM users WHERE uuid = '%s')" " OR (owner IS NULL" " AND uuid" " NOT IN (SELECT uuid FROM settings" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))))" "%s%s;", current_credentials.uuid, current_credentials.uuid, clause ? " AND " : "", clause ? clause : ""); g_free (clause); return ret; } /** * @brief Return the uuid of a resource filter from settings. * * @param[in] resource Resource (eg. Filters, Targets, CPE). * * @return resource filter uuid in settings if it exists, "" otherwise. */ char * setting_filter (const char *resource) { return sql_string ("SELECT value FROM settings WHERE name = '%s Filter'" " AND " ACL_GLOBAL_OR_USER_OWNS () "" " ORDER BY coalesce (owner, 0) DESC;", resource, current_credentials.uuid); } /** * @brief Return the Default Severity user setting as a double. * * @return The user's Default Severity. */ static double setting_default_severity_dbl () { return current_credentials.default_severity; } /** * @brief Return the Dynamic Severity user setting as an int. * * @return 1 if user's Dynamic Severity is "Yes", 0 if it is "No", * or does not exist. */ static int setting_dynamic_severity_int () { return current_credentials.dynamic_severity; } /** * @brief Return the user's timezone. * * @return User Severity Class in settings if it exists, else NULL. */ static char * setting_timezone () { return sql_string ("SELECT timezone FROM users WHERE uuid = '%s'", current_credentials.uuid); } /** * @brief Return the Auto Cache Rebuild user setting as an int. * * @return 1 if cache is rebuilt automatically, 0 if not. */ static int setting_auto_cache_rebuild_int () { return sql_int ("SELECT coalesce" " ((SELECT value FROM settings" " WHERE uuid = 'a09285b0-2d47-49b6-a4ef-946ee71f1d5c'" " AND " ACL_USER_OWNS () "" " ORDER BY coalesce (owner, 0) DESC LIMIT 1)," " '1');", current_credentials.uuid); } /** * @brief Initialise a setting iterator, including observed settings. * * @param[in] iterator Iterator. * @param[in] uuid UUID of setting to limit iteration to. 0 for all. * @param[in] filter Filter term. * @param[in] first First setting. * @param[in] max Maximum number of settings returned. * @param[in] ascending Whether to sort ascending or descending. * @param[in] sort_field Field to sort on, or NULL for "id". */ void init_setting_iterator (iterator_t *iterator, const char *uuid, const char *filter, int first, int max, int ascending, const char *sort_field) { static const char *filter_columns[] = SETTING_ITERATOR_FILTER_COLUMNS; static column_t select_columns[] = SETTING_ITERATOR_COLUMNS; gchar *clause, *columns, *quoted_uuid; assert (current_credentials.uuid); if (first < 0) first = 0; if (max < 1) max = -1; clause = filter_clause ("setting", filter, filter_columns, select_columns, NULL, 0, NULL, NULL, NULL, NULL, NULL); quoted_uuid = uuid ? sql_quote (uuid) : NULL; columns = columns_build_select (select_columns); if (quoted_uuid) init_iterator (iterator, "SELECT %s" " FROM settings" " WHERE uuid = '%s'" " AND (owner = (SELECT id FROM users WHERE uuid = '%s')" " OR (owner IS NULL" " AND uuid" " NOT IN (SELECT uuid FROM settings" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))))", columns, quoted_uuid, current_credentials.uuid, current_credentials.uuid); else init_iterator (iterator, "SELECT %s" " FROM settings" " WHERE" " (owner = (SELECT id FROM users WHERE uuid = '%s')" " OR (owner IS NULL" " AND uuid" " NOT IN (SELECT uuid FROM settings" " WHERE owner = (SELECT id FROM users" " WHERE uuid = '%s'))))" "%s%s" " ORDER BY %s %s" " LIMIT %s OFFSET %i;", columns, current_credentials.uuid, current_credentials.uuid, clause ? " AND " : "", clause ? clause : "", sort_field ? sort_field : "id", ascending ? "ASC" : "DESC", sql_select_limit (max), first); g_free (quoted_uuid); g_free (columns); g_free (clause); } /** * @brief Get the UUID from a setting iterator. * * @param[in] iterator Iterator. * * @return The UUID of the setting, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (setting_iterator_uuid, 1); /** * @brief Get the name from a setting iterator. * * @param[in] iterator Iterator. * * @return The name of the setting, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (setting_iterator_name, 2); /** * @brief Get the comment from a setting iterator. * * @param[in] iterator Iterator. * * @return The comment of the setting, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (setting_iterator_comment, 3); /** * @brief Get the value from a setting iterator. * * @param[in] iterator Iterator. * * @return The value of the setting, or NULL if iteration is complete. Freed by * cleanup_iterator. */ DEF_ACCESS (setting_iterator_value, 4); /** * @brief Get the value of a setting as a string. * * @param[in] uuid UUID of setting. * @param[out] value Freshly allocated value. * * @return 0 success, -1 error. */ int setting_value (const char *uuid, char **value) { gchar *quoted_uuid; if (value == NULL || uuid == NULL) return -1; quoted_uuid = sql_quote (uuid); if (sql_int ("SELECT count (*)" " FROM settings" " WHERE uuid = '%s'" " AND " ACL_GLOBAL_OR_USER_OWNS () ";", quoted_uuid, current_credentials.uuid) == 0) { *value = NULL; g_free (quoted_uuid); return -1; } *value = sql_string ("SELECT value" " FROM settings" " WHERE uuid = '%s'" " AND " ACL_GLOBAL_OR_USER_OWNS () /* Force the user's setting to come before the default. */ " ORDER BY coalesce (owner, 0) DESC;", quoted_uuid, current_credentials.uuid); g_free (quoted_uuid); return 0; } /** * @brief Get the value of a setting. * * @param[in] uuid UUID of setting. * @param[out] value Value. * * @return 0 success, -1 error. */ static int setting_value_int (const char *uuid, int *value) { gchar *quoted_uuid; if (value == NULL || uuid == NULL) return -1; quoted_uuid = sql_quote (uuid); if (sql_int ("SELECT count (*)" " FROM settings" " WHERE uuid = '%s'" " AND " ACL_GLOBAL_OR_USER_OWNS () ";", quoted_uuid, current_credentials.uuid) == 0) { *value = -1; g_free (quoted_uuid); return -1; } *value = sql_int ("SELECT value" " FROM settings" " WHERE uuid = '%s'" " AND " ACL_GLOBAL_OR_USER_OWNS () /* Force the user's setting to come before the default. */ " ORDER BY coalesce (owner, 0) DESC;", quoted_uuid, current_credentials.uuid); g_free (quoted_uuid); return 0; } /** * @brief Set the value of a setting. * * @param[in] uuid UUID of setting. * @param[in] name Setting name. For Timezone and Password. * @param[in] value_64 New setting value, base64 encoded. * @param[out] r_errdesc If not NULL the address of a variable to receive * a malloced string with the error description. Will * always be set to NULL on success. * * @return 0 success, 1 failed to find setting, 2 syntax error in value, * 99 permission denied, -1 on error. */ int modify_setting (const gchar *uuid, const gchar *name, const gchar *value_64, gchar **r_errdesc) { char *setting_name; assert (current_credentials.uuid); if (acl_user_may ("modify_setting") == 0) return 99; if (r_errdesc) *r_errdesc = NULL; if (name && (strcmp (name, "Timezone") == 0)) { gsize value_size; gchar *quoted_timezone, *value; if (value_64 && strlen (value_64)) { value = (gchar*) g_base64_decode (value_64, &value_size); if (g_utf8_validate (value, value_size, NULL) == FALSE) { if (r_errdesc) *r_errdesc = g_strdup ("Value cannot be decoded to" " valid UTF-8"); g_free (value); return -1; } } else { value = g_strdup (""); value_size = 0; } quoted_timezone = sql_quote (value); g_free (value); sql ("UPDATE users SET timezone = '%s', modification_time = m_now ()" " WHERE uuid = '%s';", quoted_timezone, current_credentials.uuid); g_free (quoted_timezone); return 0; } if (name && (strcmp (name, "Password") == 0)) { gsize value_size; gchar *value; int ret; assert (current_credentials.username); if (value_64 && strlen (value_64)) { value = (gchar*) g_base64_decode (value_64, &value_size); if (g_utf8_validate (value, value_size, NULL) == FALSE) { if (r_errdesc) *r_errdesc = g_strdup ("Value cannot be decoded to" " valid UTF-8"); g_free (value); return -1; } } else { value = g_strdup (""); value_size = 0; } ret = set_password (current_credentials.username, current_credentials.uuid, value, r_errdesc); g_free (value); return ret; } if (uuid && (strcmp (uuid, SETTING_UUID_ROWS_PER_PAGE) == 0 || strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0 || strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0 || strcmp (uuid, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0 || strcmp (uuid, "578a1c14-e2dc-45ef-a591-89d31391d007") == 0 || strcmp (uuid, "02e294fa-061b-11e6-ae64-28d24461215b") == 0 || strcmp (uuid, "5a9046cc-0628-11e6-ba53-28d24461215b") == 0 || strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 0)) { gsize value_size; gchar *value, *quoted_uuid, *quoted_value; assert (current_credentials.username); quoted_uuid = sql_quote (uuid); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '%s'" " AND " ACL_IS_GLOBAL () ";", quoted_uuid, current_credentials.uuid) == 0) { g_free (quoted_uuid); return 1; } if (value_64 && strlen (value_64)) { value = (gchar*) g_base64_decode (value_64, &value_size); if (g_utf8_validate (value, value_size, NULL) == FALSE) { if (r_errdesc) *r_errdesc = g_strdup ("Value cannot be decoded to" " valid UTF-8"); g_free (value); return -1; } } else { value = g_strdup (""); value_size = 0; } if (strcmp (uuid, SETTING_UUID_ROWS_PER_PAGE) == 0) { const gchar *val; /* Rows Per Page. */ val = value; while (*val && isdigit (*val)) val++; if (*val && strcmp (value, "-1")) { g_free (quoted_uuid); return 2; } } if (strcmp (uuid, "6765549a-934e-11e3-b358-406186ea4fc5") == 0) { GRegex *languages_regex; gboolean match; /* * regex: colon-separated lists of language or language and country * codes (ISO 639-1, 639-2 and 3166-1 alpha-2) * as used in the LANGUAGE env variable by gettext */ languages_regex = g_regex_new ("^(Browser Language|" "([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_-]+)?" "(:([a-z]{2,3})(_[A-Z]{2})?(@[[:alnum:]_-]+)?)*)$", 0, 0, NULL); match = g_regex_match (languages_regex, value, 0, NULL); g_regex_unref (languages_regex); /* User Interface Language. */ if (match) { // Valid languages string or "Browser Language": // keep string as it is } /* Legacy full language names */ else if (strcmp (value, "Chinese") == 0) { g_free (value); value = g_strdup ("zh_CN"); } else if (strcmp (value, "English") == 0) { g_free (value); value = g_strdup ("en"); } else if (strcmp (value, "German") == 0) { g_free (value); value = g_strdup ("de"); } /* Invalid value */ else { g_free (quoted_uuid); g_free (value); return 2; } } if (strcmp (uuid, "77ec2444-e7f2-4a80-a59b-f4237782d93f") == 0) { /* Dynamic Severity */ current_credentials.dynamic_severity = atoi (value); reports_clear_count_cache (current_credentials.uuid); } if (strcmp (uuid, "7eda49c5-096c-4bef-b1ab-d080d87300df") == 0) { double severity_dbl; /* Default Severity */ if (sscanf (value, "%lf", &severity_dbl) != 1 || severity_dbl < 0.0 || severity_dbl > 10.0) { g_free (value); return 2; } else current_credentials.default_severity = severity_dbl; } if (strcmp (uuid, "a09285b0-2d47-49b6-a4ef-946ee71f1d5c") == 0) { int value_int; /* Auto Cache Rebuild */ if (sscanf (value, "%d", &value_int) != 1 || (strcmp (value, "0") && strcmp (value, "1"))) { g_free (value); return 2; } } quoted_value = sql_quote (value); g_free (value); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", quoted_uuid, current_credentials.uuid)) sql ("UPDATE settings SET value = '%s'" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", quoted_value, quoted_uuid, current_credentials.uuid); else sql ("INSERT INTO settings (uuid, owner, name, comment, value)" " VALUES" " ('%s'," " (SELECT id FROM users WHERE uuid = '%s')," " (SELECT name FROM settings" " WHERE uuid = '%s' AND " ACL_IS_GLOBAL () " LIMIT 1)," " (SELECT comment FROM settings" " WHERE uuid = '%s' AND " ACL_IS_GLOBAL () " LIMIT 1)," " '%s');", quoted_uuid, current_credentials.uuid, quoted_uuid, quoted_uuid, quoted_value); g_free (quoted_uuid); g_free (quoted_value); return 0; } /* Export file name format */ if (uuid && (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0 || strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0 || strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0)) { gsize value_size; gchar *value, *quoted_value; assert (current_credentials.uuid); if (strcmp (uuid, "a6ac88c5-729c-41ba-ac0a-deea4a3441f2") == 0) setting_name = "Details Export File Name"; else if (strcmp (uuid, "0872a6ed-4f85-48c5-ac3f-a5ef5e006745") == 0) setting_name = "List Export File Name"; else if (strcmp (uuid, "e1a2ae0b-736e-4484-b029-330c9e15b900") == 0) setting_name = "Report Export File Name"; else return -1; if (value_64 && strlen (value_64)) { value = (gchar*) g_base64_decode (value_64, &value_size); if (g_utf8_validate (value, value_size, NULL) == FALSE) { if (r_errdesc) *r_errdesc = g_strdup ("Value cannot be decoded to" " valid UTF-8"); g_free (value); return -1; } } else { value = g_strdup (""); value_size = 0; } quoted_value = sql_quote (value); if (strcmp (value, "") == 0) { g_free (value); g_free (quoted_value); return 2; } if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", uuid, current_credentials.uuid)) sql ("UPDATE settings SET value = '%s'" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", quoted_value, uuid, current_credentials.uuid); else sql ("INSERT INTO settings (uuid, owner, name, comment, value)" " VALUES" " ('%s'," " (SELECT id FROM users WHERE uuid = '%s')," " '%s'," " (SELECT comment FROM settings" " WHERE uuid = '%s' AND " ACL_IS_GLOBAL () ")," " '%s');", uuid, current_credentials.uuid, setting_name, uuid, quoted_value); g_free (value); g_free (quoted_value); return 0; } /* Resources filters, default resource selections and chart preferences. */ setting_name = NULL; if (uuid) { /* Filters */ if (strcmp (uuid, "b833a6f2-dcdc-4535-bfb0-a5154b5b5092") == 0) setting_name = g_strdup ("Alerts Filter"); else if (strcmp (uuid, "0f040d06-abf9-43a2-8f94-9de178b0e978") == 0) setting_name = g_strdup ("Assets Filter"); else if (strcmp (uuid, "1a9fbd91-0182-44cd-bc88-a13a9b3b1bef") == 0) setting_name = g_strdup ("Configs Filter"); else if (strcmp (uuid, "186a5ac8-fe5a-4fb1-aa22-44031fb339f3") == 0) setting_name = g_strdup ("Credentials Filter"); else if (strcmp (uuid, "f9691163-976c-47e7-ad9a-38f2d5c81649") == 0) setting_name = g_strdup ("Filters Filter"); else if (strcmp (uuid, "f722e5a4-88d8-475f-95b9-e4dcafbc075b") == 0) setting_name = g_strdup ("Groups Filter"); else if (strcmp (uuid, "37562dfe-1f7e-4cae-a7c0-fa95e6f194c5") == 0) setting_name = g_strdup ("Hosts Filter"); else if (strcmp (uuid, "96abcd5a-9b6d-456c-80b8-c3221bfa499d") == 0) setting_name = g_strdup ("Notes Filter"); else if (strcmp (uuid, "f608c3ec-ce73-4ff6-8e04-7532749783af") == 0) setting_name = g_strdup ("Operating Systems Filter"); else if (strcmp (uuid, "eaaaebf1-01ef-4c49-b7bb-955461c78e0a") == 0) setting_name = g_strdup ("Overrides Filter"); else if (strcmp (uuid, "ffb16b28-538c-11e3-b8f9-406186ea4fc5") == 0) setting_name = g_strdup ("Permissions Filter"); else if (strcmp (uuid, "7d52d575-baeb-4d98-bb68-e1730dbc6236") == 0) setting_name = g_strdup ("Port Lists Filter"); else if (strcmp (uuid, "48ae588e-9085-41bc-abcb-3d6389cf7237") == 0) setting_name = g_strdup ("Reports Filter"); else if (strcmp (uuid, "249c7a55-065c-47fb-b453-78e11a665565") == 0) setting_name = g_strdup ("Report Formats Filter"); else if (strcmp (uuid, "739ab810-163d-11e3-9af6-406186ea4fc5") == 0) setting_name = g_strdup ("Results Filter"); else if (strcmp (uuid, "f38e673a-bcd1-11e2-a19a-406186ea4fc5") == 0) setting_name = g_strdup ("Roles Filter"); else if (strcmp (uuid, "ba00fe91-bdce-483c-b8df-2372e9774ad6") == 0) setting_name = g_strdup ("Scanners Filter"); else if (strcmp (uuid, "a83e321b-d994-4ae8-beec-bfb5fe3e7336") == 0) setting_name = g_strdup ("Schedules Filter"); else if (strcmp (uuid, "108eea3b-fc61-483c-9da9-046762f137a8") == 0) setting_name = g_strdup ("Tags Filter"); else if (strcmp (uuid, "236e2e41-9771-4e7a-8124-c432045985e0") == 0) setting_name = g_strdup ("Targets Filter"); else if (strcmp (uuid, "1c981851-8244-466c-92c4-865ffe05e721") == 0) setting_name = g_strdup ("Tasks Filter"); else if (strcmp (uuid, "801544de-f06d-4377-bb77-bbb23369bad4") == 0) setting_name = g_strdup ("Tickets Filter"); else if (strcmp (uuid, "34a176c1-0278-4c29-b84d-3d72117b2169") == 0) setting_name = g_strdup ("TLS Certificates Filter"); else if (strcmp (uuid, "a33635be-7263-4549-bd80-c04d2dba89b4") == 0) setting_name = g_strdup ("Users Filter"); else if (strcmp (uuid, "17c9d269-95e7-4bfa-b1b2-bc106a2175c7") == 0) setting_name = g_strdup ("Vulnerabilities Filter"); else if (strcmp (uuid, "3414a107-ae46-4dea-872d-5c4479a48e8f") == 0) setting_name = g_strdup ("CPE Filter"); else if (strcmp (uuid, "def63b5a-41ef-43f4-b9ef-03ef1665db5d") == 0) setting_name = g_strdup ("CVE Filter"); else if (strcmp (uuid, "bef08b33-075c-4f8c-84f5-51f6137e40a3") == 0) setting_name = g_strdup ("NVT Filter"); else if (strcmp (uuid, "adb6ffc8-e50e-4aab-9c31-13c741eb8a16") == 0) setting_name = g_strdup ("OVAL Filter"); else if (strcmp (uuid, "e4cf514a-17e2-4ab9-9c90-336f15e24750") == 0) setting_name = g_strdup ("CERT-Bund Filter"); else if (strcmp (uuid, "312350ed-bc06-44f3-8b3f-ab9eb828b80b") == 0) setting_name = g_strdup ("DFN-CERT Filter"); else if (strcmp (uuid, "32b3d606-461b-4770-b3e1-b9ea3cf0f84c") == 0) setting_name = g_strdup ("Notes Filter"); else if (strcmp (uuid, "956d13bd-3baa-4404-a138-5e7eb8f9630e") == 0) setting_name = g_strdup ("Overrides Filter"); /* Content composer defaults */ else if (strcmp (uuid, "b6b449ee-5d90-4ff0-af20-7e838c389d39") == 0) setting_name = g_strdup ("Report Composer Defaults"); /* Default resource selections */ else if (strcmp (uuid, "f9f5a546-8018-48d0-bef5-5ad4926ea899") == 0) setting_name = g_strdup ("Default Alert"); else if (strcmp (uuid, "fe7ea321-e3e3-4cc6-9952-da836aae83ce") == 0) setting_name = g_strdup ("Default OpenVAS Scan Config"); else if (strcmp (uuid, "fb19ac4b-614c-424c-b046-0bc32bf1be73") == 0) setting_name = g_strdup ("Default OSP Scan Config"); else if (strcmp (uuid, "6fc56b72-c1cf-451c-a4c4-3a9dc784c3bd") == 0) setting_name = g_strdup ("Default SSH Credential"); else if (strcmp (uuid, "a25c0cfe-f977-417b-b1da-47da370c03e8") == 0) setting_name = g_strdup ("Default SMB Credential"); else if (strcmp (uuid, "83545bcf-0c49-4b4c-abbf-63baf82cc2a7") == 0) setting_name = g_strdup ("Default ESXi Credential"); else if (strcmp (uuid, "024550b8-868e-4b3c-98bf-99bb732f6a0d") == 0) setting_name = g_strdup ("Default SNMP Credential"); else if (strcmp (uuid, "d74a9ee8-7d35-4879-9485-ab23f1bd45bc") == 0) setting_name = g_strdup ("Default Port List"); else if (strcmp (uuid, "f7d0f6ed-6f9e-45dc-8bd9-05cced84e80d") == 0) setting_name = g_strdup ("Default OpenVAS Scanner"); else if (strcmp (uuid, "b20697c9-be0a-4cd4-8b4d-5fe7841ebb03") == 0) setting_name = g_strdup ("Default OSP Scanner"); else if (strcmp (uuid, "353304fc-645e-11e6-ba7a-28d24461215b") == 0) setting_name = g_strdup ("Default Report Format"); else if (strcmp (uuid, "778eedad-5550-4de0-abb6-1320d13b5e18") == 0) setting_name = g_strdup ("Default Schedule"); else if (strcmp (uuid, "23409203-940a-4b4a-b70c-447475f18323") == 0) setting_name = g_strdup ("Default Target"); /* * Main dashboard */ else if (strcmp (uuid, "d97eca9f-0386-4e5d-88f2-0ed7f60c0646") == 0) setting_name = g_strdup ("Main Dashboard Configuration"); /* * Scans dashboards */ else if (strcmp (uuid, "c7584d7c-649f-4f8b-9ded-9e1dc20f24c8") == 0) setting_name = g_strdup ("Scans Dashboard Configuration"); /* Tasks dashboard settings */ else if (strcmp (uuid, "3d5db3c7-5208-4b47-8c28-48efc621b1e0") == 0) setting_name = g_strdup ("Tasks Top Dashboard Configuration"); /* Reports dashboard settings */ else if (strcmp (uuid, "e599bb6b-b95a-4bb2-a6bb-fe8ac69bc071") == 0) setting_name = g_strdup ("Reports Top Dashboard Configuration"); /* Results dashboard settings */ else if (strcmp (uuid, "0b8ae70d-d8fc-4418-8a72-e65ac8d2828e") == 0) setting_name = g_strdup ("Results Top Dashboard Configuration"); /* Vulns dashboard settings */ else if (strcmp (uuid, "43690dcb-3174-4d84-aa88-58c1936c7f5c") == 0) setting_name = g_strdup ("Vulnerabilities Top Dashboard Configuration"); /* Notes dashboard settings */ else if (strcmp (uuid, "ce7b121-c609-47b0-ab57-fd020a0336f4a") == 0) setting_name = g_strdup ("Notes Top Dashboard Configuration"); /* Overrides dashboard settings */ else if (strcmp (uuid, "054862fe-0781-4527-b1aa-2113bcd16ce7") == 0) setting_name = g_strdup ("Overrides Top Dashboard Configuration"); /* * Assets dashboards */ else if (strcmp (uuid, "0320e0db-bf30-4d4f-9379-b0a022d07cf7") == 0) setting_name = g_strdup ("Assets Dashboard Configuration"); /* Hosts dashboard settings */ else if (strcmp (uuid, "d3f5f2de-a85b-43f2-a817-b127457cc8ba") == 0) setting_name = g_strdup ("Hosts Top Dashboard Configuration"); /* TLS Certificate dashboard settings */ else if (strcmp (uuid, "9b62bf16-bf90-11e9-ad97-28d24461215b") == 0) setting_name = g_strdup ("TLS Certificates Top Dashboard Configuration"); /* Operating Systems dashboard settings */ else if (strcmp (uuid, "e93b51ed-5881-40e0-bc4f-7d3268a36177") == 0) setting_name = g_strdup ("OSs Top Dashboard Configuration"); /* * SecInfo dashboards */ else if (strcmp (uuid, "84ab32da-fe69-44d8-8a8f-70034cf28d4e") == 0) setting_name = g_strdup ("SecInfo Dashboard Configuration"); /* NVTs dashboard settings */ else if (strcmp (uuid, "f68d9369-1945-477b-968f-121c6029971b") == 0) setting_name = g_strdup ("NVTs Top Dashboard Configuration"); /* CVEs dashboard settings */ else if (strcmp (uuid, "815ddd2e-8654-46c7-a05b-d73224102240") == 0) setting_name = g_strdup ("CVEs Top Dashboard Configuration"); /* CPEs dashboard settings */ else if (strcmp (uuid, "9cff9b4d-b164-43ce-8687-f2360afc7500") == 0) setting_name = g_strdup ("CPEs Top Dashboard Configuration"); /* OVAL Definitions dashboard settings */ else if (strcmp (uuid, "9563efc0-9f4e-4d1f-8f8d-0205e32b90a4") == 0) setting_name = g_strdup ("OVAL Definitions Top Dashboard Configuration"); /* CERT-Bund Advisories dashboard settings */ else if (strcmp (uuid, "a6946f44-480f-4f37-8a73-28a4cd5310c4") == 0) setting_name = g_strdup ("CERT-Bund Advisories Top Dashboard" " Configuration"); /* DFN-CERT Advisories */ else if (strcmp (uuid, "9812ea49-682d-4f99-b3cc-eca051d1ce59") == 0) setting_name = g_strdup ("DFN-CERT Advisories Top Dashboard" " Configuration"); /* All SecInfo */ else if (strcmp (uuid, "4c7b1ea7-b7e6-4d12-9791-eb9f72b6f864") == 0) setting_name = g_strdup ("All SecInfo Top Dashboard Configuration"); /* * Resilience / Remediation dashboards */ /* Tickets */ else if (strcmp (uuid, "70b0626f-a835-478e-8194-e09f97887a15") == 0) setting_name = g_strdup ("Tickets Top Dashboard Configuration"); /* Business Process Model (BPM) */ else if (strcmp (uuid, "3232d608-e5bb-415e-99aa-019f16eede8d") == 0) setting_name = g_strdup ("BPM Dashboard Configuration"); /* * Client data for Business Process Modeling (BPM) */ else if (strcmp (uuid, "3ce2d136-bb52-448a-93f0-20069566f877") == 0) setting_name = g_strdup ("BPM Data"); } if (setting_name) { gchar *quoted_value, *value; gsize value_size; assert (current_credentials.username); if (value_64 && strlen (value_64)) { value = (gchar*) g_base64_decode (value_64, &value_size); if (g_utf8_validate (value, value_size, NULL) == FALSE) { if (r_errdesc) *r_errdesc = g_strdup ("Value cannot be decoded to" " valid UTF-8"); g_free (value); return -1; } } else { value = g_strdup (""); value_size = 0; } quoted_value = sql_quote (value); if (sql_int ("SELECT count(*) FROM settings" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", uuid, current_credentials.uuid)) sql ("UPDATE settings SET value = '%s'" " WHERE uuid = '%s'" " AND owner = (SELECT id FROM users WHERE uuid = '%s');", quoted_value, uuid, current_credentials.uuid); else sql ("INSERT INTO settings (uuid, owner, name, comment, value)" " VALUES" " ('%s'," " (SELECT id FROM users WHERE uuid = '%s')," " '%s'," " (SELECT comment FROM settings" " WHERE uuid = '%s' AND " ACL_IS_GLOBAL () ")," " '%s');", uuid, current_credentials.uuid, setting_name, uuid, quoted_value); g_free (value); g_free (quoted_value); return 0; } return 1; } /** * @brief Return max, adjusted according to maximum allowed rows. * * @param[in] max Max. * * @return Adjusted max. */ int manage_max_rows (int max) { int max_rows; if (current_credentials.uuid == NULL) return max; if (ignore_max_rows_per_page || setting_value_int (SETTING_UUID_MAX_ROWS_PER_PAGE, &max_rows)) return max; if (max_rows && (max < 0 || max > max_rows)) return max_rows; return max; } /** * @brief Get the name of a setting. * * @param[in] uuid UUID of setting. * * @return Setting name. */ static const gchar * setting_name (const gchar *uuid) { if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0) return "Default CA Cert"; if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0) return "Max Rows Per Page"; if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0) return "Debian LSC Package Maintainer"; if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0) return "Feed Import Owner"; if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0) return "Feed Import Roles"; return NULL; } /** * @brief Check whether a setting is the Default CA Cert setting. * * @param[in] uuid UUID of setting. * * @return 1 if Default CA Cert, else 0. */ int setting_is_default_ca_cert (const gchar *uuid) { return strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0; } /** * @brief Get the description of a setting. * * @param[in] uuid UUID of setting. * * @return Setting description. */ static const gchar * setting_description (const gchar *uuid) { if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0) return "Default CA Certificate for Scanners"; if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0) return "The default maximum number of rows displayed in any listing."; if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0) return "Maintainer email address used in generated Debian LSC packages."; if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0) return "User who is given ownership of new resources from feed."; if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0) return "Roles given access to new resources from feed."; return NULL; } /** * @brief Get the name of a setting. * * @param[in] uuid UUID of setting. * @param[in] value Value of setting, to verify. * @param[in] user User setting is to apply to, or NULL. * * @return 0 if valid, else 1. */ static int setting_verify (const gchar *uuid, const gchar *value, const gchar *user) { if (value == NULL) return 0; if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0) return 0; if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0) { int max_rows; max_rows = atoi (value); if (user) { if (max_rows < -1) return 1; } else if (max_rows < 0) return 1; } if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0) { if (g_regex_match_simple ("^([[:alnum:]-_]*@[[:alnum:]-_][[:alnum:]-_.]*)?$", value, 0, 0) == FALSE) return 1; } if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0 && strlen (value)) { user_t value_user; gchar *quoted_uuid; quoted_uuid = sql_quote (value); switch (sql_int64 (&value_user, "SELECT id FROM users WHERE uuid = '%s';", quoted_uuid)) { case 0: break; case 1: /* Too few rows in result of query. */ g_free (quoted_uuid); return 1; default: /* Programming error. */ assert (0); case -1: g_free (quoted_uuid); return 1; } g_free (quoted_uuid); } if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0) { gchar **split, **point; point = split = g_strsplit (value, ",", 0); while (*point) { if (g_regex_match_simple ("^[-0123456789abcdefABCDEF]{36}$", g_strstrip (*point), 0, 0) == FALSE) { g_strfreev (split); return 1; } point++; } g_strfreev (split); } return 0; } /** * @brief Normalise the value of a setting. * * @param[in] uuid UUID of setting. * @param[in] value Value of setting, to verify. * * @return Normalised value. */ static gchar * setting_normalise (const gchar *uuid, const gchar *value) { if (value == NULL) return NULL; if (strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) == 0) { int max_rows; max_rows = atoi (value); if (max_rows < 0) return NULL; return g_strdup_printf ("%i", max_rows); } if (strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) == 0) { return g_strstrip (g_strdup (value)); } if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0) { GString *normalised; gchar **split, **point; normalised = g_string_new (""); point = split = g_strsplit (value, ",", 0); while (*point) { g_string_append_printf (normalised, "%s%s", point == split ? "" : ",", g_strstrip (*point)); point++; } g_strfreev (split); g_string_ascii_down (normalised); return g_string_free (normalised, FALSE); } return g_strdup (value); } /** * @brief Change value of a setting. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of user. * @param[in] uuid UUID of setting. * @param[in] value New value. * * @return 0 success, 1 failed to find user, 2 value out of range, 3 error in * setting uuid, 4 modifying setting for a single user forbidden, * 5 syntax error in setting value, -1 error. */ int manage_modify_setting (GSList *log_config, const db_conn_info_t *database, const gchar *name, const gchar *uuid, const char *value) { int ret; gchar *quoted_name, *quoted_description, *quoted_value, *normalised; g_info (" Modifying setting."); if (strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) && strcmp (uuid, SETTING_UUID_MAX_ROWS_PER_PAGE) && strcmp (uuid, SETTING_UUID_LSC_DEB_MAINTAINER) && strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) && strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES)) { fprintf (stderr, "Error in setting UUID.\n"); return 3; } ret = manage_option_setup (log_config, database); if (ret) return ret; sql_begin_immediate (); if (setting_verify (uuid, value, name)) { sql_rollback (); fprintf (stderr, "Syntax error in setting value.\n"); manage_option_cleanup (); return 5; } if (name) { user_t user; if ((strcmp (uuid, SETTING_UUID_DEFAULT_CA_CERT) == 0) || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0) || (strcmp (uuid, SETTING_UUID_FEED_IMPORT_ROLES) == 0)) { sql_rollback (); fprintf (stderr, "Modifying this setting for a single user is forbidden.\n"); manage_option_cleanup (); return 4; } if (find_user_by_name (name, &user)) { sql_rollback (); fprintf (stderr, "Internal error.\n"); manage_option_cleanup (); return -1; } if (user == 0) { sql_rollback (); fprintf (stderr, "Failed to find user.\n"); manage_option_cleanup (); return 1; } sql ("DELETE FROM settings" " WHERE uuid = '%s'" " AND owner = %llu;", uuid, user); normalised = setting_normalise (uuid, value); if (normalised) { quoted_value = sql_quote (normalised); g_free (normalised); quoted_name = sql_quote (setting_name (uuid)); quoted_description = sql_quote (setting_description (uuid)); sql ("INSERT INTO settings (uuid, owner, name, comment, value)" " VALUES ('%s', %llu, '%s', '%s', '%s');", uuid, user, quoted_name, quoted_description, quoted_value); g_free (quoted_value); g_free (quoted_name); g_free (quoted_description); } } else { sql ("DELETE FROM settings" " WHERE uuid = '%s'" " AND owner IS NULL;", uuid); normalised = setting_normalise (uuid, value); if (normalised) { quoted_value = sql_quote (normalised); g_free (normalised); quoted_name = sql_quote (setting_name (uuid)); quoted_description = sql_quote (setting_description (uuid)); sql ("INSERT INTO settings (uuid, owner, name, comment, value)" " VALUES ('%s', NULL, '%s', '%s', '%s');", uuid, quoted_name, quoted_description, quoted_value); g_free (quoted_value); g_free (quoted_name); g_free (quoted_description); if (strcmp (uuid, SETTING_UUID_FEED_IMPORT_OWNER) == 0) { migrate_predefined_configs (); migrate_predefined_port_lists (); if (migrate_predefined_report_formats ()) { sql_rollback (); manage_option_cleanup (); return -1; } } } } sql_commit (); manage_option_cleanup (); return 0; } /** * @brief Get the default CA cert. * * @return Freshly allocated value of Default CA Cert setting. */ char * manage_default_ca_cert () { return sql_string ("SELECT value FROM settings" " WHERE uuid = '" SETTING_UUID_DEFAULT_CA_CERT "';"); } /* Users. */ /** * @brief Create the given user. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of user. * @param[in] password Password for user. Autogenerated if NULL. * @param[in] role_name Role of user. Admin if NULL. * * @return 0 success, -1 error, * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_create_user (GSList *log_config, const db_conn_info_t *database, const gchar *name, const gchar *password, const gchar *role_name) { char *uuid; array_t *roles; int ret; gchar *rejection_msg; g_info (" Creating user."); ret = manage_option_setup (log_config, database); if (ret) return ret; roles = make_array (); if (role_name) { role_t role; if (find_role_by_name (role_name, &role)) { array_free (roles); fprintf (stderr, "Internal Error.\n"); manage_option_cleanup (); return -1; } if (role == 0) { array_free (roles); fprintf (stderr, "Failed to find role.\n"); manage_option_cleanup (); return -1; } array_add (roles, role_uuid (role)); } else array_add (roles, g_strdup (ROLE_UUID_ADMIN)); if (password) uuid = NULL; else uuid = gvm_uuid_make (); /* Setup a dummy user, so that create_user will work. */ current_credentials.uuid = ""; ret = create_user (name, password ? password : uuid, "", NULL, 0, NULL, 0, NULL, NULL, NULL, roles, NULL, &rejection_msg, NULL, 0); switch (ret) { case 0: if (password) printf ("User created.\n"); else printf ("User created with password '%s'.\n", uuid); break; case -2: fprintf (stderr, "User exists already.\n"); break; default: if (rejection_msg) { fprintf (stderr, "Failed to create user: %s\n", rejection_msg); } else fprintf (stderr, "Failed to create user.\n"); break; } current_credentials.uuid = NULL; g_free (rejection_msg); array_free (roles); free (uuid); manage_option_cleanup (); return ret; } /** * @brief Delete the given user. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of user. * @param[in] inheritor_name Name of user that inherits user's resources. * * @return 0 success, 2 failed to find user, 4 user has active tasks, -1 error. * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_delete_user (GSList *log_config, const db_conn_info_t *database, const gchar *name, const gchar *inheritor_name) { int ret; g_info (" Deleting user."); ret = manage_option_setup (log_config, database); if (ret) return ret; /* Setup a dummy user, so that delete_user will work. */ current_credentials.uuid = ""; switch ((ret = delete_user (NULL, name, 1, 0, NULL, inheritor_name))) { case 0: printf ("User deleted.\n"); break; case 2: fprintf (stderr, "Failed to find user.\n"); break; case 4: fprintf (stderr, "User has active tasks.\n"); break; case 6: fprintf (stderr, "Inheritor not found.\n"); break; case 7: fprintf (stderr, "Inheritor same as deleted user.\n"); break; case 8: fprintf (stderr, "Invalid inheritor.\n"); break; case 9: fprintf (stderr, "Resources owned by the user are still in use by others.\n"); break; case 10: fprintf (stderr, "User is Feed Import Owner.\n"); break; default: fprintf (stderr, "Internal Error.\n"); break; } current_credentials.uuid = NULL; manage_option_cleanup (); return ret; } /** * @brief List users. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] role_name Role name. * @param[in] verbose Whether to print UUID. * * @return 0 success, -1 error. */ int manage_get_users (GSList *log_config, const db_conn_info_t *database, const gchar* role_name, int verbose) { iterator_t users; int ret; g_info (" Getting users."); ret = manage_option_setup (log_config, database); if (ret) return ret; if (role_name) { role_t role; if (find_role_by_name (role_name, &role)) { fprintf (stderr, "Internal Error.\n"); manage_option_cleanup (); return -1; } if (role == 0) { fprintf (stderr, "Failed to find role.\n"); manage_option_cleanup (); return -1; } init_iterator (&users, "SELECT name, uuid FROM users" " WHERE id IN (SELECT \"user\" FROM role_users" " WHERE role = %llu);", role); } else init_iterator (&users, "SELECT name, uuid FROM users;"); while (next (&users)) if (verbose) printf ("%s %s\n", iterator_string (&users, 0), iterator_string (&users, 1)); else printf ("%s\n", iterator_string (&users, 0)); cleanup_iterator (&users); manage_option_cleanup (); return 0; } /** * @brief Set the password of a user. * * @param[in] name Name of user. * @param[in] uuid User UUID. * @param[in] password New password. * @param[out] r_errdesc Address to receive a malloced string with the error * description, or NULL. * * @return 0 success, -1 error. */ static int set_password (const gchar *name, const gchar *uuid, const gchar *password, gchar **r_errdesc) { gchar *errstr, *hash; assert (name && uuid); if ((errstr = gvm_validate_password (password, name))) { g_warning ("new password for '%s' rejected: %s", name, errstr); if (r_errdesc) *r_errdesc = errstr; else g_free (errstr); return -1; } hash = manage_authentication_hash (password); sql ("UPDATE users SET password = '%s', modification_time = m_now ()" " WHERE uuid = '%s';", hash, uuid); g_free (hash); return 0; } /** * @brief Set the password of a user. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of user. * @param[in] password New password. * * @return 0 success, -1 error. */ int manage_set_password (GSList *log_config, const db_conn_info_t *database, const gchar *name, const gchar *password) { user_t user; char *uuid; int ret; gchar *rejection_msg; g_info (" Modifying user password."); if (name == NULL) { fprintf (stderr, "--user required.\n"); return -1; } ret = manage_option_setup (log_config, database); if (ret) return ret; sql_begin_immediate (); if (find_user_by_name (name, &user)) { fprintf (stderr, "Internal error.\n"); goto fail; } if (user == 0) { fprintf (stderr, "Failed to find user.\n"); goto fail; } uuid = user_uuid (user); if (uuid == NULL) { fprintf (stderr, "Failed to allocate UUID.\n"); goto fail; } rejection_msg = NULL; if (set_password (name, uuid, password, &rejection_msg)) { if (rejection_msg) { fprintf (stderr, "New password rejected: %s\n", rejection_msg); g_free (rejection_msg); } else fprintf (stderr, "New password rejected.\n"); free (uuid); goto fail; } sql_commit (); free (uuid); manage_option_cleanup (); return ret; fail: sql_rollback (); manage_option_cleanup (); return -1; } /** * @brief Find a user for a specific permission, given a UUID. * * @param[in] uuid UUID of user. * @param[out] user User return, 0 if successfully failed to find user. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find user), TRUE on error. */ static gboolean find_user_with_permission (const char* uuid, user_t* user, const char *permission) { return find_resource_with_permission ("user", uuid, user, permission, 0); } /** * @brief Find a user given a name. * * @param[in] name A user name. * @param[out] user User return, 0 if successfully failed to find user. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find user), TRUE on error. */ gboolean find_user_by_name_with_permission (const char* name, user_t *user, const char *permission) { return find_resource_by_name_with_permission ("user", name, user, permission); } /** * @brief Find a user given a name. * * @param[in] name A user name. * @param[out] user User return, 0 if successfully failed to find user. * * @return FALSE on success (including if failed to find user), TRUE on error. */ static gboolean find_user_by_name (const char* name, user_t *user) { return find_resource_by_name ("user", name, user); } /** * @brief Adds a new user to the GVM installation. * * @todo Adding users authenticating with certificates is not yet implemented. * * @param[in] name The name of the new user. * @param[in] password The password of the new user. * @param[in] comment Comment for the new user or NULL. * @param[in] hosts The host the user is allowed/forbidden to scan. * @param[in] hosts_allow Whether hosts is allow or forbid. * @param[in] ifaces Interfaces the user is allowed/forbidden to scan. * @param[in] ifaces_allow Whether ifaces is allow or forbid. * @param[in] allowed_methods Allowed login methods. * @param[in] groups Groups. * @param[out] group_id_return ID of group on "failed to find" error. * @param[in] roles Roles. * @param[out] role_id_return ID of role on "failed to find" error. * @param[out] r_errdesc If not NULL the address of a variable to receive * a malloced string with the error description. Will * always be set to NULL on success. * @param[out] new_user Created user. * @param[in] forbid_super_admin Whether to forbid creation of Super Admin. * * @return 0 if the user has been added successfully, 1 failed to find group, * 2 failed to find role, 3 syntax error in hosts, 99 permission denied, * -1 on error, -2 if user exists already. */ int create_user (const gchar * name, const gchar * password, const gchar *comment, const gchar * hosts, int hosts_allow, const gchar *ifaces, int ifaces_allow, const array_t * allowed_methods, array_t *groups, gchar **group_id_return, array_t *roles, gchar **role_id_return, gchar **r_errdesc, user_t *new_user, int forbid_super_admin) { char *errstr, *uuid; gchar *quoted_hosts, *quoted_ifaces, *quoted_method, *quoted_name, *hash; gchar *quoted_comment, *clean, *generated; int index, max, ret; user_t user; GArray *cache_users; assert (name); assert (password); if (r_errdesc) *r_errdesc = NULL; /* allowed_methods is a NULL terminated array. */ if (allowed_methods && (allowed_methods->len > 2)) return -3; if (allowed_methods && (allowed_methods->len == 0)) allowed_methods = NULL; // TODO validate methods single source, one of ldap, ... if (validate_username (name) != 0) { g_warning ("Invalid characters in user name!"); if (r_errdesc) *r_errdesc = g_strdup ("Invalid characters in user name"); return -1; } if (allowed_methods && (!strcmp (g_ptr_array_index (allowed_methods, 0), "ldap_connect") || !strcmp (g_ptr_array_index (allowed_methods, 0), "radius_connect"))) password = generated = gvm_uuid_make (); else generated = NULL; if ((errstr = gvm_validate_password (password, name))) { g_warning ("new password for '%s' rejected: %s", name, errstr); if (r_errdesc) *r_errdesc = errstr; else g_free (errstr); g_free (generated); return -1; } sql_begin_immediate (); if (acl_user_may ("create_user") == 0) { sql_rollback (); g_free (generated); return 99; } /* Check if user exists already. */ if (resource_with_name_exists_global (name, "user", 0)) { sql_rollback (); g_free (generated); return -2; } quoted_name = sql_quote (name); /* Check hosts. */ max = manage_max_hosts (); manage_set_max_hosts (MANAGE_USER_MAX_HOSTS); if (hosts && (manage_count_hosts (hosts, NULL) < 0)) { manage_set_max_hosts (max); sql_rollback (); g_free (generated); return 3; } manage_set_max_hosts (max); /* Get the password hashes. */ hash = manage_authentication_hash (password); /* Get the quoted comment */ if (comment) quoted_comment = sql_quote (comment); else quoted_comment = g_strdup (""); /* Add the user to the database. */ clean = clean_hosts (hosts ? hosts : "", &max); quoted_hosts = sql_quote (clean); quoted_ifaces = sql_quote (ifaces ? ifaces : ""); g_free (clean); quoted_method = sql_quote (allowed_methods ? g_ptr_array_index (allowed_methods, 0) : "file"); ret = sql_error ("INSERT INTO users" " (uuid, owner, name, password, comment, hosts, hosts_allow," " ifaces, ifaces_allow, method, creation_time," " modification_time)" " VALUES" " (make_uuid ()," " (SELECT id FROM users WHERE uuid = '%s')," " '%s', '%s', '%s', '%s', %i," " '%s', %i, '%s', m_now ()," " m_now ());", current_credentials.uuid, quoted_name, hash, quoted_comment, quoted_hosts, hosts_allow, quoted_ifaces, ifaces_allow, quoted_method); g_free (generated); g_free (hash); g_free (quoted_comment); g_free (quoted_hosts); g_free (quoted_ifaces); g_free (quoted_method); g_free (quoted_name); if (ret == 3) { sql_rollback (); return -2; } else if (ret) { sql_rollback (); return -1; } user = sql_last_insert_id (); /* Add the user to any given groups. */ index = 0; while (groups && (index < groups->len)) { gchar *group_id; group_t group; group_id = (gchar*) g_ptr_array_index (groups, index); if (strcmp (group_id, "0") == 0) { index++; continue; } if (find_group_with_permission (group_id, &group, "modify_group")) { sql_rollback (); return -1; } if (group == 0) { sql_rollback (); if (group_id_return) *group_id_return = group_id; return 1; } sql ("INSERT INTO group_users (\"group\", \"user\") VALUES (%llu, %llu);", group, user); index++; } /* Add the user to any given roles. */ index = 0; while (roles && (index < roles->len)) { gchar *role_id; role_t role; role_id = (gchar*) g_ptr_array_index (roles, index); if (strcmp (role_id, "0") == 0) { index++; continue; } if (forbid_super_admin && acl_role_can_super_everyone (role_id)) { sql_rollback (); return 99; } if (find_role_with_permission (role_id, &role, "get_roles")) { sql_rollback (); return -1; } if (role == 0) { sql_rollback (); if (role_id_return) *role_id_return = role_id; return 2; } sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);", role, user); index++; } if (new_user) *new_user = user; /* Ensure the user can see themself. */ uuid = user_uuid (user); if (uuid == NULL) { g_warning ("%s: Failed to allocate UUID", __func__); sql_rollback (); return -1; } create_permission_internal (1, "GET_USERS", "Automatically created when adding user", NULL, uuid, "user", uuid, NULL); free (uuid); /* Cache permissions. */ cache_users = g_array_new (TRUE, TRUE, sizeof (user_t)); g_array_append_val (cache_users, user); cache_all_permissions_for_users (cache_users); g_free (g_array_free (cache_users, TRUE)); sql_commit (); return 0; } /** * @brief Create a user from an existing user. * * @param[in] name Name of new user. NULL to copy from existing. * @param[in] comment Comment on new user. NULL to copy from existing. * @param[in] user_id UUID of existing user. * @param[out] new_user New user. * * @return 0 success, 1 user exists already, 2 failed to find existing * user, 99 permission denied, -1 error. */ int copy_user (const char* name, const char* comment, const char *user_id, user_t* new_user) { user_t user; int ret; gchar *quoted_uuid; GArray *cache_users; char *uuid; if (acl_user_can_super_everyone (user_id)) return 99; sql_begin_immediate (); ret = copy_resource_lock ("user", name, comment, user_id, "password, timezone, hosts, hosts_allow," " ifaces, ifaces_allow, method", 1, &user, NULL); if (ret) { sql_rollback (); return ret; } sql ("UPDATE users SET password = NULL WHERE id = %llu;", user); quoted_uuid = sql_quote (user_id); sql ("INSERT INTO group_users (\"user\", \"group\")" " SELECT %llu, \"group\" FROM group_users" " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');", user, quoted_uuid); sql ("INSERT INTO role_users (\"user\", role)" " SELECT %llu, role FROM role_users" " WHERE \"user\" = (SELECT id FROM users WHERE uuid = '%s');", user, quoted_uuid); g_free (quoted_uuid); /* Ensure the user can see themself. */ uuid = user_uuid (user); if (uuid == NULL) { g_warning ("%s: Failed to allocate UUID", __func__); sql_rollback (); return -1; } create_permission_internal (1, "GET_USERS", "Automatically created when adding user", NULL, uuid, "user", uuid, NULL); free (uuid); /* Cache permissions. */ cache_users = g_array_new (TRUE, TRUE, sizeof (user_t)); g_array_append_val (cache_users, user); cache_all_permissions_for_users (cache_users); g_free (g_array_free (cache_users, TRUE)); sql_commit (); if (new_user) *new_user = user; return ret; } /** * @brief Delete a user. * * @param[in] user_id_arg UUID of user. * @param[in] name_arg Name of user. Overridden by user_id. * @param[in] ultimate Whether to remove entirely, or to trashcan. * @param[in] forbid_super_admin Whether to forbid removal of Super Admin. * @param[in] inheritor_id UUID of user who will inherit owned objects. * @param[in] inheritor_name Name of user who will inherit owned objects. * * @return 0 success, 2 failed to find user, 4 user has active tasks, * 5 attempted suicide, 6 inheritor not found, 7 inheritor same as * deleted user, 8 invalid inheritor, 9 resources still in use, * 10 user is 'Feed Import Owner' 99 permission denied, -1 error. */ int delete_user (const char *user_id_arg, const char *name_arg, int ultimate, int forbid_super_admin, const char* inheritor_id, const char *inheritor_name) { iterator_t tasks; user_t user, inheritor; get_data_t get; char *current_uuid, *feed_owner_id; gboolean has_rows; iterator_t rows; gchar *deleted_user_id; assert (user_id_arg || name_arg); if (current_credentials.username && current_credentials.uuid) { if (user_id_arg) { if (strcmp (user_id_arg, current_credentials.uuid) == 0) return 5; } else if (name_arg && (strcmp (name_arg, current_credentials.username) == 0)) return 5; } sql_begin_immediate (); if (acl_user_may ("delete_user") == 0) { sql_rollback (); return 99; } user = 0; if (user_id_arg) { if (forbid_super_admin && (strcmp (user_id_arg, ROLE_UUID_SUPER_ADMIN) == 0)) { sql_rollback (); return 99; } if (find_user_with_permission (user_id_arg, &user, "delete_user")) { sql_rollback (); return -1; } } else if (find_user_by_name_with_permission (name_arg, &user, "delete_user")) { sql_rollback (); return -1; } if (user == 0) return 2; setting_value (SETTING_UUID_FEED_IMPORT_OWNER, &feed_owner_id); if (feed_owner_id) { char *uuid; uuid = user_uuid (user); if (strcmp (uuid, feed_owner_id) == 0) { free (uuid); free (feed_owner_id); sql_rollback (); return 10; } free (feed_owner_id); free (uuid); } if (forbid_super_admin) { char *uuid; uuid = user_uuid (user); if (acl_user_is_super_admin (uuid)) { free (uuid); sql_rollback (); return 99; } free (uuid); } /* Fail if there are any active tasks. */ memset (&get, '\0', sizeof (get)); current_uuid = current_credentials.uuid; current_credentials.uuid = sql_string ("SELECT uuid FROM users" " WHERE id = %llu;", user); init_user_task_iterator (&tasks, 0, 1); while (next (&tasks)) switch (task_iterator_run_status (&tasks)) { case TASK_STATUS_DELETE_REQUESTED: case TASK_STATUS_DELETE_ULTIMATE_REQUESTED: case TASK_STATUS_DELETE_ULTIMATE_WAITING: case TASK_STATUS_DELETE_WAITING: case TASK_STATUS_REQUESTED: case TASK_STATUS_RUNNING: case TASK_STATUS_QUEUED: case TASK_STATUS_STOP_REQUESTED: case TASK_STATUS_STOP_WAITING: { cleanup_iterator (&tasks); free (current_credentials.uuid); current_credentials.uuid = current_uuid; sql_rollback (); return 4; } default: break; } cleanup_iterator (&tasks); free (current_credentials.uuid); current_credentials.uuid = current_uuid; /* Check if there's an inheritor. */ if (inheritor_id && strcmp (inheritor_id, "")) { if (strcmp (inheritor_id, "self") == 0) { sql_int64 (&inheritor, "SELECT id FROM users WHERE uuid = '%s'", current_credentials.uuid); if (inheritor == 0) { sql_rollback (); return -1; } } else { if (find_user_with_permission (inheritor_id, &inheritor, "get_users")) { sql_rollback (); return -1; } if (inheritor == 0) { sql_rollback (); return 6; } } } else if (inheritor_name && strcmp (inheritor_name, "")) { if (find_user_by_name_with_permission (inheritor_name, &inheritor, "get_users")) { sql_rollback (); return -1; } if (inheritor == 0) { sql_rollback (); return 6; } } else inheritor = 0; if (inheritor) { gchar *deleted_user_name; gchar *real_inheritor_id, *real_inheritor_name; /* Transfer ownership of objects to the inheritor. */ if (inheritor == user) { sql_rollback (); return 7; } real_inheritor_id = user_uuid (inheritor); /* Only the current user, owned users or global users may inherit. */ if (strcmp (real_inheritor_id, current_credentials.uuid) && sql_int ("SELECT NOT (" ACL_IS_GLOBAL () ")" " FROM users WHERE id = %llu", inheritor) && ! acl_user_owns ("user", inheritor, 0) && sql_int ("SELECT owner != 0 FROM users WHERE id = %llu", inheritor)) { g_free (real_inheritor_id); sql_rollback (); return 8; } deleted_user_id = user_uuid (user); deleted_user_name = user_name (deleted_user_id); real_inheritor_name = user_name (real_inheritor_id); g_log ("event user", G_LOG_LEVEL_MESSAGE, "User %s (%s) is inheriting from %s (%s)", real_inheritor_name, real_inheritor_id, deleted_user_name, deleted_user_id); g_free (deleted_user_name); g_free (real_inheritor_id); g_free (real_inheritor_name); /* Transfer owned resources. */ sql ("UPDATE alerts SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE alerts_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE configs SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE configs_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE credentials SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE credentials_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE host_identifiers SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE host_oss SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE hosts SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE filters SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE filters_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE notes SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE notes_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE oss SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu", inheritor, user); inherit_port_lists (user, inheritor); sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE report_counts SET \"user\" = %llu WHERE \"user\" = %llu", inheritor, user); sql ("UPDATE reports SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE results SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE results_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE overrides SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE overrides_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE permissions SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE permissions_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE scanners SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE scanners_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE schedules SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE schedules_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("DELETE FROM tag_resources" " WHERE resource_type = 'user' AND resource = %llu;", user); sql ("UPDATE tags SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("DELETE FROM tag_resources_trash" " WHERE resource_type = 'user' AND resource = %llu;", user); sql ("UPDATE tags_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE targets SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE targets_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE tasks SET owner = %llu WHERE owner = %llu;", inheritor, user); inherit_tickets (user, inheritor); inherit_tls_certificates (user, inheritor); sql ("UPDATE groups SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE roles SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE users SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE groups_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); sql ("UPDATE roles_trash SET owner = %llu WHERE owner = %llu;", inheritor, user); /* Report Formats. */ has_rows = inherit_report_formats (user, inheritor, &rows); /* Delete user. */ sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user); sql ("DELETE FROM group_users_trash WHERE \"user\" = %llu;", user); sql ("DELETE FROM role_users WHERE \"user\" = %llu;", user); sql ("DELETE FROM role_users_trash WHERE \"user\" = %llu;", user); delete_permissions_cache_for_user (user); sql ("DELETE FROM settings WHERE owner = %llu;", user); sql ("DELETE FROM users WHERE id = %llu;", user); /* Very last: report formats dirs. */ if (deleted_user_id == NULL) g_warning ("%s: deleted_user_id NULL, skipping dirs", __func__); else if (has_rows) do { inherit_report_format_dir (iterator_string (&rows, 0), deleted_user_id, inheritor); } while (next (&rows)); g_free (deleted_user_id); cleanup_iterator (&rows); sql_commit (); return 0; } /* Delete settings and miscellaneous resources not referenced directly. */ /* Settings. */ sql ("DELETE FROM settings WHERE owner = %llu;", user); /* Delete data modifiers (not directly referenced) */ /* Notes. */ sql ("DELETE FROM notes WHERE owner = %llu;", user); sql ("DELETE FROM notes_trash WHERE owner = %llu;", user); /* Overrides. */ sql ("DELETE FROM overrides WHERE owner = %llu;", user); sql ("DELETE FROM overrides_trash WHERE owner = %llu;", user); /* Tags. */ sql ("DELETE FROM tag_resources" " WHERE resource_type = 'user' AND resource = %llu;", user); sql ("DELETE FROM tag_resources" " WHERE tag IN (SELECT id FROM tags WHERE owner = %llu);", user); sql ("DELETE FROM tags WHERE owner = %llu;", user); sql ("DELETE FROM tag_resources_trash" " WHERE resource_type = 'user' AND resource = %llu;", user); sql ("DELETE FROM tag_resources_trash" " WHERE tag IN (SELECT id FROM tags_trash WHERE owner = %llu);", user); sql ("DELETE FROM tags_trash WHERE owner = %llu;", user); delete_tickets_user (user); delete_tls_certificates_user (user); /* Delete assets (not directly referenced). */ /* Hosts. */ sql ("DELETE FROM host_details WHERE host IN" " (SELECT id FROM hosts WHERE owner = %llu);", user); sql ("DELETE FROM host_max_severities WHERE host IN" " (SELECT id FROM hosts WHERE owner = %llu);", user); sql ("DELETE FROM host_identifiers WHERE owner = %llu;", user); sql ("DELETE FROM host_oss WHERE owner = %llu;", user); sql ("DELETE FROM hosts WHERE owner = %llu;", user); /* OSs. */ sql ("DELETE FROM oss WHERE owner = %llu;", user); /* Delete report data and tasks (not directly referenced). */ /* Counts. */ sql ("DELETE FROM report_counts WHERE \"user\" = %llu", user); sql ("DELETE FROM report_counts" " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);", user); /* Hosts. */ sql ("DELETE FROM report_host_details" " WHERE report_host IN (SELECT id FROM report_hosts" " WHERE report IN (SELECT id FROM reports" " WHERE owner = %llu));", user); sql ("DELETE FROM report_hosts" " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);", user); /* Results. */ sql ("DELETE FROM results" " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);", user); sql ("DELETE FROM results_trash" " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);", user); /* Reports. */ sql ("DELETE FROM result_nvt_reports" " WHERE report IN (SELECT id FROM reports WHERE owner = %llu);", user); sql ("DELETE FROM reports WHERE owner = %llu;", user); /* Delete tasks (not directly referenced). */ if (user_resources_in_use (user, "tasks", target_in_use, NULL, NULL)) { sql_rollback (); return 9; } tickets_remove_tasks_user (user); sql ("DELETE FROM task_alerts" " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);", user); sql ("DELETE FROM task_files" " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);", user); sql ("DELETE FROM task_preferences" " WHERE task IN (SELECT id FROM tasks WHERE owner = %llu);", user); sql ("DELETE FROM tasks WHERE owner = %llu;", user); /* Delete resources directly used by tasks. */ /* Alerts. */ if (user_resources_in_use (user, "alerts", alert_in_use, "alerts_trash", trash_alert_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM alert_condition_data" " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);", user); sql ("DELETE FROM alert_condition_data_trash" " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);", user); sql ("DELETE FROM alert_event_data" " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);", user); sql ("DELETE FROM alert_event_data_trash" " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);", user); sql ("DELETE FROM alert_method_data" " WHERE alert IN (SELECT id FROM alerts WHERE owner = %llu);", user); sql ("DELETE FROM alert_method_data_trash" " WHERE alert IN (SELECT id FROM alerts_trash WHERE owner = %llu);", user); sql ("DELETE FROM alerts WHERE owner = %llu;", user); sql ("DELETE FROM alerts_trash WHERE owner = %llu;", user); /* Configs. */ if (user_resources_in_use (user, "configs", config_in_use, "configs_trash", trash_config_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM nvt_selectors" " WHERE name IN (SELECT nvt_selector FROM configs WHERE owner = %llu)" " AND name != '" MANAGE_NVT_SELECTOR_UUID_ALL "';", user); sql ("DELETE FROM config_preferences" " WHERE config IN (SELECT id FROM configs WHERE owner = %llu);", user); sql ("DELETE FROM config_preferences_trash" " WHERE config IN (SELECT id FROM configs_trash WHERE owner = %llu);", user); sql ("DELETE FROM configs WHERE owner = %llu;", user); sql ("DELETE FROM configs_trash WHERE owner = %llu;", user); /* Scanners. */ if (user_resources_in_use (user, "scanners", scanner_in_use, "scanners_trash", trash_scanner_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM scanners WHERE owner = %llu;", user); sql ("DELETE FROM scanners_trash WHERE owner = %llu;", user); /* Schedules. */ if (user_resources_in_use (user, "schedules", schedule_in_use, "schedules_trash", trash_schedule_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM schedules WHERE owner = %llu;", user); sql ("DELETE FROM schedules_trash WHERE owner = %llu;", user); /* Targets. */ if (user_resources_in_use (user, "targets", target_in_use, "targets_trash", trash_target_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM targets_login_data WHERE target IN" " (SELECT id FROM targets WHERE owner = %llu);", user); sql ("DELETE FROM targets_trash_login_data WHERE target IN" " (SELECT id FROM targets_trash WHERE owner = %llu);", user); sql ("DELETE FROM targets WHERE owner = %llu;", user); sql ("DELETE FROM targets_trash WHERE owner = %llu;", user); /* Delete resources used indirectly by tasks */ /* Filters (used by alerts and settings). */ if (user_resources_in_use (user, "filters", filter_in_use, "filters_trash", trash_filter_in_use)) { sql_rollback (); return 9; } sql ("DELETE FROM filters WHERE owner = %llu;", user); sql ("DELETE FROM filters_trash WHERE owner = %llu;", user); /* Port lists (used by targets). */ if (user_resources_in_use (user, "port_lists", port_list_in_use, "port_lists_trash", trash_port_list_in_use)) { sql_rollback (); return 9; } delete_port_lists_user (user); /* Check credentials before deleting report formats, because we can't * rollback the deletion of the report format dirs. */ if (user_resources_in_use (user, "credentials", credential_in_use, "credentials_trash", trash_credential_in_use)) { sql_rollback (); return 9; } /* Check report formats (used by alerts). */ if (user_resources_in_use (user, "report_formats", report_format_in_use, "report_formats_trash", trash_report_format_in_use)) { sql_rollback (); return 9; } /* Delete credentials last because they can be used in various places */ sql ("DELETE FROM credentials_data WHERE credential IN" " (SELECT id FROM credentials WHERE owner = %llu);", user); sql ("DELETE FROM credentials_trash_data WHERE credential IN" " (SELECT id FROM credentials_trash WHERE owner = %llu);", user); sql ("DELETE FROM credentials WHERE owner = %llu;", user); sql ("DELETE FROM credentials_trash WHERE owner = %llu;", user); /* Make permissions global if they are owned by the user and are related * to users/groups/roles that are owned by the user. */ sql ("UPDATE permissions SET owner = NULL" " WHERE owner = %llu" " AND ((subject_type = 'user' AND subject IN (SELECT id FROM users WHERE owner = %llu))" " OR (subject_type = 'group' AND subject IN (SELECT id FROM groups WHERE owner = %llu))" " OR (subject_type = 'role' AND subject IN (SELECT id FROM roles WHERE owner = %llu))" " OR (resource_type = 'user' AND resource IN (SELECT id FROM users WHERE owner = %llu))" " OR (resource_type = 'group' AND resource IN (SELECT id FROM groups WHERE owner = %llu))" " OR (resource_type = 'role' AND resource IN (SELECT id FROM roles WHERE owner = %llu)));", user, user, user, user, user, user, user); /* Make users, roles and groups global if they are owned by the user. */ sql ("UPDATE users SET owner = NULL WHERE owner = %llu;", user); sql ("UPDATE roles SET owner = NULL WHERE owner = %llu;", user); sql ("UPDATE groups SET owner = NULL WHERE owner = %llu;", user); sql ("UPDATE roles_trash SET owner = NULL WHERE owner = %llu;", user); sql ("UPDATE groups_trash SET owner = NULL WHERE owner = %llu;", user); /* Remove all other permissions owned by the user or given on the user. */ sql ("DELETE FROM permissions" " WHERE owner = %llu" " OR subject_type = 'user' AND subject = %llu" " OR (resource_type = 'user' AND resource = %llu);", /* For Super. */ user, user, user); sql ("DELETE FROM permissions_get_tasks WHERE \"user\" = %llu;", user); /* Delete permissions granted by the user. */ sql ("DELETE FROM permissions WHERE owner = %llu;", user); sql ("DELETE FROM permissions_trash WHERE owner = %llu;", user); /* Remove user from groups and roles. */ sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user); sql ("DELETE FROM group_users_trash WHERE \"user\" = %llu;", user); sql ("DELETE FROM role_users WHERE \"user\" = %llu;", user); sql ("DELETE FROM role_users_trash WHERE \"user\" = %llu;", user); /* Delete report formats. */ has_rows = delete_report_formats_user (user, &rows); /* Delete user. */ deleted_user_id = user_uuid (user); sql ("DELETE FROM users WHERE id = %llu;", user); /* Delete report format dirs. */ if (deleted_user_id) delete_report_format_dirs_user (deleted_user_id, has_rows ? &rows : NULL); else g_warning ("%s: deleted_user_id NULL, skipping removal of report formats dir", __func__); sql_commit (); return 0; } /** * @brief Modify a user. * * @param[in] user_id The UUID of the user. Overrides name. * @param[in] name The name of the user. If NULL then set to name * when return is 3 or 4. * @param[in] new_name New name for the user. NULL to leave as is. * @param[in] password The password of the user. NULL to leave as is. * @param[in] comment The comment for the user. NULL to leave as is. * @param[in] hosts The host the user is allowed/forbidden to scan. * NULL to leave as is. * @param[in] hosts_allow Whether hosts is allow or forbid. * @param[in] ifaces Interfaces the user is allowed/forbidden to scan. * @param[in] ifaces_allow Whether ifaces is allow or forbid. * @param[in] allowed_methods Allowed login methods. * @param[in] groups Groups. * @param[out] group_id_return ID of group on "failed to find" error. * @param[in] roles Roles. * @param[out] role_id_return ID of role on "failed to find" error. * @param[out] r_errdesc If not NULL the address of a variable to receive * a malloced string with the error description. Will * always be set to NULL on success. * * @return 0 if the user has been added successfully, 1 failed to find group, * 2 failed to find user, 3 success and user gained admin, 4 success * and user lost admin, 5 failed to find role, 6 syntax error in hosts, * 7 syntax error in new name, 99 permission denied, -1 on error, * -2 for an unknown role, -3 if wrong number of methods. */ int modify_user (const gchar * user_id, gchar **name, const gchar *new_name, const gchar * password, const gchar * comment, const gchar * hosts, int hosts_allow, const gchar *ifaces, int ifaces_allow, const array_t * allowed_methods, array_t *groups, gchar **group_id_return, array_t *roles, gchar **role_id_return, gchar **r_errdesc) { char *errstr; gchar *hash, *quoted_hosts, *quoted_ifaces, *quoted_method, *clean, *uuid; gchar *quoted_new_name, *quoted_comment; user_t user; int max, was_admin, is_admin; GArray *cache_users; if (r_errdesc) *r_errdesc = NULL; /* allowed_methods is a NULL terminated array. */ if (allowed_methods && (allowed_methods->len > 2)) return -3; if (allowed_methods && (allowed_methods->len == 0)) allowed_methods = NULL; if (allowed_methods && ((g_ptr_array_index (allowed_methods, 0) == NULL) || (strlen (g_ptr_array_index (allowed_methods, 0)) == 0))) allowed_methods = NULL; // TODO Validate methods: single source, one of "", "ldap", ... sql_begin_immediate (); if (acl_user_may ("modify_user") == 0) { sql_rollback (); return 99; } user = 0; if (user_id) { if (find_user_with_permission (user_id, &user, "modify_user")) { sql_rollback (); return -1; } } else if (find_user_by_name_with_permission (*name, &user, "modify_user")) { sql_rollback (); return -1; } if (user == 0) { sql_rollback (); return 2; } uuid = sql_string ("SELECT uuid FROM users WHERE id = %llu", user); /* The only user that can edit a Super Admin is the Super Admin themself. */ if (acl_user_can_super_everyone (uuid) && strcmp (uuid, current_credentials.uuid)) { g_free (uuid); sql_rollback (); return 99; } was_admin = acl_user_is_admin (uuid); if (password) { char *user_name; user_name = sql_string ("SELECT name FROM users WHERE id = %llu", user); errstr = gvm_validate_password (password, user_name); if (errstr) { g_warning ("new password for '%s' rejected: %s", user_name, errstr); if (r_errdesc) *r_errdesc = errstr; else g_free (errstr); sql_rollback (); g_free (user_name); return -1; } g_free (user_name); } /* Check hosts. */ max = manage_max_hosts (); manage_set_max_hosts (MANAGE_USER_MAX_HOSTS); if (hosts && (manage_count_hosts (hosts, NULL) < 0)) { manage_set_max_hosts (max); sql_rollback (); return 6; } manage_set_max_hosts (max); /* Check new name. */ if (new_name) { if (validate_username (new_name) != 0) { sql_rollback (); return 7; } if (strcmp (uuid, current_credentials.uuid) == 0) { sql_rollback (); return 99; } if (resource_with_name_exists_global (new_name, "user", user)) { sql_rollback (); return 8; } quoted_new_name = sql_quote (new_name); } else quoted_new_name = NULL; /* Get the password hashes. */ if (password) hash = manage_authentication_hash (password); else hash = NULL; if (comment) { quoted_comment = sql_quote (comment); } else { quoted_comment = NULL; } /* Update the user in the database. */ clean = clean_hosts (hosts ? hosts : "", &max); if ((hosts_allow == 0) && (max == 0)) /* Convert "Deny none" to "Allow All". */ hosts_allow = 2; quoted_ifaces = sql_quote (ifaces ? ifaces : ""); quoted_hosts = sql_quote (clean); g_free (clean); quoted_method = sql_quote (allowed_methods ? g_ptr_array_index (allowed_methods, 0) : ""); sql ("UPDATE users" " SET name = %s%s%s," " comment = %s%s%s," " hosts = '%s'," " hosts_allow = '%i'," " ifaces = '%s'," " ifaces_allow = %i," " method = %s%s%s," " modification_time = m_now ()" " WHERE id = %llu;", quoted_new_name ? "'" : "", quoted_new_name ? quoted_new_name : "name", quoted_new_name ? "'" : "", quoted_comment ? "'" : "", quoted_comment ? quoted_comment : "comment", quoted_comment ? "'" : "", quoted_hosts, hosts_allow, quoted_ifaces, ifaces_allow, allowed_methods ? "'" : "", allowed_methods ? quoted_method : "method", allowed_methods ? "'" : "", user); g_free (quoted_new_name); g_free (quoted_hosts); g_free (quoted_ifaces); g_free (quoted_method); if (hash) sql ("UPDATE users" " SET password = '%s'" " WHERE id = %llu;", hash, user); g_free (hash); /* Update the user groups. */ if (groups) { int index; sql ("DELETE FROM group_users WHERE \"user\" = %llu;", user); index = 0; while (groups && (index < groups->len)) { gchar *group_id; group_t group; group_id = (gchar*) g_ptr_array_index (groups, index); if (strcmp (group_id, "0") == 0) { index++; continue; } if (find_group_with_permission (group_id, &group, "modify_group")) { sql_rollback (); return -1; } if (group == 0) { sql_rollback (); if (group_id_return) *group_id_return = group_id; return 1; } sql ("INSERT INTO group_users (\"group\", \"user\")" " VALUES (%llu, %llu);", group, user); index++; } } /* Update the user roles. */ if (roles) { int index; sql ("DELETE FROM role_users" " WHERE \"user\" = %llu" " AND role != (SELECT id from roles" " WHERE uuid = '" ROLE_UUID_SUPER_ADMIN "');", user); index = 0; while (roles && (index < roles->len)) { gchar *role_id; role_t role; role_id = (gchar*) g_ptr_array_index (roles, index); if (strcmp (role_id, "0") == 0) { index++; continue; } if (find_role_with_permission (role_id, &role, "get_roles")) { sql_rollback (); return -1; } if (role == 0) { sql_rollback (); if (role_id_return) *role_id_return = role_id; return 1; } if (acl_role_can_super_everyone (role_id)) { sql_rollback (); return 99; } sql ("INSERT INTO role_users (role, \"user\") VALUES (%llu, %llu);", role, user); index++; } } cache_users = g_array_new (TRUE, TRUE, sizeof (user_t)); g_array_append_val (cache_users, user); cache_all_permissions_for_users (cache_users); g_free (g_array_free (cache_users, TRUE)); sql_commit (); if (was_admin) { is_admin = acl_user_is_admin (uuid); g_free (uuid); if (is_admin) return 0; if (*name == NULL) *name = sql_string ("SELECT name FROM users WHERE id = %llu", user); return 4; } is_admin = acl_user_is_admin (uuid); g_free (uuid); if (is_admin) { if (*name == NULL) *name = sql_string ("SELECT name FROM users WHERE id = %llu", user); return 3; } return 0; } /** * @brief Return the name of a user. * * @param[in] uuid UUID of user. * * @return Newly allocated name if available, else NULL. */ gchar* user_name (const char *uuid) { gchar *name, *quoted_uuid; quoted_uuid = sql_quote (uuid); name = sql_string ("SELECT name FROM users WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return name; } /** * @brief Return the UUID of a user. * * Warning: this is only safe for users that are known to be in the db. * * @param[in] user User. * * @return Newly allocated UUID if available, else NULL. */ char* user_uuid (user_t user) { return sql_string ("SELECT uuid FROM users WHERE id = %llu;", user); } /** * @brief Check whether a user is in use. * * @param[in] user User. * * @return 1 yes, 0 no. */ int user_in_use (user_t user) { return 0; } /** * @brief Check whether a trashcan user is in use. * * @param[in] user User. * * @return 1 yes, 0 no. */ int trash_user_in_use (user_t user) { return 0; } /** * @brief Check whether a user is writable. * * @param[in] user User. * * @return 1 yes, 0 no. */ int user_writable (user_t user) { return 1; } /** * @brief Check whether a trashcan user is writable. * * @param[in] user User. * * @return 1 yes, 0 no. */ int trash_user_writable (user_t user) { return 1; } /** * @brief Return the ifaces of a user. * * @param[in] uuid UUID of user. * * @return Newly allocated ifaces value if available, else NULL. */ gchar* user_ifaces (const char *uuid) { gchar *name, *quoted_uuid; quoted_uuid = sql_quote (uuid); name = sql_string ("SELECT ifaces FROM users WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return name; } /** * @brief Return whether ifaces value of a user denotes allowed. * * @param[in] uuid UUID of user. * * @return 1 if allow, else 0. */ int user_ifaces_allow (const char *uuid) { gchar *quoted_uuid; int allow; quoted_uuid = sql_quote (uuid); allow = sql_int ("SELECT ifaces_allow FROM users WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return allow; } /** * @brief Return the hosts of a user. * * @param[in] uuid UUID of user. * * @return Newly allocated hosts value if available, else NULL. */ gchar* user_hosts (const char *uuid) { gchar *name, *quoted_uuid; quoted_uuid = sql_quote (uuid); name = sql_string ("SELECT hosts FROM users WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return name; } /** * @brief Return whether hosts value of a user denotes allowed. * * @param[in] uuid UUID of user. * * @return 1 if allow, else 0. */ int user_hosts_allow (const char *uuid) { gchar *quoted_uuid; int allow; quoted_uuid = sql_quote (uuid); allow = sql_int ("SELECT hosts_allow FROM users WHERE uuid = '%s';", quoted_uuid); g_free (quoted_uuid); return allow; } /** * @brief User columns for user iterator. */ #define USER_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "method", "roles", "groups", "hosts", \ "ifaces", NULL } /** * @brief User iterator columns. */ #define USER_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (users), \ { "method", NULL, KEYWORD_TYPE_STRING }, \ { "hosts", NULL, KEYWORD_TYPE_STRING }, \ { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER }, \ { \ "coalesce ((SELECT group_concat (name, ', ')" \ " FROM (SELECT DISTINCT name, order_role (name)" \ " FROM roles, role_users" \ " WHERE role_users.role = roles.id" \ " AND \"user\" = users.id" \ " ORDER BY order_role (roles.name) ASC)" \ " AS user_iterator_sub)," \ " '')", \ "roles", \ KEYWORD_TYPE_STRING \ }, \ { \ "coalesce ((SELECT group_concat (name, ', ')" \ " FROM (SELECT DISTINCT name FROM groups, group_users" \ " WHERE group_users.\"group\" = groups.id" \ " AND \"user\" = users.id" \ " ORDER BY groups.name ASC)" \ " AS user_iterator_sub)," \ " '')", \ "groups", \ KEYWORD_TYPE_STRING \ }, \ { "ifaces", NULL, KEYWORD_TYPE_STRING }, \ { "ifaces_allow", NULL, KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief User iterator columns for trash case. */ #define USER_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (users_trash), \ { "method", NULL, KEYWORD_TYPE_STRING }, \ { "hosts", NULL, KEYWORD_TYPE_STRING }, \ { "hosts_allow", NULL, KEYWORD_TYPE_INTEGER }, \ { "ifaces", NULL, KEYWORD_TYPE_STRING }, \ { "ifaces_allow", NULL, KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Count number of users. * * @param[in] get GET params. * * @return Total number of users in usered set. */ int user_count (const get_data_t *get) { static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = USER_ITERATOR_COLUMNS; return count ("user", get, columns, NULL, filter_columns, 0, 0, 0, TRUE); } /** * @brief Initialise a user iterator, including observed users. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find user, 2 failed to find user (filt_id), * -1 error. */ int init_user_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = USER_ITERATOR_FILTER_COLUMNS; static column_t columns[] = USER_ITERATOR_COLUMNS; static column_t trash_columns[] = USER_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "user", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Get the method of the user from a user iterator. * * @param[in] iterator Iterator. * * @return Method of the user or NULL if iteration is complete. */ DEF_ACCESS (user_iterator_method, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get the hosts from a user iterator. * * @param[in] iterator Iterator. * * @return Hosts or NULL if iteration is complete. */ DEF_ACCESS (user_iterator_hosts, GET_ITERATOR_COLUMN_COUNT + 1); /** * @brief Get the hosts allow value from a user iterator. * * @param[in] iterator Iterator. * * @return Hosts allow. */ int user_iterator_hosts_allow (iterator_t* iterator) { if (iterator->done) return -1; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 2); } /** * @brief Get the ifaces from a user iterator. * * @param[in] iterator Iterator. * * @return Interfaces or NULL if iteration is complete. */ DEF_ACCESS (user_iterator_ifaces, GET_ITERATOR_COLUMN_COUNT + 5); /** * @brief Get the ifaces allow value from a user iterator. * * @param[in] iterator Iterator. * * @return Interfaces allow. */ int user_iterator_ifaces_allow (iterator_t* iterator) { if (iterator->done) return -1; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Initialise an info iterator. * * @param[in] iterator Iterator. * @param[in] user User. */ void init_user_group_iterator (iterator_t *iterator, user_t user) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (user); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_groups")); available = acl_where_owned ("group", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT DISTINCT id, uuid, name, %s FROM groups" " WHERE id IN (SELECT \"group\" FROM group_users" " WHERE \"user\" = %llu)" " ORDER by name;", with_clause ? with_clause : "", available, user); g_free (with_clause); g_free (available); } /** * @brief Get the UUID from a user group iterator. * * @param[in] iterator Iterator. * * @return UUID or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (user_group_iterator_uuid, 1); /** * @brief Get the NAME from a user group iterator. * * @param[in] iterator Iterator. * * @return NAME or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (user_group_iterator_name, 2); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int user_group_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 3); } /** * @brief Initialise an info iterator. * * @param[in] iterator Iterator. * @param[in] user User. */ void init_user_role_iterator (iterator_t *iterator, user_t user) { gchar *available, *with_clause; get_data_t get; array_t *permissions; assert (user); get.trash = 0; permissions = make_array (); array_add (permissions, g_strdup ("get_roles")); available = acl_where_owned ("role", &get, 1, "any", 0, permissions, 0, &with_clause); array_free (permissions); init_iterator (iterator, "%s" " SELECT DISTINCT id, uuid, name, order_role (name), %s" " FROM roles" " WHERE id IN (SELECT role FROM role_users" " WHERE \"user\" = %llu)" " ORDER by order_role (name);", with_clause ? with_clause : "", available, user); g_free (with_clause); g_free (available); } /** * @brief Get the UUID from a user role iterator. * * @param[in] iterator Iterator. * * @return UUID or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (user_role_iterator_uuid, 1); /** * @brief Get the NAME from a user role iterator. * * @param[in] iterator Iterator. * * @return NAME or NULL if iteration is complete. Freed by cleanup_iterator. */ DEF_ACCESS (user_role_iterator_name, 2); /** * @brief Get the read permission status from a GET iterator. * * @param[in] iterator Iterator. * * @return 1 if may read, else 0. */ int user_role_iterator_readable (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, 4); } /** * @brief Check if a user still has resources that are in use. * * @param[in] user The user to check. * @param[in] table The table to check for resources in use. * @param[in] in_use Function to check if a resource is in use. * @param[in] trash_table The trash table to check for resources in use. * @param[in] trash_in_use Function to check if a trash resource is in use. * * @return 0 no resources in use, 1 found resources used by user. */ static int user_resources_in_use (user_t user, const char *table, int(*in_use)(resource_t), const char *trash_table, int(*trash_in_use)(resource_t)) { iterator_t iter; int has_resource_in_use = 0; init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu", table, user); while (next (&iter) && has_resource_in_use == 0) { resource_t resource = iterator_int64 (&iter, 0); has_resource_in_use = in_use (resource); } cleanup_iterator (&iter); if (has_resource_in_use) return 1; if (trash_table == NULL || trash_in_use == NULL) return 0; init_iterator (&iter, "SELECT id FROM %s WHERE owner = %llu", trash_table, user); while (next (&iter) && has_resource_in_use == 0) { resource_t resource = iterator_int64 (&iter, 0); has_resource_in_use = trash_in_use (resource); } cleanup_iterator (&iter); if (has_resource_in_use) return 2; return 0; } /** * @brief Filter columns for vuln iterator. */ #define VULN_ITERATOR_FILTER_COLUMNS \ { \ GET_ITERATOR_FILTER_COLUMNS, "results", "hosts", "severity", \ "qod", "oldest", "newest", "type", NULL \ } /** * @brief Results SQL for VULN_ITERATOR_COLUMNS. */ #define VULN_RESULTS_WHERE \ " WHERE nvt = vulns.uuid" \ " AND (opts.report IS NULL OR results.report = opts.report)" \ " AND (opts.task IS NULL OR results.task = opts.task)" \ " AND (opts.host IS NULL OR results.host = opts.host)" \ " AND (results.severity != " G_STRINGIFY (SEVERITY_ERROR) ")" \ " AND (SELECT has_permission FROM permissions_get_tasks" \ " WHERE \"user\" = gvmd_user ()" \ " AND task = results.task)" /** * @brief Vuln iterator columns. */ #define VULN_ITERATOR_COLUMNS \ { \ /* The following must match GET_ITERATOR_COLUMNS */ \ { "id", "id", KEYWORD_TYPE_INTEGER }, \ { "uuid", "uuid", KEYWORD_TYPE_STRING }, \ { "name", "name", KEYWORD_TYPE_STRING }, \ { "''", "comment", KEYWORD_TYPE_STRING }, \ { "iso_time (creation_time)", NULL, KEYWORD_TYPE_STRING }, \ { "iso_time (modification_time)", NULL, KEYWORD_TYPE_STRING }, \ { "creation_time", "created", KEYWORD_TYPE_INTEGER }, \ { "modification_time", "modified", KEYWORD_TYPE_INTEGER }, \ { "cast (null AS text)", "_owner", KEYWORD_TYPE_INTEGER }, \ { "''", "owner", KEYWORD_TYPE_STRING }, \ /* Type specific columns */ \ { \ "vuln_results (uuid, opts.task, opts.report, opts.host)", \ "results", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT count(*) FROM" \ " (SELECT results.host FROM results" \ VULN_RESULTS_WHERE \ " GROUP BY results.host) AS hosts_subquery)", \ "hosts", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "severity", NULL, KEYWORD_TYPE_DOUBLE \ }, \ { \ "qod", NULL, KEYWORD_TYPE_INTEGER \ }, \ { \ "type", NULL, KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT min (date) FROM results" \ VULN_RESULTS_WHERE ")", \ "oldest", \ KEYWORD_TYPE_INTEGER \ }, \ { \ "(SELECT max (date) FROM results" \ VULN_RESULTS_WHERE ")", \ "newest", \ KEYWORD_TYPE_INTEGER \ }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Generate the extra_tables string for a vuln iterator. * * @param[in] task_id UUID of the task to limit vulns to. * @param[in] report_id UUID of the report to limit vulns to. * @param[in] host IP address of the task to limit vulns to. * @param[in] min_qod Minimum QoD. * * @return Newly allocated string with the extra_tables clause. */ static gchar* vuln_iterator_opts_table (const gchar *task_id, const gchar *report_id, const gchar *host, int min_qod) { GString *ret; ret = g_string_new (", (SELECT"); if (task_id && strcmp (task_id, "")) { task_t task = 0; find_task_with_permission (task_id, &task, "get_tasks"); g_string_append_printf (ret, " %llu AS task,", task); } else { g_string_append (ret, " cast (null AS integer) AS task,"); } if (report_id && strcmp (report_id, "")) { report_t report = 0; find_report_with_permission (report_id, &report, "get_reports"); g_string_append_printf (ret, " %llu AS report,", report); } else { g_string_append (ret, " cast (null AS integer) AS report,"); } if (host && strcmp (host, "")) { gchar *quoted_host = sql_quote (host); g_string_append_printf (ret, " '%s' AS host,", quoted_host); g_free (quoted_host); } else { g_string_append (ret, " cast (null AS text) AS host,"); } g_string_append_printf (ret, " %d AS min_qod) AS opts", min_qod); return g_string_free (ret, FALSE); } /** * @brief Generate the extra_tables string for a vuln iterator using a filter. * * @param[in] filter The filter term to use. * * @return Newly allocated string with the extra_tables clause. */ static gchar* vuln_iterator_opts_from_filter (const gchar *filter) { gchar *task_id, *report_id, *host; int min_qod; gchar *ret; if (filter) { task_id = filter_term_value (filter, "task_id"); report_id = filter_term_value (filter, "report_id"); host = filter_term_value (filter, "host"); } else task_id = report_id = host = NULL; min_qod = filter_term_min_qod (filter); ret = vuln_iterator_opts_table (task_id, report_id, host, min_qod); g_free (task_id); g_free (report_id); g_free (host); return ret; } /** * @brief Initialise a vulnerability iterator, including observed vulns. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find vuln, 2 failed to find filter (filt_id), * -1 error. */ int init_vuln_iterator (iterator_t* iterator, const get_data_t *get) { int ret; gchar *filter; gchar *extra_tables, *extra_where; static const char *filter_columns[] = VULN_ITERATOR_FILTER_COLUMNS; static column_t select_columns[] = VULN_ITERATOR_COLUMNS; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { if (get->filter_replacement) /* Replace the filter term with one given by the caller. This is * used by GET_REPORTS to use the default filter with any task (when * given the special value of -3 in filt_id). */ filter = g_strdup (get->filter_replacement); else filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; extra_tables = vuln_iterator_opts_from_filter (filter ? filter : get->filter); extra_where = vulns_extra_where (); ret = init_get_iterator2 (iterator, "vuln", get, select_columns, NULL, /* trash_select_columns */ NULL, /* where_columns */ NULL, /* trash_where_columns */ filter_columns, 0 /* distinct */, extra_tables, /* extra_tables */ extra_where, /* extra_where */ NULL, /* extra_where_single */ 0, /* owned */ 0, /* ignore_id */ NULL);/* extra_order */ g_free (extra_tables); g_free (extra_where); return ret; } /** * @brief Get the number of results from a vuln iterator. * * @param[in] iterator Iterator. * * @return The number of results. */ int vuln_iterator_results (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 0); } /** * @brief Get the number of hosts from a vuln iterator. * * @param[in] iterator Iterator. * * @return The number of hosts. */ int vuln_iterator_hosts (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); } /** * @brief Get the severity from a vuln iterator. * * @param[in] iterator Iterator. * * @return The severity. */ double vuln_iterator_severity (iterator_t* iterator) { if (iterator->done) return SEVERITY_MISSING; return iterator_double (iterator, GET_ITERATOR_COLUMN_COUNT + 2); } /** * @brief Get the QoD from a vuln iterator. * * @param[in] iterator Iterator. * * @return The QoD. */ int vuln_iterator_qod (iterator_t* iterator) { if (iterator->done) return -1; return iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3); } /** * @brief Get the QoD from a vuln iterator. * * @param[in] iterator Iterator. * * @return The QoD. */ const char* vuln_iterator_type (iterator_t* iterator) { if (iterator->done) return NULL; return iterator_string (iterator, GET_ITERATOR_COLUMN_COUNT + 4); } /** * @brief Get the date of the oldest result from a vuln iterator. * * @param[in] iterator Iterator. * * @return The oldest result date. */ time_t vuln_iterator_oldest (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 5); } /** * @brief Get the date of the oldest result from a vuln iterator. * * @param[in] iterator Iterator. * * @return The oldest result date. */ time_t vuln_iterator_newest (iterator_t* iterator) { if (iterator->done) return 0; return iterator_int64 (iterator, GET_ITERATOR_COLUMN_COUNT + 6); } /** * @brief Count number of vulns. * * @param[in] get GET params. * * @return Total number of vulns in filtered set. */ int vuln_count (const get_data_t *get) { gchar *filter; static const char *filter_columns[] = VULN_ITERATOR_FILTER_COLUMNS; static column_t columns[] = VULN_ITERATOR_COLUMNS; gchar *extra_tables, *extra_where; int ret; if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { if (get->filter_replacement) /* Replace the filter term with one given by the caller. This is * used by GET_REPORTS to use the default filter with any task (when * given the special value of -3 in filt_id). */ filter = g_strdup (get->filter_replacement); else filter = filter_term (get->filt_id); if (filter == NULL) return 2; } else filter = NULL; extra_tables = vuln_iterator_opts_from_filter (filter ? filter : get->filter); extra_where = vulns_extra_where (); ret = count ("vuln", get, columns, NULL /*trash_columns*/, filter_columns, 0, extra_tables, extra_where, FALSE); g_free (filter); g_free (extra_tables); g_free (extra_where); return ret; } /** * @brief Extra WHERE clause for vulns. * * @return WHERE clause. */ static gchar* vulns_extra_where () { return g_strdup (" AND vuln_results_exist (uuid, opts.task, opts.report," " opts.host)" " AND (qod >= opts.min_qod)"); } /** * @brief Get LDAP info. * * @param[out] enabled Whether LDAP is enabled. * @param[out] host Freshly allocated host. * @param[out] authdn Freshly allocated Auth DN. * @param[out] plaintext Whether plaintext auth is allowed. * @param[out] cacert CA cert if there's one, else NULL. */ void manage_get_ldap_info (int *enabled, gchar **host, gchar **authdn, int *plaintext, gchar **cacert) { if (enabled) *enabled = ldap_auth_enabled (); *host = sql_string ("SELECT value FROM meta" " WHERE name = 'ldap_host';"); if (*host == NULL) *host = g_strdup ("127.0.0.1"); *authdn = sql_string ("SELECT value FROM meta" " WHERE name = 'ldap_authdn';"); if (*authdn == NULL) *authdn = g_strdup ("userid=%s,dc=example,dc=org"); *plaintext = sql_int ("SELECT coalesce ((SELECT CAST (value AS integer)" " FROM meta" " WHERE name" " = 'ldap_allow_plaintext')," " 0);"); *cacert = sql_string ("SELECT value FROM meta" " WHERE name = 'ldap_cacert';"); } /** * @brief Set LDAP info. * * @param[in] enabled Whether LDAP is enabled. -1 to keep current value. * @param[in] host LDAP host. NULL to keep current value. * @param[in] authdn Auth DN. NULL to keep current value. * @param[in] allow_plaintext Whether plaintext auth is allowed. -1 to * keep current value. * @param[in] cacert CA certificate. NULL to keep current value. */ void manage_set_ldap_info (int enabled, gchar *host, gchar *authdn, int allow_plaintext, gchar *cacert) { gchar *quoted; sql_begin_immediate (); if (enabled >= 0) { sql ("DELETE FROM meta WHERE name LIKE 'ldap_enable';"); sql ("INSERT INTO meta (name, value) VALUES ('ldap_enable', %i);", enabled); } if (host) { sql ("DELETE FROM meta WHERE name LIKE 'ldap_host';"); quoted = sql_quote (host); sql ("INSERT INTO meta (name, value) VALUES ('ldap_host', '%s');", quoted); g_free (quoted); } if (authdn) { sql ("DELETE FROM meta WHERE name LIKE 'ldap_authdn';"); quoted = sql_quote (authdn); sql ("INSERT INTO meta (name, value) VALUES ('ldap_authdn', '%s');", quoted); g_free (quoted); } if (allow_plaintext >= 0) { sql ("DELETE FROM meta WHERE name LIKE 'ldap_allow_plaintext';"); sql ("INSERT INTO meta (name, value) VALUES ('ldap_allow_plaintext', %i);", allow_plaintext); } if (cacert) { sql ("DELETE FROM meta WHERE name LIKE 'ldap_cacert';"); quoted = sql_quote (cacert); sql ("INSERT INTO meta (name, value) VALUES ('ldap_cacert', '%s');", quoted); g_free (quoted); } sql_commit (); } /** * @brief Get RADIUS info. * * @param[out] enabled Whether RADIUS is enabled. * @param[out] host Freshly allocated RADIUS host. * @param[out] key Freshly allocated RADIUS secret key. */ void manage_get_radius_info (int *enabled, char **host, char **key) { if (enabled) *enabled = radius_auth_enabled (); *host = sql_string ("SELECT value FROM meta WHERE name = 'radius_host';"); if (!*host) *host = g_strdup ("127.0.0.1"); *key = sql_string ("SELECT value FROM meta WHERE name = 'radius_key';"); if (!*key) *key = g_strdup ("testing123"); } /** * @brief Set RADIUS info. * * @param[out] enabled Whether RADIUS is enabled. -1 to keep current value. * @param[out] host RADIUS host. NULL to keep current value. * @param[out] key Secret key. NULL to keep current value. */ void manage_set_radius_info (int enabled, gchar *host, gchar *key) { char *quoted; sql_begin_immediate (); if (enabled >= 0) { sql ("DELETE FROM meta WHERE name LIKE 'radius_enable';"); sql ("INSERT INTO meta (name, value) VALUES ('radius_enable', %i);", enabled); } if (host) { sql ("DELETE FROM meta WHERE name LIKE 'radius_host';"); quoted = sql_quote (host); sql ("INSERT INTO meta (name, value) VALUES ('radius_host', '%s');", quoted); g_free (quoted); } if (key) { sql ("DELETE FROM meta WHERE name LIKE 'radius_key';"); quoted = sql_quote (key); sql ("INSERT INTO meta (name, value) VALUES ('radius_key', '%s');", quoted); g_free (quoted); } sql_commit (); } /* Tags */ /** * @brief Add a resource to a tag. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource Type. * @param[in] uuid The resource UUID. * @param[in] resource The resource row id. * @param[in] location Whether the resource is in the trashcan. * * @return 0 success, -1 error */ static int tag_add_resource (tag_t tag, const char *type, const char *uuid, resource_t resource, int location) { int already_added, ret; gchar *quoted_resource_uuid; ret = 0; quoted_resource_uuid = uuid ? sql_insert (uuid) : g_strdup ("''"); if (type_is_info_subtype (type)) already_added = sql_int ("SELECT count(*) FROM tag_resources" " WHERE resource_type = '%s'" " AND resource_uuid = %s" " AND tag = %llu", type, quoted_resource_uuid, tag); else already_added = sql_int ("SELECT count(*) FROM tag_resources" " WHERE resource_type = '%s'" " AND resource = %llu" " AND resource_location = %d" " AND tag = %llu", type, resource, location, tag); if (already_added == 0) { g_debug ("%s - adding %s %s", __func__, type, uuid); sql ("INSERT INTO tag_resources" " (tag, resource_type, resource, resource_uuid, resource_location)" " VALUES (%llu, '%s', %llu, %s, %d)", tag, type, resource, quoted_resource_uuid, location); } else { g_debug ("%s - skipping %s %s", __func__, type, uuid); } g_free (quoted_resource_uuid); return ret; } /** * @brief Find a resource by UUID and add it as a tag resource. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource Type. * @param[in] uuid The resource UUID. * @param[in] permission The permission required to get the resource. * * @return 0 success, -1 error, 1 resource not found. */ static int tag_add_resource_uuid (tag_t tag, const char *type, const char *uuid, const char *permission) { int resource_location = LOCATION_TABLE; resource_t resource; if (find_resource_with_permission (type, uuid, &resource, permission, 0)) { g_warning ("%s: Failed to find %s %s", __func__, type, uuid); return -1; } else if (resource == 0 && type_has_trash (type)) { if (find_resource_with_permission (type, uuid, &resource, permission, 1)) { g_warning ("%s: Failed to find trash %s %s", __func__, type, uuid); return -1; } else if (resource != 0) resource_location = LOCATION_TRASH; } if (resource == 0) return 1; return tag_add_resource (tag, type, uuid, resource, resource_location); } /** * @brief Find resources from an array by UUID and insert as a tag resource. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource type. * @param[in] uuids The array of resource UUIDs. * @param[out] error_extra Extra error output. Contains UUID if not found. * * @return 0 success, -1 error, 1 resource not found. */ static int tag_add_resources_list (tag_t tag, const char *type, array_t *uuids, gchar **error_extra) { gchar *resource_permission, *current_uuid; int index; if (type_is_info_subtype (type)) resource_permission = g_strdup ("get_info"); else if (type_is_asset_subtype (type)) resource_permission = g_strdup ("get_assets"); else resource_permission = g_strdup_printf ("get_%ss", type); index = 0; while ((current_uuid = g_ptr_array_index (uuids, index++))) { int ret; ret = tag_add_resource_uuid (tag, type, current_uuid, resource_permission); if (ret) { g_free (resource_permission); if (error_extra) *error_extra = g_strdup (current_uuid); return ret; } } return 0; } /** * @brief Find resources using a filter and insert as tag resources. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource type. * @param[in] filter The filter to select resources with. * * @return 0 success, -1 error, 1 resource not found, 2 no resources returned. */ static int tag_add_resources_filter (tag_t tag, const char *type, const char *filter) { iterator_t resources; gchar *filtered_select; get_data_t resources_get; int ret; memset (&resources_get, '\0', sizeof (resources_get)); resources_get.filter = g_strdup (filter); resources_get.filt_id = FILT_ID_NONE; resources_get.trash = LOCATION_TABLE; resources_get.type = g_strdup (type); ignore_max_rows_per_page = 1; filtered_select = NULL; if (strcasecmp (type, "TICKET") == 0) { /* TODO This is how it should be done for all types, in order * to contain each per-resource implementation in its own file. */ if (init_ticket_iterator (&resources, &resources_get)) { g_warning ("%s: Failed to build filter SELECT", __func__); sql_rollback (); g_free (resources_get.filter); g_free (resources_get.type); return -1; } } else { gchar *columns; columns = g_strdup_printf ("%ss.id, %ss.uuid", type, type); switch (type_build_select (type, columns, &resources_get, 0, 1, NULL, NULL, NULL, &filtered_select)) { case 0: g_free (columns); if (sql_int ("SELECT count(*) FROM (%s) AS filter_selection", filtered_select) == 0) { g_free (filtered_select); return 2; } init_iterator (&resources, "%s", filtered_select); break; default: g_free (columns); ignore_max_rows_per_page = 0; g_warning ("%s: Failed to build filter SELECT", __func__); sql_rollback (); g_free (resources_get.filter); g_free (resources_get.type); return -1; } } ignore_max_rows_per_page = 0; g_free (resources_get.filter); g_free (resources_get.type); ret = 2; while (next (&resources)) { resource_t resource; const char *current_uuid; int add_ret; resource = iterator_int64 (&resources, 0); current_uuid = iterator_string (&resources, 1); add_ret = tag_add_resource (tag, type, current_uuid, resource, LOCATION_TABLE); if (add_ret) { ret = add_ret; break; } ret = 0; } cleanup_iterator (&resources); g_free (filtered_select); return ret; } /** * @brief Remove resources from a tag using a UUIDs array. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource type. * @param[in] uuids The array of resource UUIDs. * @param[out] error_extra Extra error output. Contains UUID if not found. * * @return 0 success, -1 error, 1 resource not found. */ static int tag_remove_resources_list (tag_t tag, const char *type, array_t *uuids, gchar **error_extra) { gchar *current_uuid; int index; index = 0; while ((current_uuid = g_ptr_array_index (uuids, index++))) { gchar *uuid_escaped = g_markup_escape_text (current_uuid, -1); if (sql_int ("SELECT count(*) FROM tag_resources" " WHERE tag = %llu AND resource_uuid = '%s'", tag, uuid_escaped) == 0) { if (error_extra) *error_extra = g_strdup (current_uuid); g_free (uuid_escaped); return 1; } sql ("DELETE FROM tag_resources" " WHERE tag = %llu AND resource_uuid = '%s'", tag, uuid_escaped); g_free (uuid_escaped); } return 0; } /** * @brief Remove resources from a tag using a filter. * * @param[in] tag Tag to attach to the resource. * @param[in] type The resource type. * @param[in] filter The filter to select resources with. * * @return 0 success, -1 error, 1 resource not found, 2 no resources returned. */ static int tag_remove_resources_filter (tag_t tag, const char *type, const char *filter) { iterator_t resources; gchar *iterator_select; get_data_t resources_get; int ret; memset (&resources_get, '\0', sizeof (resources_get)); resources_get.filter = g_strdup (filter); resources_get.filt_id = FILT_ID_NONE; resources_get.trash = LOCATION_TABLE; resources_get.type = g_strdup (type); ignore_max_rows_per_page = 1; iterator_select = NULL; if (strcasecmp (type, "TICKET") == 0) { /* TODO This is how it should be done for all types, in order * to contain each per-resource implementation in its own file. */ if (init_ticket_iterator (&resources, &resources_get)) { ignore_max_rows_per_page = 0; g_warning ("%s: Failed to init ticket iterator", __func__); sql_rollback (); g_free (resources_get.filter); g_free (resources_get.type); return -1; } } else { gchar *columns; columns = g_strdup_printf ("%ss.id", type); switch (type_build_select (type, columns, &resources_get, 0, 1, NULL, NULL, NULL, &iterator_select)) { case 0: g_free (columns); init_iterator (&resources, "%s", iterator_select); break; default: g_free (columns); ignore_max_rows_per_page = 0; g_warning ("%s: Failed to build filter SELECT", __func__); sql_rollback (); g_free (resources_get.filter); g_free (resources_get.type); return -1; } } ignore_max_rows_per_page = 0; g_free (resources_get.filter); g_free (resources_get.type); ret = 2; while (next (&resources)) { resource_t resource; resource = iterator_int64 (&resources, 0); ret = 0; sql ("DELETE FROM tag_resources" " WHERE tag = %llu" " AND resource = %llu" " AND resource_location = %d", tag, resource, resources_get.trash); } cleanup_iterator (&resources); g_free (iterator_select); return ret; } /** * @brief Find a tag for a specific permission, given a UUID. * * @param[in] uuid UUID of tag. * @param[out] tag Tag return, 0 if successfully failed to find tag. * @param[in] permission Permission. * * @return FALSE on success (including if failed to find tag), TRUE on error. */ static gboolean find_tag_with_permission (const char* uuid, tag_t* tag, const char *permission) { return find_resource_with_permission ("tag", uuid, tag, permission, 0); } /** * @brief Create a tag from an existing tag. * * @param[in] name Name of new tag. NULL to copy from existing. * @param[in] comment Comment on new tag. NULL to copy from existing. * @param[in] tag_id UUID of existing tag. * @param[out] new_tag_return New tag. * * @return 0 success, 2 failed to find existing tag, * 99 permission denied, -1 error. */ int copy_tag (const char* name, const char* comment, const char *tag_id, tag_t* new_tag_return) { int ret = 0; tag_t new_tag, old_tag; ret = copy_resource ("tag", name, comment, tag_id, "value, resource_type, active", 1, &new_tag, &old_tag); if (ret) return ret; if (new_tag_return) *new_tag_return = new_tag; sql ("INSERT INTO tag_resources" " (tag, resource_type, resource, resource_uuid, resource_location)" " SELECT" " %llu, resource_type, resource, resource_uuid, resource_location" " FROM tag_resources" " WHERE tag = %llu", new_tag, old_tag); return 0; } /** * @brief Create a tag. * * @param[in] name Name of the tag. * @param[in] comment Comment for the tag. * @param[in] value Value of the tag. * @param[in] resource_type Resource type to attach the tag to. * @param[in] resource_uuids Unique IDs of the resource to attach the tag to. * @param[in] resources_filter Filter to select resources to attach tag to. * @param[in] active 0 for inactive, NULL or any other value for active. * @param[out] tag Created tag. * @param[out] error_extra Extra string for error (e.g. missing resource ID) * * @return 0 success, 1 resource ID not found (sets error_extra to UUID), * 2 filter returned no results, 3 too many resources selected, * 99 permission denied, -1 error. */ int create_tag (const char * name, const char * comment, const char * value, const char * resource_type, array_t * resource_uuids, const char * resources_filter, const char * active, tag_t * tag, gchar **error_extra) { gchar *quoted_name, *quoted_comment, *quoted_value; gchar *lc_resource_type, *quoted_resource_type; tag_t new_tag; sql_begin_immediate (); if (acl_user_may ("create_tag") == 0) { sql_rollback (); return 99; } lc_resource_type = g_ascii_strdown (resource_type, -1); if (strcmp (lc_resource_type, "") && valid_db_resource_type (lc_resource_type) == 0) { g_free (lc_resource_type); sql_rollback (); return -1; } quoted_name = sql_insert (name); quoted_resource_type = sql_insert (lc_resource_type); quoted_comment = sql_insert (comment ? comment : ""); quoted_value = sql_insert (value ? value : ""); sql ("INSERT INTO tags" " (uuid, owner, creation_time, modification_time, name, comment," " value, resource_type, active)" " VALUES" " (make_uuid (), (SELECT id FROM users WHERE users.uuid = '%s')," " %i, %i, %s, %s, %s, %s, %i);", current_credentials.uuid, time (NULL), time (NULL), quoted_name, quoted_comment, quoted_value, quoted_resource_type, active ? (strcmp (active, "0") == 0 ? 0 : 1) : 1); new_tag = sql_last_insert_id (); g_free (quoted_name); g_free (quoted_comment); g_free (quoted_value); g_free (quoted_resource_type); /* Handle resource IDs */ if (resource_uuids) { int ret; ret = tag_add_resources_list (new_tag, lc_resource_type, resource_uuids, error_extra); if (ret) { // Assume tag_add_resources_list return codes match sql_rollback (); g_free (lc_resource_type); return ret; } } /* Handle filter */ if (resources_filter && strcmp (resources_filter, "")) { int ret; ret = tag_add_resources_filter (new_tag, lc_resource_type, resources_filter); if (ret) { // Assume tag_add_resources_list return codes match sql_rollback (); g_free (lc_resource_type); return ret; } } g_free (lc_resource_type); if (tag) *tag = new_tag; sql_commit (); return 0; } /** * @brief Delete a tag. * * @param[in] tag_id UUID of tag. * @param[in] ultimate Whether to remove entirely, or to trashcan. * * @return 0 success, 2 failed to find tag, 99 permission denied, -1 error. */ int delete_tag (const char *tag_id, int ultimate) { tag_t tag = 0; sql_begin_immediate (); if (acl_user_may ("delete_tag") == 0) { sql_rollback (); return 99; } if (find_tag_with_permission (tag_id, &tag, "delete_tag")) { sql_rollback (); return -1; } if (tag == 0) { if (find_trash ("tag", tag_id, &tag)) { sql_rollback (); return -1; } if (tag == 0) { sql_rollback (); return 2; } if (ultimate == 0) { /* It's already in the trashcan. */ sql_commit (); return 0; } permissions_set_orphans ("tag", tag, LOCATION_TRASH); sql ("DELETE FROM tag_resources_trash WHERE tag = %llu", tag); sql ("DELETE FROM tags_trash WHERE id = %llu;", tag); sql_commit (); return 0; } if (ultimate == 0) { tag_t trash_tag; sql ("INSERT INTO tags_trash" " (uuid, owner, name, comment, creation_time," " modification_time, resource_type, active, value)" " SELECT uuid, owner, name, comment, creation_time," " modification_time, resource_type, active, value" " FROM tags WHERE id = %llu;", tag); trash_tag = sql_last_insert_id (); sql ("INSERT INTO tag_resources_trash" " (tag, resource_type, resource, resource_uuid, resource_location)" " SELECT" " %llu, resource_type, resource, resource_uuid, resource_location" " FROM tag_resources WHERE tag = %llu;", trash_tag, tag); permissions_set_locations ("tag", tag, trash_tag, LOCATION_TRASH); } else { permissions_set_orphans ("tag", tag, LOCATION_TABLE); tags_remove_resource ("tag", tag, LOCATION_TABLE); } sql ("DELETE FROM tag_resources WHERE tag = %llu", tag); sql ("DELETE FROM tags WHERE id = %llu;", tag); sql_commit (); return 0; } /** * @brief Modify a tag. * * @param[in] tag_id UUID of tag. * @param[in] name New name of the tag or NULL. * @param[in] comment New comment for the tag or NULL. * @param[in] value New value of the tag or NULL. * @param[in] resource_type New resource type to attach the tag to or NULL. * @param[in] resource_uuids New Unique IDs of the resources to attach. * @param[in] resources_filter Filter to select resources to attach tag to. * @param[in] resources_action Resources action, e.g. "add" or "remove". * @param[in] active 0 for inactive, any other for active or NULL. * @param[out] error_extra Extra string for error (e.g. missing resource ID) * * @return 0 success, 1 failed to find tag, 2 tag_id required, * 3 unexpected resource action, * 4 resource ID not found (sets error_extra to UUID), * 5 filter returned no results, 6 too many resources selected, * 99 permission denied, -1 internal error. */ int modify_tag (const char *tag_id, const char *name, const char *comment, const char *value, const char *resource_type, array_t *resource_uuids, const char *resources_filter, const char *resources_action, const char *active, gchar **error_extra) { gchar *quoted_name, *quoted_comment, *quoted_value; gchar *lc_resource_type, *quoted_resource_type; tag_t tag; gchar *current_resource_type; if (tag_id == NULL) return 2; sql_begin_immediate (); assert (current_credentials.uuid); if (acl_user_may ("modify_tag") == 0) { sql_rollback (); return 99; } tag = 0; if (find_tag_with_permission (tag_id, &tag, "modify_tag")) { sql_rollback (); return -1; } if (tag == 0) { sql_rollback (); return 1; } lc_resource_type = (resource_type ? g_ascii_strdown (resource_type, -1) : g_strdup ("")); if (strcmp (lc_resource_type, "") && valid_db_resource_type (lc_resource_type) == 0) { sql_rollback (); return -1; } quoted_resource_type = sql_insert (lc_resource_type); quoted_name = sql_insert (name ? name : ""); quoted_comment = sql_insert (comment ? comment : ""); quoted_value = sql_insert (value ? value : ""); if (name) { sql ("UPDATE tags SET" " name = %s" " WHERE id = %llu;", quoted_name, tag); } if (resource_type) { sql ("UPDATE tags SET" " resource_type = %s" " WHERE id = %llu;", quoted_resource_type, tag); } if (comment) { sql ("UPDATE tags SET" " comment = %s" " WHERE id = %llu;", quoted_comment, tag); } if (value) { sql ("UPDATE tags SET" " value = %s" " WHERE id = %llu;", quoted_value, tag); } if (active) { sql ("UPDATE tags SET" " active = %i" " WHERE id = %llu;", strcmp (active, "0") ? 1 : 0, tag); } sql ("UPDATE tags SET" " modification_time = %i" " WHERE id = %llu;", time (NULL), tag); g_free (quoted_name); g_free (quoted_resource_type); g_free (quoted_comment); g_free (quoted_value); current_resource_type = sql_string ("SELECT resource_type" " FROM tags" " WHERE id = %llu", tag); /* Clear old resources */ if (resources_action == NULL || strcmp (resources_action, "") == 0 || strcmp (resources_action, "set") == 0) { if (resource_uuids || (resources_filter && strcmp (resources_filter, ""))) { sql ("DELETE FROM tag_resources WHERE tag = %llu", tag); } } else if (strcmp (resources_action, "add") && strcmp (resources_action, "remove")) { sql_rollback (); g_free (current_resource_type); g_free (lc_resource_type); return 3; } /* Handle resource IDs */ if (resource_uuids) { int ret; if (resources_action && strcmp (resources_action, "remove") == 0) ret = tag_remove_resources_list (tag, current_resource_type, resource_uuids, error_extra); else ret = tag_add_resources_list (tag, current_resource_type, resource_uuids, error_extra); if (ret) { sql_rollback (); g_free (current_resource_type); g_free (lc_resource_type); // Assume return codes besides -1 are offset from create_tag if (ret > 0) return ret + 3; else return ret; } } /* Handle filter */ if (resources_filter && strcmp (resources_filter, "")) { int ret; if (resources_action && strcmp (resources_action, "remove") == 0) ret = tag_remove_resources_filter (tag, current_resource_type, resources_filter); else ret = tag_add_resources_filter (tag, current_resource_type, resources_filter); if (ret) { // Assume tag_add_resources_list return codes match sql_rollback (); g_free (current_resource_type); g_free (lc_resource_type); // Assume return codes besides -1 are offset from create_tag if (ret > 0) return ret + 3; else return ret; } } g_free (current_resource_type); g_free (lc_resource_type); sql_commit (); return 0; } /** * @brief Filter columns for Tag iterator. */ #define TAG_ITERATOR_FILTER_COLUMNS \ { GET_ITERATOR_FILTER_COLUMNS, "resource_type", "active", "value", \ "resources", NULL } /** * @brief Tag iterator columns. */ #define TAG_ITERATOR_COLUMNS \ { \ GET_ITERATOR_COLUMNS (tags), \ { "resource_type", NULL, KEYWORD_TYPE_STRING }, \ { "active", NULL, KEYWORD_TYPE_INTEGER }, \ { "value", NULL, KEYWORD_TYPE_STRING }, \ { "(SELECT count(*) FROM tag_resources" \ " WHERE tag = tags.id" \ " AND resource_location = " G_STRINGIFY (LOCATION_TABLE) ")", \ "resources", KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Tag iterator trash columns. */ #define TAG_ITERATOR_TRASH_COLUMNS \ { \ GET_ITERATOR_COLUMNS (tags_trash), \ { "resource_type", NULL, KEYWORD_TYPE_STRING }, \ { "active", NULL, KEYWORD_TYPE_INTEGER }, \ { "value", NULL, KEYWORD_TYPE_STRING }, \ { "(SELECT count(*) FROM tag_resources_trash" \ " WHERE tag = tags_trash.id)", \ "resources", KEYWORD_TYPE_INTEGER }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Filter columns for Tag name iterator. */ #define TAG_NAME_ITERATOR_FILTER_COLUMNS \ { "name", "resource_type", NULL } /** * @brief Tag name iterator columns. */ #define TAG_NAME_ITERATOR_COLUMNS \ { \ { "name", NULL, KEYWORD_TYPE_STRING }, \ { "resource_type", NULL, KEYWORD_TYPE_STRING }, \ { NULL, NULL, KEYWORD_TYPE_UNKNOWN } \ } /** * @brief Initialise a tag iterator. * * @param[in] iterator Iterator. * @param[in] get GET data. * * @return 0 success, 1 failed to find tag, 2 failed to find filter, * -1 error. */ int init_tag_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = TAG_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TAG_ITERATOR_COLUMNS; static column_t trash_columns[] = TAG_ITERATOR_TRASH_COLUMNS; return init_get_iterator (iterator, "tag", get, columns, trash_columns, filter_columns, 0, NULL, NULL, TRUE); } /** * @brief Count number of tags. * * @param[in] get GET params. * * @return Total number of tags in filtered set. */ int tag_count (const get_data_t *get) { static const char *filter_columns[] = TAG_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TAG_ITERATOR_COLUMNS; static column_t trash_columns[] = TAG_ITERATOR_TRASH_COLUMNS; return count ("tag", get, columns, trash_columns, filter_columns, 0, 0, 0, TRUE); } /** * @brief Get the resource_type from a Tag iterator. * * @param[in] iterator Iterator. * * @return The resource type attached to a tag. */ DEF_ACCESS (tag_iterator_resource_type, GET_ITERATOR_COLUMN_COUNT); /** * @brief Get if a tag is active from a Tag iterator. * * @param[in] iterator Iterator. * * @return Whether a tag is active (0 = inactive, 1 = active). */ int tag_iterator_active (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 1); return ret; } /** * @brief Get the value from a Tag iterator. * * @param[in] iterator Iterator. * * @return The value associated with a tag. */ DEF_ACCESS (tag_iterator_value, GET_ITERATOR_COLUMN_COUNT + 2); /** * @brief Get number of resources linked to tag. * * @param[in] iterator Iterator. * * @return Count of resources linked to tag. */ int tag_iterator_resources (iterator_t* iterator) { int ret; if (iterator->done) return -1; ret = iterator_int (iterator, GET_ITERATOR_COLUMN_COUNT + 3); return ret; } /** * @brief Initialise a iterator of tag names. * * @param[in] iterator Iterator. * @param[in] get GET params. * * @return 0 success, -1 error. */ int init_tag_name_iterator (iterator_t* iterator, const get_data_t *get) { static const char *filter_columns[] = TAG_NAME_ITERATOR_FILTER_COLUMNS; static column_t columns[] = TAG_NAME_ITERATOR_COLUMNS; return init_get_iterator (iterator, "tag", get, columns, columns, filter_columns, 1, NULL, NULL, TRUE); } /** * @brief Get the name from a Tag name iterator. * * @param[in] iterator Iterator. * * @return The tag name. */ DEF_ACCESS (tag_name_iterator_name, 0); /** * @brief Initialise a iterator of tags attached to a resource. * * @param[in] iterator Iterator. * @param[in] type Resource type. * @param[in] resource Resource. * @param[in] active_only Whether to select only active tags. * @param[in] sort_field Field to sort by. * @param[in] ascending Whether to sort in ascending order. * * @return 0 success, -1 error. */ int init_resource_tag_iterator (iterator_t* iterator, const char* type, resource_t resource, int active_only, const char* sort_field, int ascending) { get_data_t get; gchar *owned_clause, *with_clause; assert (type); assert (resource); assert (current_credentials.uuid); get.trash = 0; owned_clause = acl_where_owned ("tag", &get, 1, "any", 0, NULL, 0, &with_clause); init_iterator (iterator, "%s" " SELECT id, uuid, name, value, comment" " FROM tags" " WHERE EXISTS" " (SELECT * FROM tag_resources" " WHERE resource_type = '%s'" " AND resource = %llu" " AND resource_location = %d" " AND tag = tags.id)" "%s" " AND %s" " ORDER BY %s %s;", with_clause ? with_clause : "", type, resource, LOCATION_TABLE, active_only ? " AND active=1" : "", owned_clause, sort_field ? sort_field : "active DESC, name", ascending ? "ASC" : "DESC"); g_free (with_clause); g_free (owned_clause); return 0; } /** * @brief Get the Tag UUID from a resource Tag iterator. * * @param[in] iterator Iterator. * * @return The UUID of the tag. */ DEF_ACCESS (resource_tag_iterator_uuid, 1); /** * @brief Get the Tag name from a resource Tag iterator. * * @param[in] iterator Iterator. * * @return The name of the tag. */ DEF_ACCESS (resource_tag_iterator_name, 2); /** * @brief Get the Tag value from a resource Tag iterator. * * @param[in] iterator Iterator. * * @return The value of the tag. */ DEF_ACCESS (resource_tag_iterator_value, 3); /** * @brief Get the Tag comment from a resource Tag iterator. * * @param[in] iterator Iterator. * * @return The comment of the tag. */ DEF_ACCESS (resource_tag_iterator_comment, 4); /** * @brief Check if there are tags attached to a resource. * * @param[in] type Resource type. * @param[in] resource Resource. * @param[in] active_only Whether to count only active tags. * * @return 1 if resource has tags, else 0. */ int resource_tag_exists (const char* type, resource_t resource, int active_only) { int ret; assert (type); assert (resource); ret = sql_int ("SELECT EXISTS (SELECT *" " FROM tags" " WHERE resource_type = '%s'" " AND EXISTS" " (SELECT * FROM tag_resources" " WHERE tag = tags.id" " AND resource = %llu" " AND resource_location = %d" " AND tags.resource_type" " = tag_resources.resource_type)" " %s);", type, resource, LOCATION_TABLE, active_only ? "AND active=1": ""); return ret; } /** * @brief Count number of tags attached to a resource. * * @param[in] type Resource type. * @param[in] resource Resource. * @param[in] active_only Whether to count only active tags. * * @return Total number of tags attached to the resource. */ int resource_tag_count (const char* type, resource_t resource, int active_only) { int ret; assert (type); assert (resource); ret = sql_int ("SELECT count (id)" " FROM tags" " WHERE resource_type = '%s'" " AND EXISTS" " (SELECT * FROM tag_resources" " WHERE tag = tags.id" " AND resource = %llu" " AND resource_location = %d" " AND tags.resource_type = tag_resources.resource_type)" " %s;", type, resource, LOCATION_TABLE, active_only ? "AND active=1": ""); return ret; } /** * @brief Return whether a tag is in use by a task. * * @param[in] tag Tag. * * @return 1 if in use, else 0. */ int tag_in_use (tag_t tag) { return 0; } /** * @brief Return whether a trashcan tag is referenced by a task. * * @param[in] tag Tag. * * @return 1 if in use, else 0. */ int trash_tag_in_use (tag_t tag) { return 0; } /** * @brief Return whether a tag is writable. * * @param[in] tag Tag. * * @return 1 if writable, else 0. */ int tag_writable (tag_t tag) { return 1; } /** * @brief Return whether a trashcan tag is writable. * * @param[in] tag Tag. * * @return 1 if writable, else 0. */ int trash_tag_writable (tag_t tag) { return 0; } /** * @brief Return whether a column is one containing timestamp. * * @param[in] column The column name. * * @return 1 if column contains timestamps, 0 otherwise. */ int column_is_timestamp (const char* column) { return column && (strcmp (column, "created") == 0 || strcmp (column, "date") == 0 || strcmp (column, "modified") == 0 || strcmp (column, "published") == 0 || strcmp (column, "updated") == 0); } /** * @brief Return the columns for a resource iterator. * * @param[in] type Resource type to get columns of. * * @return The columns. */ static column_t * type_select_columns (const char *type) { static column_t alert_columns[] = ALERT_ITERATOR_COLUMNS; static column_t cert_bund_adv_columns[] = CERT_BUND_ADV_INFO_ITERATOR_COLUMNS; static column_t config_columns[] = CONFIG_ITERATOR_COLUMNS; static column_t cpe_columns[] = CPE_INFO_ITERATOR_COLUMNS; static column_t credential_columns[] = CREDENTIAL_ITERATOR_COLUMNS; static column_t cve_columns[] = CVE_INFO_ITERATOR_COLUMNS; static column_t dfn_cert_adv_columns[] = DFN_CERT_ADV_INFO_ITERATOR_COLUMNS; static column_t filter_columns[] = FILTER_ITERATOR_COLUMNS; static column_t group_columns[] = GROUP_ITERATOR_COLUMNS; static column_t host_columns[] = HOST_ITERATOR_COLUMNS; static column_t note_columns[] = NOTE_ITERATOR_COLUMNS; static column_t nvt_columns[] = NVT_ITERATOR_COLUMNS; static column_t os_columns[] = OS_ITERATOR_COLUMNS; static column_t ovaldef_columns[] = OVALDEF_INFO_ITERATOR_COLUMNS; static column_t override_columns[] = OVERRIDE_ITERATOR_COLUMNS; static column_t permission_columns[] = PERMISSION_ITERATOR_COLUMNS; static column_t report_columns[] = REPORT_ITERATOR_COLUMNS; static column_t result_columns[] = RESULT_ITERATOR_COLUMNS; static column_t result_columns_no_cert[] = RESULT_ITERATOR_COLUMNS_NO_CERT; static column_t role_columns[] = ROLE_ITERATOR_COLUMNS; static column_t scanner_columns[] = SCANNER_ITERATOR_COLUMNS; static column_t schedule_columns[] = SCHEDULE_ITERATOR_COLUMNS; static column_t tag_columns[] = TAG_ITERATOR_COLUMNS; static column_t target_columns[] = TARGET_ITERATOR_COLUMNS; static column_t task_columns[] = TASK_ITERATOR_COLUMNS; static column_t user_columns[] = USER_ITERATOR_COLUMNS; static column_t vuln_columns[] = VULN_ITERATOR_COLUMNS; if (type == NULL) return NULL; if (strcasecmp (type, "ALERT") == 0) return alert_columns; if (strcasecmp (type, "CERT_BUND_ADV") == 0) return cert_bund_adv_columns; if (strcasecmp (type, "CONFIG") == 0) return config_columns; if (strcasecmp (type, "CPE") == 0) return cpe_columns; if (strcasecmp (type, "CREDENTIAL") == 0) return credential_columns; if (strcasecmp (type, "CVE") == 0) return cve_columns; if (strcasecmp (type, "DFN_CERT_ADV") == 0) return dfn_cert_adv_columns; if (strcasecmp (type, "FILTER") == 0) return filter_columns; if (strcasecmp (type, "GROUP") == 0) return group_columns; if (strcasecmp (type, "HOST") == 0) return host_columns; if (strcasecmp (type, "NOTE") == 0) return note_columns; if (strcasecmp (type, "NVT") == 0) return nvt_columns; if (strcasecmp (type, "OS") == 0) return os_columns; if (strcasecmp (type, "OVALDEF") == 0) return ovaldef_columns; if (strcasecmp (type, "OVERRIDE") == 0) return override_columns; if (strcasecmp (type, "PERMISSION") == 0) return permission_columns; if (strcasecmp (type, "PORT_LIST") == 0) return port_list_select_columns (); if (strcasecmp (type, "REPORT") == 0) return report_columns; if (strcasecmp (type, "REPORT_FORMAT") == 0) return report_format_select_columns (); if (strcasecmp (type, "RESULT") == 0) { if (manage_cert_loaded ()) return result_columns; return result_columns_no_cert; } if (strcasecmp (type, "ROLE") == 0) return role_columns; if (strcasecmp (type, "SCANNER") == 0) return scanner_columns; if (strcasecmp (type, "SCHEDULE") == 0) return schedule_columns; if (strcasecmp (type, "TAG") == 0) return tag_columns; if (strcasecmp (type, "TARGET") == 0) return target_columns; if (strcasecmp (type, "TASK") == 0) return task_columns; /* Tickets don't use this. */ assert (strcasecmp (type, "ticket")); if (strcasecmp (type, "TLS_CERTIFICATE") == 0) return tls_certificate_select_columns (); if (strcasecmp (type, "USER") == 0) return user_columns; if (strcasecmp (type, "VULN") == 0) return vuln_columns; return NULL; } /** * @brief Return the columns for a resource iterator. * * @param[in] type Resource type to get columns of. * * @return The columns. */ static column_t * type_where_columns (const char *type) { static column_t task_columns[] = TASK_ITERATOR_WHERE_COLUMNS; static column_t report_columns[] = REPORT_ITERATOR_WHERE_COLUMNS; static column_t host_columns[] = HOST_ITERATOR_WHERE_COLUMNS; static column_t os_columns[] = OS_ITERATOR_WHERE_COLUMNS; if (type == NULL) return NULL; if (strcasecmp (type, "TASK") == 0) return task_columns; if (strcasecmp (type, "REPORT") == 0) return report_columns; if (strcasecmp (type, "HOST") == 0) return host_columns; if (strcasecmp (type, "OS") == 0) return os_columns; return NULL; } /** * @brief Return the filter columns for a resource iterator. * * @param[in] type Resource type to get columns of. * * @return The filter columns. */ static const char** type_filter_columns (const char *type) { if (type == NULL) return NULL; if (strcasecmp (type, "ALERT") == 0) { static const char *ret[] = ALERT_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "CERT_BUND_ADV") == 0) { static const char *ret[] = CERT_BUND_ADV_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "CONFIG") == 0) { static const char *ret[] = CONFIG_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "CREDENTIAL") == 0) { static const char *ret[] = CREDENTIAL_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "CPE") == 0) { static const char *ret[] = CPE_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "CVE") == 0) { static const char *ret[] = CVE_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "DFN_CERT_ADV") == 0) { static const char *ret[] = DFN_CERT_ADV_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "FILTER") == 0) { static const char *ret[] = FILTER_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "GROUP") == 0) { static const char *ret[] = GROUP_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "HOST") == 0) { static const char *ret[] = HOST_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "NOTE") == 0) { static const char *ret[] = NOTE_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "NVT") == 0) { static const char *ret[] = NVT_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "OS") == 0) { static const char *ret[] = OS_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "OVALDEF") == 0) { static const char *ret[] = OVALDEF_INFO_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "OVERRIDE") == 0) { static const char *ret[] = OVERRIDE_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "PERMISSION") == 0) { static const char *ret[] = PERMISSION_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "PORT_LIST") == 0) return port_list_filter_columns (); if (strcasecmp (type, "REPORT") == 0) { static const char *ret[] = REPORT_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "REPORT_FORMAT") == 0) return report_format_filter_columns (); if (strcasecmp (type, "RESULT") == 0) { static const char *ret[] = RESULT_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "ROLE") == 0) { static const char *ret[] = ROLE_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "SCANNER") == 0) { static const char *ret[] = SCANNER_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "SCHEDULE") == 0) { static const char *ret[] = SCHEDULE_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "TAG") == 0) { static const char *ret[] = TAG_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "TARGET") == 0) { static const char *ret[] = TARGET_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "TASK") == 0) { static const char *ret[] = TASK_ITERATOR_FILTER_COLUMNS; return ret; } /* Tickets don't use this. */ assert (strcasecmp (type, "ticket")); if (strcasecmp (type, "TLS_CERTIFICATE") == 0) return tls_certificate_filter_columns (); if (strcasecmp (type, "USER") == 0) { static const char *ret[] = USER_ITERATOR_FILTER_COLUMNS; return ret; } if (strcasecmp (type, "VULN") == 0) { static const char *ret[] = VULN_ITERATOR_FILTER_COLUMNS; return ret; } return NULL; } /** * @brief Return the opts subquery definition for a resource type. * * @param[in] type Resource type to get columns of. * @param[in] filter Filter to apply. * * @return The SQL subquery definition. */ static gchar* type_opts_table (const char *type, const char *filter) { if (type == NULL) return NULL; if (strcasecmp (type, "TASK") == 0) return task_iterator_opts_table (filter_term_apply_overrides (filter), filter_term_min_qod (filter), 0); if (strcasecmp (type, "OS") == 0) return asset_os_iterator_opts_table (); if (strcasecmp (type, "REPORT") == 0) return report_iterator_opts_table (filter_term_apply_overrides (filter), filter_term_min_qod (filter)); if (strcasecmp (type, "RESULT") == 0) return result_iterator_opts_table (filter_term_apply_overrides (filter), setting_dynamic_severity_int ()); if (strcasecmp (type, "VULN") == 0) { gchar *task_id, *report_id, *host; gchar *ret; task_id = filter_term_value (filter, "task_id"); report_id = filter_term_value (filter, "report_id"); host = filter_term_value (filter, "host"); ret = vuln_iterator_opts_table (task_id, report_id, host, filter_term_min_qod (filter)); g_free (task_id); g_free (report_id); g_free (host); return ret; } return NULL; } /** * @brief Return the table name or union for a resource type. * * @param[in] type Resource type to get columns of. * @param[in] trash Whether to get the trash table. * * @return The SQL column definitions. */ static char* type_table (const char *type, int trash) { if (type == NULL) return NULL; if (trash && type_trash_in_table (type) == 0) return g_strdup_printf ("%ss_trash", type); if (trash == 0 || type_trash_in_table (type)) return g_strdup_printf ("%ss", type); return NULL; } /** * @brief Return addition to the WHERE clause if required for a resource type. * * @param[in] type Resource type to get columns of. * @param[in] trash Whether to get the trash table. * @param[in] filter The filter term. * @param[in] extra_params Optional extra parameters. * * @return The newly allocated WHERE clause additions. */ static gchar* type_extra_where (const char *type, int trash, const char *filter, GHashTable *extra_params) { gchar *extra_where; if (strcasecmp (type, "CONFIG") == 0 && extra_params) { gchar *usage_type; if (extra_params) usage_type = g_hash_table_lookup (extra_params, "usage_type"); else usage_type = NULL; extra_where = configs_extra_where (usage_type); if (extra_where == NULL) extra_where = g_strdup (""); } else if (strcasecmp (type, "TASK") == 0) { gchar *usage_type; if (extra_params) usage_type = g_hash_table_lookup (extra_params, "usage_type"); else usage_type = NULL; extra_where = tasks_extra_where (trash, usage_type); } else if (strcasecmp (type, "TLS_CERTIFICATE") == 0) { extra_where = tls_certificate_extra_where (filter); } else if (strcasecmp (type, "REPORT") == 0) { if (trash) extra_where = g_strdup (" AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 2"); else extra_where = g_strdup (" AND (SELECT hidden FROM tasks" " WHERE tasks.id = task)" " = 0"); } else if (strcasecmp (type, "RESULT") == 0) { int apply_overrides; gchar *report_id; report_t report; /* Note: This keyword may be removed or renamed at any time once there * is a better solution like an operator for conditions that must always * apply or support for parentheses in filters. */ report_id = filter_term_value (filter, "_and_report_id"); report = 0; if (report_id) { if (find_report_with_permission (report_id, &report, NULL)) { g_free (report_id); g_warning ("Failed to get report"); return NULL; } if (report == 0) report = -1; } g_free (report_id); apply_overrides = filter_term_apply_overrides (filter); extra_where = results_extra_where (trash, report, NULL, apply_overrides, setting_dynamic_severity_int (), filter, NULL); } else if (strcasecmp (type, "VULN") == 0) { extra_where = vulns_extra_where (); } else extra_where = g_strdup (""); return extra_where; } /** * @brief Get the extra WITH clauses for a resource type. * * @param[in] type The resource type. * * @return The extra WITH clauses. */ static const char * type_extra_with (const char *type) { return NULL; } /** * @brief Builds a filtered SELECT statement for a certain type. * * @param[in] type Resource type * @param[in] columns_str Columns to get (as used in SQL) * @param[in] get The get data * @param[in] distinct Whether to get distinct items * @param[in] ordered Whether to apply the ordering * @param[in] extra_tables Extra tables / subqueries * @param[in] given_extra_where Extra expressions for WHERE clause * @param[in] group_by Column(s) to group by, NULL for no grouping * @param[out] select Output of newly allocated SELECT statement * * @return 0: success, 1: filter not found, -1 error. */ static int type_build_select (const char *type, const char *columns_str, const get_data_t *get, gboolean distinct, gboolean ordered, const char *extra_tables, const char *given_extra_where, const char *group_by, gchar **select) { gchar *filter, *with; gchar *from_table, *opts_table; gchar *clause, *extra_where, *filter_order; int first, max; gchar *owned_clause, *owner_filter; array_t *permissions; const char *extra_with; column_t *select_columns, *where_columns; const char **filter_columns; gchar *pagination_clauses; assert (select); // Get filter if (get->filt_id && strcmp (get->filt_id, FILT_ID_NONE)) { if (get->filter_replacement) /* Replace the filter term with one given by the caller. This is * used by GET_REPORTS to use the default filter with any task (when * given the special value of -3 in filt_id). */ filter = g_strdup (get->filter_replacement); else filter = filter_term (get->filt_id); if (filter == NULL) { return 1; } } else filter = NULL; // FROM ... part from_table = type_table (type, get->trash); opts_table = type_opts_table (type, filter ? filter : get->filter); if (strcasecmp (type, "RESULT") == 0) { gchar *original; int overrides, dynamic; overrides = filter_term_apply_overrides (filter ? filter : get->filter); dynamic = setting_dynamic_severity_int (); original = opts_table; opts_table = g_strdup_printf (" LEFT OUTER JOIN nvts" " ON results.nvt = nvts.oid %s," " LATERAL %s AS lateral_new_severity", original, result_iterator_lateral (overrides, dynamic)); g_free (original); } // WHERE ... part select_columns = type_select_columns (type); where_columns = type_where_columns (type); filter_columns = type_filter_columns (type); if (filter_columns == NULL) return -1; clause = filter_clause (type, filter ? filter : get->filter, filter_columns, select_columns, where_columns, get->trash, &filter_order, &first, &max, &permissions, &owner_filter); owned_clause = acl_where_owned (type, get, type_owned (type), owner_filter, 0, permissions, 0, &with); if (given_extra_where) extra_where = g_strdup (given_extra_where); else extra_where = type_extra_where (type, get->trash, filter ? filter : get->filter, get->extra_params); if (get->ignore_pagination) pagination_clauses = NULL; else pagination_clauses = g_strdup_printf (" LIMIT %s OFFSET %d", sql_select_limit (max), first); extra_with = type_extra_with (type); if (extra_with) { if (with) { gchar *old_with; old_with = with; with = g_strdup_printf ("%s, %s", old_with, extra_with); g_free (old_with); } else with = g_strdup_printf ("WITH %s", extra_with); } *select = g_strdup_printf ("%s" // WITH "SELECT%s %s" // DISTINCT, columns " FROM %s%s%s" // from_table, opts_table, extra_tables " WHERE" " %s%s" // owned_clause, extra_where " %s%s%s" // (filter) clause " %s%s" // group_by " %s" // ORDER BY (filter_order) " %s", // pagination_clauses with ? with : "", distinct ? " DISTINCT" : "", columns_str, from_table, opts_table ? opts_table : "", extra_tables ? extra_tables : "", owned_clause, extra_where, clause ? " AND (" : "", clause ? clause : "", clause ? ")" : "", group_by ? " GROUP BY " : "", group_by ? group_by : "", (ordered && filter_order) ? filter_order : "", pagination_clauses ? pagination_clauses : ""); g_free (with); g_free (from_table); g_free (opts_table); g_free (owned_clause); g_free (extra_where); g_free (pagination_clauses); return 0; } /** * @brief Remove a resource from tags. * * @param[in] type Type. * @param[in] resource Resource. * @param[in] location Location: table or trash. */ void tags_remove_resource (const char *type, resource_t resource, int location) { sql ("DELETE FROM tag_resources" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", type, resource, location); } /** * @brief Adjust location of resource in tags. * * @param[in] type Type. * @param[in] old Resource ID in old table. * @param[in] new Resource ID in new table. * @param[in] to Destination, trash or table. */ void tags_set_locations (const char *type, resource_t old, resource_t new, int to) { sql ("UPDATE tag_resources SET resource_location = %i, resource = %llu" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", to, new, type, old, to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE); sql ("UPDATE tag_resources_trash SET resource_location = %i, resource = %llu" " WHERE resource_type = '%s' AND resource = %llu" " AND resource_location = %i;", to, new, type, old, to == LOCATION_TABLE ? LOCATION_TRASH : LOCATION_TABLE); } /** * @brief Get a GArray of all users as user_t. * * @return Newly allocated GArray containing all users. */ static GArray* all_users_array () { iterator_t users_iter; GArray *ret; ret = g_array_new (TRUE, TRUE, sizeof (resource_t)); init_iterator (&users_iter, "SELECT id FROM users;"); while (next (&users_iter)) { user_t user = iterator_int64 (&users_iter, 0); g_array_append_val (ret, user); } cleanup_iterator (&users_iter); return ret; } /** * @brief Update permissions cache for a resource. * * @param[in] type Resource type. * @param[in] resource The resource to update the cache for. * @param[in] cache_users GArray of users to create cache for or NULL for all. */ static void cache_permissions_for_resource (const char *type, resource_t resource, GArray *cache_users) { int free_users; if (type == NULL || resource == 0 || resource == -1) return; if (cache_users == NULL) { g_debug ("%s: Getting all users", __func__); free_users = 1; cache_users = all_users_array (); } else free_users = 0; if (strcmp (type, "task") == 0) { char* old_current_user_id; gchar *resource_id; int user_index; old_current_user_id = current_credentials.uuid; resource_id = resource_uuid (type, resource); g_debug ("%s: Caching permissions on %s \"%s\" for %d user(s)", __func__, type, resource_id, cache_users->len); for (user_index = 0; user_index < cache_users->len; user_index++) { user_t user; gchar *user_id; user = g_array_index (cache_users, user_t, user_index); user_id = user_uuid (user); current_credentials.uuid = user_id; manage_session_init (user_id); if (sql_int ("SELECT count(*) FROM permissions_get_%ss" " WHERE \"user\" = %llu" " AND %s = %llu;", type, user, type, resource)) { sql ("UPDATE permissions_get_%ss" " SET has_permission" " = user_has_access_uuid (cast ('%s' as text)," " cast ('%s' as text)," " cast ('get_%ss' as text)," " 0)" " WHERE \"user\" = %llu" " AND %s = %llu;", type, type, resource_id, type, user, type, resource); } else { sql ("INSERT INTO permissions_get_%ss" " (\"user\", %s, has_permission)" " SELECT %llu, %llu," " user_has_access_uuid (cast ('%s' as text)," " cast ('%s' as text)," " cast ('get_%ss' as text)," " 0);", type, type, user, resource, type, resource_id, type); } g_free (user_id); current_credentials.uuid = NULL; } current_credentials.uuid = old_current_user_id; manage_session_init (old_current_user_id); g_free (resource_id); } if (free_users) g_array_free (cache_users, TRUE); } /** * @brief Update permissions cache for a given type and selection of users. * * @param[in] type Type. * @param[in] cache_users GArray of users to create cache for. */ static void cache_permissions_for_users (const char *type, GArray *cache_users) { int free_users; if (type == NULL) return; if (cache_users == NULL) { g_debug ("%s: Getting all users", __func__); free_users = 1; cache_users = all_users_array (); } else free_users = 0; if (strcmp (type, "task") == 0) { iterator_t resources; init_iterator (&resources, "SELECT id FROM %ss;", type); while (next (&resources)) { resource_t resource = iterator_int64 (&resources, 0); cache_permissions_for_resource (type, resource, cache_users); } cleanup_iterator (&resources); } if (free_users) g_array_free (cache_users, TRUE); } /** * @brief Update entire permission cache the given users. * * @param[in] cache_users GArray of users to create cache for. NULL means * all users. */ static void cache_all_permissions_for_users (GArray *cache_users) { int free_users; if (cache_users == NULL) { g_debug ("%s: Getting all users", __func__); free_users = 1; cache_users = all_users_array (); } else free_users = 0; cache_permissions_for_users ("task", cache_users); if (free_users) g_array_free (cache_users, TRUE); } /** * @brief Delete permission cache a resource. * * @param[in] type Resource type. * @param[in] resource Resource. */ void delete_permissions_cache_for_resource (const char* type, resource_t resource) { if (type == NULL || resource == 0) return; if (strcmp (type, "task") == 0) { sql ("DELETE FROM permissions_get_%ss WHERE \"%s\" = %llu", type, type, resource); } } /** * @brief Delete permission cache the given user. * * @param[in] user User. */ void delete_permissions_cache_for_user (user_t user) { sql ("DELETE FROM permissions_get_tasks WHERE \"user\" = %llu;", user); } /* Optimize. */ /** * @brief Run one of the optimizations. * * @param[in] log_config Log configuration. * @param[in] database Location of manage database. * @param[in] name Name of optimization. * * @return 0 success, 1 error in name, -1 error, * -2 database is wrong version, -3 database needs to be initialised * from server. */ int manage_optimize (GSList *log_config, const db_conn_info_t *database, const gchar *name) { gchar *success_text; int ret; g_info (" Optimizing: %s.", name); if (name == NULL) { fprintf (stderr, "Name required for optimize.\n"); return 1; } ret = manage_option_setup (log_config, database); if (ret) return ret; ret = 0; if (strcasecmp (name, "vacuum") == 0) { gchar *quoted_db_name; unsigned long long int old_size, new_size; quoted_db_name = sql_quote (sql_database ()); old_size = sql_int64_0 ("SELECT pg_database_size ('%s')", quoted_db_name); sql ("VACUUM;"); new_size = sql_int64_0 ("SELECT pg_database_size ('%s')", quoted_db_name); g_free (quoted_db_name); if (old_size <= 0 || new_size <= 0) success_text = g_strdup_printf ("Optimized: vacuum."); else if (new_size <= old_size) success_text = g_strdup_printf ("Optimized: vacuum." " Database file size reduced by" " %llu MiB (%0.1f %%).\n", (old_size - new_size) / (1024 * 1024), (old_size - new_size) * 100.0 / old_size); else success_text = g_strdup_printf ("Optimized: vacuum." " Database file size *increased* by" " %llu MiB (%0.1f %%).\n", (new_size - old_size) / (1024 * 1024), (new_size - old_size) * 100.0 / old_size); } else if (strcasecmp (name, "add-feed-permissions") == 0) { int permissions_count, object_count; permissions_count = 0; object_count = 0; sql_begin_immediate (); add_feed_role_permissions ("config", "Scan Config / Policy", &permissions_count, &object_count); add_feed_role_permissions ("port_list", "Port List", &permissions_count, &object_count); add_feed_role_permissions ("report_format", "Report Format", &permissions_count, &object_count); sql_commit (); success_text = g_strdup_printf ("Optimized: add-feed-permissions." " Added %d permissions" " for %d data objects.", permissions_count, object_count); } else if (strcasecmp (name, "analyze") == 0) { sql ("ANALYZE;"); success_text = g_strdup_printf ("Optimized: analyze."); } else if (strcasecmp (name, "cleanup-config-prefs") == 0) { int removed, fixed_values; sql ("DELETE FROM config_preferences WHERE id NOT IN" " (SELECT min(id) FROM config_preferences" " GROUP BY config, type, name, value);"); removed = sql_changes(); sql ("UPDATE config_preferences" " SET value = (SELECT value FROM nvt_preferences" " WHERE name='scanner_plugins_timeout')" " WHERE name = 'scanner_plugins_timeout'" " AND value = 'SCANNER_NVT_TIMEOUT';"); fixed_values = sql_changes(); success_text = g_strdup_printf ("Optimized: cleanup-config-prefs." " Duplicate config preferences removed:" " %d. Corrected preference values: %d", removed, fixed_values); } else if (strcasecmp (name, "cleanup-feed-permissions") == 0) { int permissions_count, object_count; permissions_count = 0; object_count = 0; sql_begin_immediate (); clean_feed_role_permissions ("config", "Scan Config / Policy", &permissions_count, &object_count); clean_feed_role_permissions ("port_list", "Port List", &permissions_count, &object_count); clean_feed_role_permissions ("report_format", "Report Format", &permissions_count, &object_count); sql_commit (); success_text = g_strdup_printf ("Optimized: cleanup-feed-permissions." " Removed %d permissions" " for %d data objects.", permissions_count, object_count); } else if (strcasecmp (name, "cleanup-port-names") == 0) { int changes_iana, changes_old_format; sql_begin_immediate (); sql ("UPDATE results" " SET port = substr (port, 1," " strpos (port, ' (IANA:') - 1)" " WHERE port LIKE '% (IANA:%';"); changes_iana = sql_changes(); sql ("UPDATE results" " SET port = substr (port," " strpos (port ,'(') + 1," " strpos (port, ')') - strpos (port, '(') - 1)" " WHERE port LIKE '%(%)%';"); changes_old_format = sql_changes(); sql_commit (); success_text = g_strdup_printf ("Optimized: cleanup-port-names." " Ports converted from old format: %d," " removed IANA port names: %d.", changes_old_format, changes_iana); } else if (strcasecmp (name, "cleanup-report-formats") == 0) { iterator_t alert_data; int alert_changes = 0; /* Clean up alerts with missing report formats */ sql_begin_immediate (); init_iterator (&alert_data, "SELECT id, data," " (SELECT uuid FROM alerts WHERE id = alert)" " FROM alert_method_data" " WHERE (name = 'notice_attach_format'" " OR name = 'notice_report_format'" " OR name = 'send_report_format')" " AND data NOT IN (SELECT uuid" " FROM report_formats)" " AND data NOT IN (SELECT uuid" " FROM report_formats_trash)"); while (next (&alert_data)) { alert_changes ++; g_message ("Alert %s uses a non-existent report format (%s)" " and will now use the TXT report format (%s)" " if TXT exists.", iterator_string (&alert_data, 2), iterator_string (&alert_data, 1), "a3810a62-1f62-11e1-9219-406186ea4fc5"); sql ("UPDATE alert_method_data SET data = '%s'" " WHERE id = %llu", "a3810a62-1f62-11e1-9219-406186ea4fc5", iterator_int64 (&alert_data, 0)); } cleanup_iterator(&alert_data); init_iterator (&alert_data, "SELECT id, data," " (SELECT uuid FROM alerts_trash WHERE id = alert)" " FROM alert_method_data_trash" " WHERE (name = 'notice_attach_format'" " OR name = 'notice_report_format'" " OR name = 'send_report_format')" " AND data NOT IN (SELECT uuid" " FROM report_formats)" " AND data NOT IN (SELECT uuid" " FROM report_formats_trash)"); while (next (&alert_data)) { alert_changes ++; g_warning ("Trash Alert %s uses a non-existent report format (%s)" " and will now use the TXT report format (%s)" " if TXT exists.", iterator_string (&alert_data, 2), iterator_string (&alert_data, 1), "a3810a62-1f62-11e1-9219-406186ea4fc5"); sql ("UPDATE alert_method_data_trash SET data = '%s'" " WHERE id = %llu", "a3810a62-1f62-11e1-9219-406186ea4fc5", iterator_int64 (&alert_data, 0)); } cleanup_iterator(&alert_data); sql_commit (); success_text = g_strdup_printf ("Optimized: cleanup-report-formats." " Cleaned up report format references in" " %d alert(s).", alert_changes); } else if (strcasecmp (name, "cleanup-result-nvts") == 0) { sql_begin_immediate (); if (cleanup_result_nvts ()) { sql_rollback(); fprintf (stderr, "Clean-up of result_nvts failed.\n"); manage_option_cleanup (); return 1; } sql_commit (); success_text = g_strdup_printf ("Optimized: Cleaned up result_nvts."); } else if (strcasecmp (name, "cleanup-result-severities") == 0) { int missing_severity_changes = 0; sql_begin_immediate (); sql ("UPDATE results" " SET severity" " = (SELECT CAST (value AS real) FROM settings" " WHERE uuid = '7eda49c5-096c-4bef-b1ab-d080d87300df'" " AND (settings.owner = results.owner" " OR settings.owner IS NULL)" " ORDER BY settings.owner DESC LIMIT 1)" " WHERE severity IS NULL;"); missing_severity_changes = sql_changes(); sql_commit (); success_text = g_strdup_printf ("Optimized: cleanup-result-severities." " Missing severity scores added: %d.", missing_severity_changes); } else if (strcasecmp (name, "cleanup-result-encoding") == 0) { sql_begin_immediate (); g_debug ("%s: Stripping control chars out of result descriptions", __func__); sql ("UPDATE results" " SET description = regexp_replace (description," " '[\x01-\x09\xB-\x1F]'," " ' '," " 'g')" " WHERE description ~ '[\x01-\x09\xB-\x1F]';"); sql_commit (); success_text = g_strdup_printf ("Optimized: Cleaned up result encoding."); } else if (strcasecmp (name, "cleanup-schedule-times") == 0) { int changes; sql_begin_immediate (); changes = cleanup_schedule_times (); sql_commit (); success_text = g_strdup_printf ("Optimized: cleanup-schedule-times." " Due date updated for %d tasks.", changes); } else if (strcasecmp (name, "migrate-relay-sensors") == 0) { if (get_relay_mapper_path ()) { sql_begin_immediate (); success_text = manage_migrate_relay_sensors (); sql_commit (); } else { fprintf (stderr, "No relay mapper found." " Please check your $PATH or the --relay-mapper option.\n"); success_text = NULL; ret = -1; } } else if (strcasecmp (name, "rebuild-permissions-cache") == 0) { sql_begin_immediate (); sql ("DELETE FROM permissions_get_tasks"); cache_all_permissions_for_users (NULL); sql_commit (); success_text = g_strdup_printf ("Optimized: rebuild-permissions-cache." " Permission cache rebuilt."); } else if (strcasecmp (name, "rebuild-report-cache") == 0) { int changes; sql_begin_immediate (); reports_build_count_cache (1, &changes); sql_commit (); success_text = g_strdup_printf ("Optimized: rebuild-report-cache." " Result counts recalculated for %d" " reports.", changes); } else if (strcasecmp (name, "update-report-cache") == 0) { int changes; sql_begin_immediate (); reports_build_count_cache (0, &changes); sql_commit (); success_text = g_strdup_printf ("Optimized: update-report-cache." " Result counts calculated for %d" " reports.", changes); } else { fprintf (stderr, "Error in optimize name.\n"); ret = 1; success_text = NULL; } if (success_text) { printf ("%s\n", success_text); g_message (" %s", success_text); g_free (success_text); } current_credentials.uuid = NULL; manage_option_cleanup (); return ret; } /** * @brief Cancels the current SQL statement. * * @return 0 on success, -1 on error. */ int sql_cancel () { g_debug ("%s: cancelling current SQL statement", __func__); return sql_cancel_internal (); } /** * @brief Get the VT verification collation override. * * @return The collation or NULL for automatic. */ const char * get_vt_verification_collation () { return vt_verification_collation; } /** * @brief Sets the VT verification collation override. * * This must be done before the SQL functions are created to be effective. * * @param[in] new_collation The new collation. */ void set_vt_verification_collation (const char *new_collation) { g_free (vt_verification_collation); if (new_collation && strcmp (new_collation, "")) vt_verification_collation = g_strdup(new_collation); else vt_verification_collation = NULL; }