feat(osint): create DNS result relations
This commit is contained in:
parent
f6f3e71002
commit
16a5d81e95
15 changed files with 340 additions and 75 deletions
2
Makefile
2
Makefile
|
|
@ -565,7 +565,9 @@ $(TEST_OSINT_DNS_INTEGRATION): \
|
|||
src/models/osint_dns_proposal.c \
|
||||
src/models/osint_dns_query.c \
|
||||
src/models/entity_record.c \
|
||||
src/models/relation_record.c \
|
||||
src/dao/entity_dao.c \
|
||||
src/dao/relation_dao.c \
|
||||
src/database/database.c \
|
||||
src/database/schema.c \
|
||||
src/database/statement.c \
|
||||
|
|
|
|||
|
|
@ -149,7 +149,9 @@ Le socle actuel comprend notamment :
|
|||
- affichage sélectionnable des sorties standard et d’erreur, sans persistance ;
|
||||
- révision des réponses DNS sous forme de propositions avant intégration ;
|
||||
- sélection explicite et intégration transactionnelle des propositions DNS
|
||||
compatibles, avec normalisation et détection des doublons.
|
||||
compatibles, avec normalisation et détection des doublons ;
|
||||
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
||||
`uses_name_server` depuis l'entité interrogée.
|
||||
|
||||
Les outils actuellement présents dans le catalogue initial sont :
|
||||
|
||||
|
|
|
|||
|
|
@ -1109,8 +1109,10 @@ enregistrées.
|
|||
### Limite actuelle de la traçabilité OSINT
|
||||
|
||||
L'intégration DNS initiale peut créer des entités `domain_name` et
|
||||
`ip_address`. La description de l'entité conserve une indication minimale :
|
||||
outil `dns.dig`, cible interrogée et type d'enregistrement.
|
||||
`ip_address`, ainsi que les relations `resolves_to`, `aliases_to` et
|
||||
`uses_name_server`. La description de l'entité et la justification de la
|
||||
relation conservent une indication minimale : outil `dns.dig`, cible
|
||||
interrogée et type d'enregistrement.
|
||||
|
||||
Cette information ne remplace pas une provenance structurée. Une migration
|
||||
future devra introduire un objet de résultat OSINT capable de conserver au
|
||||
|
|
|
|||
|
|
@ -32,25 +32,31 @@ typedef enum
|
|||
GQuark osint_dns_integration_error_quark(void);
|
||||
|
||||
/**
|
||||
* @brief Intègre une sélection de propositions dans une transaction unique.
|
||||
* @brief Intègre des propositions et leurs relations dans une transaction.
|
||||
*
|
||||
* Les propositions doivent être des OsintDnsProposal empruntées. Les valeurs
|
||||
* incompatibles sont ignorées. Les couples type/valeur déjà présents sont
|
||||
* comptés comme doublons et ne sont pas réinsérés.
|
||||
*
|
||||
* @param database Connexion SQLite active.
|
||||
* @param source_entity_identifier UUID de l'entité interrogée.
|
||||
* @param selected_proposals Propositions explicitement sélectionnées.
|
||||
* @param out_inserted_count Nombre d'entités créées.
|
||||
* @param out_skipped_count Nombre de propositions ignorées ou déjà présentes.
|
||||
* @param out_inserted_entity_count Nombre d'entités créées.
|
||||
* @param out_skipped_entity_count Nombre d'entités ignorées ou réutilisées.
|
||||
* @param out_inserted_relation_count Nombre de relations créées.
|
||||
* @param out_skipped_relation_count Nombre de relations ignorées.
|
||||
* @param error Emplacement facultatif recevant une erreur.
|
||||
*
|
||||
* @return TRUE lorsque la transaction est validée, sinon FALSE.
|
||||
*/
|
||||
gboolean osint_dns_integration_apply(
|
||||
Database *database,
|
||||
const char *source_entity_identifier,
|
||||
GPtrArray *selected_proposals,
|
||||
guint *out_inserted_count,
|
||||
guint *out_skipped_count,
|
||||
guint *out_inserted_entity_count,
|
||||
guint *out_skipped_entity_count,
|
||||
guint *out_inserted_relation_count,
|
||||
guint *out_skipped_relation_count,
|
||||
GError **error
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,20 @@ char *osint_dns_proposal_dup_normalized_value(
|
|||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le type de relation correspondant au type DNS.
|
||||
*
|
||||
* A, AAAA et PTR utilisent `resolves_to`, CNAME utilise `aliases_to` et NS
|
||||
* utilise `uses_name_server`.
|
||||
*
|
||||
* @param proposal Proposition consultée.
|
||||
*
|
||||
* @return Identifiant statique de relation, ou NULL.
|
||||
*/
|
||||
const char *osint_dns_proposal_get_relation_type(
|
||||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -125,11 +125,13 @@ typedef void (*MainWindowAddRelationCallback)(
|
|||
* @brief Callback appelé lors du déclenchement d'une action OSINT.
|
||||
*
|
||||
* @param action_identifier Identifiant stable de l'action.
|
||||
* @param target_identifier UUID de la sélection ciblée.
|
||||
* @param target_value Valeur métier ciblée.
|
||||
* @param user_data Données privées fournies par l'appelant.
|
||||
*/
|
||||
typedef void (*MainWindowOsintActionCallback)(
|
||||
const char *action_identifier,
|
||||
const char *target_identifier,
|
||||
const char *target_value,
|
||||
gpointer user_data
|
||||
);
|
||||
|
|
|
|||
|
|
@ -84,11 +84,13 @@ typedef void (*WorkspaceAddRelationCallback)(
|
|||
* Les chaînes sont empruntées et uniquement valides pendant l'appel.
|
||||
*
|
||||
* @param action_identifier Identifiant stable de l'action.
|
||||
* @param target_identifier UUID de la sélection ciblée.
|
||||
* @param target_value Valeur métier ciblée par l'action.
|
||||
* @param user_data Données empruntées fournies par l'appelant.
|
||||
*/
|
||||
typedef void (*WorkspaceOsintActionCallback)(
|
||||
const char *action_identifier,
|
||||
const char *target_identifier,
|
||||
const char *target_value,
|
||||
gpointer user_data
|
||||
);
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ typedef struct
|
|||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *target_identifier;
|
||||
char *target_value;
|
||||
} ApplicationOsintActionContext;
|
||||
|
||||
|
|
@ -169,6 +170,7 @@ typedef struct
|
|||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *source_entity_identifier;
|
||||
GPtrArray *proposals;
|
||||
} ApplicationOsintReviewContext;
|
||||
|
||||
|
|
@ -189,6 +191,7 @@ static void application_osint_review_context_free(gpointer user_data)
|
|||
ApplicationOsintReviewContext *context = user_data;
|
||||
if (context == NULL) return;
|
||||
g_clear_pointer(&context->proposals, g_ptr_array_unref);
|
||||
g_free(context->source_entity_identifier);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
|
|
@ -198,16 +201,21 @@ static void application_on_osint_integration_confirmed(
|
|||
gpointer user_data
|
||||
)
|
||||
{
|
||||
Application *application = user_data;
|
||||
ApplicationOsintReviewContext *review_context = user_data;
|
||||
Application *application = review_context != NULL
|
||||
? review_context->application : NULL;
|
||||
const InvestigationProject *project = NULL;
|
||||
Database *database = NULL;
|
||||
const char *database_path = NULL;
|
||||
guint inserted_count = 0U;
|
||||
guint skipped_count = 0U;
|
||||
guint inserted_relation_count = 0U;
|
||||
guint skipped_relation_count = 0U;
|
||||
char *message = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
if (application == NULL || application->session == NULL ||
|
||||
if (application == NULL || review_context->source_entity_identifier == NULL ||
|
||||
application->session == NULL ||
|
||||
application->main_window == NULL)
|
||||
{
|
||||
return;
|
||||
|
|
@ -218,8 +226,9 @@ static void application_on_osint_integration_confirmed(
|
|||
database_path = project != NULL
|
||||
? investigation_project_get_database_path(project) : NULL;
|
||||
if (!osint_dns_integration_apply(
|
||||
database, selected_proposals, &inserted_count, &skipped_count,
|
||||
&error
|
||||
database, review_context->source_entity_identifier,
|
||||
selected_proposals, &inserted_count, &skipped_count,
|
||||
&inserted_relation_count, &skipped_relation_count, &error
|
||||
))
|
||||
{
|
||||
application_present_error(
|
||||
|
|
@ -233,10 +242,12 @@ static void application_on_osint_integration_confirmed(
|
|||
}
|
||||
|
||||
message = g_strdup_printf(
|
||||
"%u entité(s) créée(s), %u proposition(s) ignorée(s).\n\n"
|
||||
"%u entité(s) créée(s), %u entité(s) réutilisée(s) ou ignorée(s).\n"
|
||||
"%u relation(s) créée(s), %u relation(s) ignorée(s).\n\n"
|
||||
"La provenance complète devra être ajoutée par une future migration "
|
||||
"du schéma OSINT.",
|
||||
inserted_count, skipped_count
|
||||
inserted_count, skipped_count,
|
||||
inserted_relation_count, skipped_relation_count
|
||||
);
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(application->main_window),
|
||||
|
|
@ -260,7 +271,7 @@ static void application_on_osint_prepare_integration(gpointer user_data)
|
|||
main_window_get_window(context->application->main_window),
|
||||
context->proposals,
|
||||
application_on_osint_integration_confirmed,
|
||||
context->application
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -279,6 +290,7 @@ static void application_osint_action_context_free(
|
|||
}
|
||||
|
||||
g_free(context->target_value);
|
||||
g_free(context->target_identifier);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
|
|
@ -1178,9 +1190,13 @@ static void application_on_dns_lookup_completed(
|
|||
if (review_context != NULL)
|
||||
{
|
||||
review_context->application = application;
|
||||
review_context->source_entity_identifier = g_strdup(
|
||||
context->target_identifier
|
||||
);
|
||||
review_context->proposals = g_ptr_array_ref(proposals);
|
||||
}
|
||||
if (review_context == NULL || review_context->proposals == NULL)
|
||||
if (review_context == NULL || review_context->proposals == NULL ||
|
||||
review_context->source_entity_identifier == NULL)
|
||||
{
|
||||
application_osint_review_context_free(review_context);
|
||||
review_context = NULL;
|
||||
|
|
@ -1224,6 +1240,7 @@ static void application_on_dns_lookup_completed(
|
|||
*/
|
||||
static void application_start_dns_lookup(
|
||||
Application *application,
|
||||
const char *target_identifier,
|
||||
const char *target_value
|
||||
)
|
||||
{
|
||||
|
|
@ -1236,6 +1253,7 @@ static void application_start_dns_lookup(
|
|||
|
||||
if (application == NULL || application->task_manager == NULL ||
|
||||
application->tool_initializer == NULL || target_value == NULL ||
|
||||
target_identifier == NULL || !g_uuid_string_is_valid(target_identifier) ||
|
||||
!osint_dns_query_is_valid_target(target_value))
|
||||
{
|
||||
if (application != NULL)
|
||||
|
|
@ -1274,10 +1292,12 @@ static void application_start_dns_lookup(
|
|||
if (context != NULL)
|
||||
{
|
||||
context->application = application;
|
||||
context->target_identifier = g_strdup(target_identifier);
|
||||
context->target_value = g_strdup(target_value);
|
||||
}
|
||||
|
||||
if (context == NULL || context->target_value == NULL)
|
||||
if (context == NULL || context->target_identifier == NULL ||
|
||||
context->target_value == NULL)
|
||||
{
|
||||
application_osint_action_context_free(context);
|
||||
tool_task_free(tool_task);
|
||||
|
|
@ -1319,6 +1339,7 @@ static void application_start_dns_lookup(
|
|||
*/
|
||||
static void application_on_osint_action_requested(
|
||||
const char *action_identifier,
|
||||
const char *target_identifier,
|
||||
const char *target_value,
|
||||
gpointer user_data
|
||||
)
|
||||
|
|
@ -1327,7 +1348,9 @@ static void application_on_osint_action_requested(
|
|||
|
||||
if (g_strcmp0(action_identifier, "dns-preview") == 0)
|
||||
{
|
||||
application_start_dns_lookup(application, target_value);
|
||||
application_start_dns_lookup(
|
||||
application, target_identifier, target_value
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
#include "core/osint_dns_integration.h"
|
||||
|
||||
#include "dao/entity_dao.h"
|
||||
#include "dao/relation_dao.h"
|
||||
#include "database/error.h"
|
||||
#include "database/transaction.h"
|
||||
#include "models/entity_record.h"
|
||||
#include "models/osint_dns_proposal.h"
|
||||
#include "models/relation_record.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <string.h>
|
||||
|
|
@ -50,6 +52,18 @@ static char *osint_dns_integration_build_key(
|
|||
return g_strdup_printf("%s\n%s", entity_type, value);
|
||||
}
|
||||
|
||||
/** @brief Construit la clé anti-doublon d'une relation orientée. */
|
||||
static char *osint_dns_integration_build_relation_key(
|
||||
const char *source_identifier,
|
||||
const char *target_identifier,
|
||||
const char *relation_type
|
||||
)
|
||||
{
|
||||
return g_strdup_printf(
|
||||
"%s\n%s\n%s", source_identifier, target_identifier, relation_type
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Renseigne une erreur littérale si possible. */
|
||||
static void osint_dns_integration_set_error(
|
||||
GError **error,
|
||||
|
|
@ -68,25 +82,38 @@ GQuark osint_dns_integration_error_quark(void)
|
|||
|
||||
gboolean osint_dns_integration_apply(
|
||||
Database *database,
|
||||
const char *source_entity_identifier,
|
||||
GPtrArray *selected_proposals,
|
||||
guint *out_inserted_count,
|
||||
guint *out_skipped_count,
|
||||
guint *out_inserted_entity_count,
|
||||
guint *out_skipped_entity_count,
|
||||
guint *out_inserted_relation_count,
|
||||
guint *out_skipped_relation_count,
|
||||
GError **error
|
||||
)
|
||||
{
|
||||
EntityDao *entity_dao = NULL;
|
||||
RelationDao *relation_dao = NULL;
|
||||
GPtrArray *existing_entities = NULL;
|
||||
GPtrArray *existing_relations = NULL;
|
||||
GHashTable *known_entities = NULL;
|
||||
GHashTable *known_relations = NULL;
|
||||
EntityRecord *source_entity = NULL;
|
||||
GDateTime *now = NULL;
|
||||
char *timestamp = NULL;
|
||||
guint inserted_count = 0U;
|
||||
guint skipped_count = 0U;
|
||||
guint inserted_entity_count = 0U;
|
||||
guint skipped_entity_count = 0U;
|
||||
guint inserted_relation_count = 0U;
|
||||
guint skipped_relation_count = 0U;
|
||||
gboolean success = FALSE;
|
||||
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
if (out_inserted_count != NULL) *out_inserted_count = 0U;
|
||||
if (out_skipped_count != NULL) *out_skipped_count = 0U;
|
||||
if (database == NULL || selected_proposals == NULL ||
|
||||
if (out_inserted_entity_count != NULL) *out_inserted_entity_count = 0U;
|
||||
if (out_skipped_entity_count != NULL) *out_skipped_entity_count = 0U;
|
||||
if (out_inserted_relation_count != NULL) *out_inserted_relation_count = 0U;
|
||||
if (out_skipped_relation_count != NULL) *out_skipped_relation_count = 0U;
|
||||
if (database == NULL || source_entity_identifier == NULL ||
|
||||
!g_uuid_string_is_valid(source_entity_identifier) ||
|
||||
selected_proposals == NULL ||
|
||||
selected_proposals->len == 0U)
|
||||
{
|
||||
osint_dns_integration_set_error(
|
||||
|
|
@ -98,9 +125,27 @@ gboolean osint_dns_integration_apply(
|
|||
|
||||
entity_dao = entity_dao_new(database, error);
|
||||
if (entity_dao == NULL) return FALSE;
|
||||
relation_dao = relation_dao_new(database, error);
|
||||
if (relation_dao == NULL) goto cleanup;
|
||||
source_entity = entity_dao_find_by_identifier(
|
||||
entity_dao, source_entity_identifier, error
|
||||
);
|
||||
if (source_entity == NULL)
|
||||
{
|
||||
if (error == NULL || *error == NULL)
|
||||
osint_dns_integration_set_error(
|
||||
error, OSINT_DNS_INTEGRATION_ERROR_INVALID_ARGUMENT,
|
||||
"L'entité source de la résolution DNS est introuvable."
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
existing_entities = entity_dao_list_all(entity_dao, error);
|
||||
if (existing_entities == NULL) goto cleanup;
|
||||
known_entities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
existing_relations = relation_dao_list_all(relation_dao, error);
|
||||
if (existing_relations == NULL) goto cleanup;
|
||||
known_entities = g_hash_table_new_full(
|
||||
g_str_hash, g_str_equal, g_free, g_free
|
||||
);
|
||||
for (guint index = 0; index < existing_entities->len; index++)
|
||||
{
|
||||
const EntityRecord *entity = g_ptr_array_index(existing_entities, index);
|
||||
|
|
@ -108,15 +153,33 @@ gboolean osint_dns_integration_apply(
|
|||
char *normalized_value = osint_dns_integration_normalize_existing_value(
|
||||
entity_type, entity_record_get_value(entity)
|
||||
);
|
||||
g_hash_table_add(
|
||||
g_hash_table_insert(
|
||||
known_entities,
|
||||
osint_dns_integration_build_key(
|
||||
entity_type,
|
||||
normalized_value
|
||||
)
|
||||
),
|
||||
g_strdup(entity_record_get_identifier(entity))
|
||||
);
|
||||
g_free(normalized_value);
|
||||
}
|
||||
known_relations = g_hash_table_new_full(
|
||||
g_str_hash, g_str_equal, g_free, NULL
|
||||
);
|
||||
for (guint index = 0; index < existing_relations->len; index++)
|
||||
{
|
||||
const RelationRecord *relation = g_ptr_array_index(
|
||||
existing_relations, index
|
||||
);
|
||||
g_hash_table_add(
|
||||
known_relations,
|
||||
osint_dns_integration_build_relation_key(
|
||||
relation_record_get_source_entity_identifier(relation),
|
||||
relation_record_get_target_entity_identifier(relation),
|
||||
relation_record_get_relation_type(relation)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
now = g_date_time_new_now_utc();
|
||||
timestamp = now != NULL
|
||||
|
|
@ -146,29 +209,39 @@ gboolean osint_dns_integration_apply(
|
|||
selected_proposals, index
|
||||
);
|
||||
const char *entity_type = osint_dns_proposal_get_entity_type(proposal);
|
||||
const char *relation_type =
|
||||
osint_dns_proposal_get_relation_type(proposal);
|
||||
char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal);
|
||||
char *entity_key = NULL;
|
||||
char *identifier = NULL;
|
||||
char *target_identifier = NULL;
|
||||
char *description = NULL;
|
||||
EntityRecord *entity = NULL;
|
||||
RelationRecord *relation = NULL;
|
||||
char *relation_identifier = NULL;
|
||||
char *relation_key = NULL;
|
||||
char *relation_label = NULL;
|
||||
char *relation_justification = NULL;
|
||||
GError *model_error = NULL;
|
||||
|
||||
if (entity_type == NULL || normalized_value == NULL)
|
||||
if (entity_type == NULL || relation_type == NULL ||
|
||||
normalized_value == NULL)
|
||||
{
|
||||
skipped_count++;
|
||||
skipped_entity_count++;
|
||||
skipped_relation_count++;
|
||||
g_free(normalized_value);
|
||||
continue;
|
||||
}
|
||||
entity_key = osint_dns_integration_build_key(entity_type, normalized_value);
|
||||
if (g_hash_table_contains(known_entities, entity_key))
|
||||
target_identifier = g_strdup(
|
||||
g_hash_table_lookup(known_entities, entity_key)
|
||||
);
|
||||
if (target_identifier != NULL)
|
||||
{
|
||||
skipped_count++;
|
||||
g_free(entity_key);
|
||||
g_free(normalized_value);
|
||||
continue;
|
||||
skipped_entity_count++;
|
||||
}
|
||||
|
||||
identifier = g_uuid_string_random();
|
||||
else
|
||||
{
|
||||
target_identifier = g_uuid_string_random();
|
||||
description = g_strdup_printf(
|
||||
"Proposition DNS %s obtenue avec dns.dig depuis %s. "
|
||||
"Résultat OSINT à vérifier.",
|
||||
|
|
@ -176,9 +249,9 @@ gboolean osint_dns_integration_apply(
|
|||
osint_dns_proposal_get_target(proposal)
|
||||
);
|
||||
entity = entity_record_new(
|
||||
identifier, entity_type, normalized_value, normalized_value,
|
||||
description, 50, timestamp, timestamp, ENTITY_STATUS_ACTIVE,
|
||||
&model_error
|
||||
target_identifier, entity_type, normalized_value,
|
||||
normalized_value, description, 50, timestamp, timestamp,
|
||||
ENTITY_STATUS_ACTIVE, &model_error
|
||||
);
|
||||
if (entity == NULL || !entity_dao_insert(entity_dao, entity, error))
|
||||
{
|
||||
|
|
@ -187,19 +260,80 @@ gboolean osint_dns_integration_apply(
|
|||
entity_record_free(entity);
|
||||
g_clear_error(&model_error);
|
||||
g_free(description);
|
||||
g_free(identifier);
|
||||
g_free(target_identifier);
|
||||
g_free(entity_key);
|
||||
g_free(normalized_value);
|
||||
database_transaction_rollback(database);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
g_hash_table_add(known_entities, entity_key);
|
||||
inserted_count++;
|
||||
g_hash_table_insert(
|
||||
known_entities, entity_key, g_strdup(target_identifier)
|
||||
);
|
||||
entity_key = NULL;
|
||||
inserted_entity_count++;
|
||||
entity_record_free(entity);
|
||||
g_clear_error(&model_error);
|
||||
g_free(description);
|
||||
g_free(identifier);
|
||||
}
|
||||
|
||||
relation_key = osint_dns_integration_build_relation_key(
|
||||
source_entity_identifier, target_identifier, relation_type
|
||||
);
|
||||
if (g_strcmp0(source_entity_identifier, target_identifier) == 0 ||
|
||||
g_hash_table_contains(known_relations, relation_key))
|
||||
{
|
||||
skipped_relation_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
relation_identifier = g_uuid_string_random();
|
||||
relation_label = g_strdup_printf(
|
||||
"%s — %s → %s",
|
||||
osint_dns_proposal_get_target(proposal), relation_type,
|
||||
normalized_value
|
||||
);
|
||||
relation_justification = g_strdup_printf(
|
||||
"Relation DNS %s obtenue avec dns.dig depuis %s. "
|
||||
"Résultat OSINT à vérifier.",
|
||||
osint_dns_proposal_get_record_type(proposal),
|
||||
osint_dns_proposal_get_target(proposal)
|
||||
);
|
||||
relation = relation_record_new(
|
||||
relation_identifier, source_entity_identifier,
|
||||
target_identifier, relation_type, relation_label,
|
||||
relation_justification, 50, timestamp, timestamp,
|
||||
RELATION_STATUS_ACTIVE, &model_error
|
||||
);
|
||||
if (relation == NULL ||
|
||||
!relation_dao_insert(relation_dao, relation, error))
|
||||
{
|
||||
if (error != NULL && *error == NULL && model_error != NULL)
|
||||
g_propagate_error(error, g_steal_pointer(&model_error));
|
||||
relation_record_free(relation);
|
||||
g_clear_error(&model_error);
|
||||
g_free(relation_identifier);
|
||||
g_free(relation_label);
|
||||
g_free(relation_justification);
|
||||
g_free(relation_key);
|
||||
g_free(target_identifier);
|
||||
g_free(entity_key);
|
||||
g_free(normalized_value);
|
||||
database_transaction_rollback(database);
|
||||
goto cleanup;
|
||||
}
|
||||
g_hash_table_add(known_relations, relation_key);
|
||||
relation_key = NULL;
|
||||
inserted_relation_count++;
|
||||
relation_record_free(relation);
|
||||
g_clear_error(&model_error);
|
||||
g_free(relation_identifier);
|
||||
g_free(relation_label);
|
||||
g_free(relation_justification);
|
||||
}
|
||||
|
||||
g_free(relation_key);
|
||||
g_free(target_identifier);
|
||||
g_free(entity_key);
|
||||
g_free(normalized_value);
|
||||
}
|
||||
|
||||
|
|
@ -215,14 +349,24 @@ gboolean osint_dns_integration_apply(
|
|||
goto cleanup;
|
||||
}
|
||||
success = TRUE;
|
||||
if (out_inserted_count != NULL) *out_inserted_count = inserted_count;
|
||||
if (out_skipped_count != NULL) *out_skipped_count = skipped_count;
|
||||
if (out_inserted_entity_count != NULL)
|
||||
*out_inserted_entity_count = inserted_entity_count;
|
||||
if (out_skipped_entity_count != NULL)
|
||||
*out_skipped_entity_count = skipped_entity_count;
|
||||
if (out_inserted_relation_count != NULL)
|
||||
*out_inserted_relation_count = inserted_relation_count;
|
||||
if (out_skipped_relation_count != NULL)
|
||||
*out_skipped_relation_count = skipped_relation_count;
|
||||
|
||||
cleanup:
|
||||
g_free(timestamp);
|
||||
g_clear_pointer(&now, g_date_time_unref);
|
||||
g_clear_pointer(&known_entities, g_hash_table_unref);
|
||||
g_clear_pointer(&known_relations, g_hash_table_unref);
|
||||
g_clear_pointer(&existing_entities, g_ptr_array_unref);
|
||||
g_clear_pointer(&existing_relations, g_ptr_array_unref);
|
||||
entity_record_free(source_entity);
|
||||
relation_dao_free(relation_dao);
|
||||
entity_dao_free(entity_dao);
|
||||
return success;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,3 +210,19 @@ char *osint_dns_proposal_dup_normalized_value(
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *osint_dns_proposal_get_relation_type(
|
||||
const OsintDnsProposal *proposal
|
||||
)
|
||||
{
|
||||
const char *record_type = osint_dns_proposal_get_record_type(proposal);
|
||||
if (g_strcmp0(record_type, "A") == 0 ||
|
||||
g_strcmp0(record_type, "AAAA") == 0 ||
|
||||
g_strcmp0(record_type, "PTR") == 0)
|
||||
return "resolves_to";
|
||||
if (g_strcmp0(record_type, "CNAME") == 0)
|
||||
return "aliases_to";
|
||||
if (g_strcmp0(record_type, "NS") == 0)
|
||||
return "uses_name_server";
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ struct MainWindow
|
|||
*/
|
||||
static void main_window_on_osint_action_requested(
|
||||
const char *action_identifier,
|
||||
const char *target_identifier,
|
||||
const char *target_value,
|
||||
gpointer user_data
|
||||
)
|
||||
|
|
@ -149,6 +150,7 @@ static void main_window_on_osint_action_requested(
|
|||
|
||||
main_window->osint_action_callback(
|
||||
action_identifier,
|
||||
target_identifier,
|
||||
target_value,
|
||||
main_window->osint_action_user_data
|
||||
);
|
||||
|
|
|
|||
|
|
@ -142,18 +142,25 @@ static GtkWidget *osint_dns_review_dialog_create_row(
|
|||
{
|
||||
GtkWidget *row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
||||
const char *entity_type = osint_dns_proposal_get_entity_type(proposal);
|
||||
const char *relation_type = osint_dns_proposal_get_relation_type(proposal);
|
||||
char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal);
|
||||
const gboolean compatible = entity_type != NULL && normalized_value != NULL;
|
||||
const gboolean compatible = entity_type != NULL && relation_type != NULL &&
|
||||
normalized_value != NULL;
|
||||
char *title = g_strdup_printf(
|
||||
"%s → %s",
|
||||
osint_dns_proposal_get_record_type(proposal),
|
||||
osint_dns_proposal_get_value(proposal)
|
||||
);
|
||||
char *details = g_strdup_printf(
|
||||
"Cible : %s\nPropriétaire : %s\nType Labfy : %s",
|
||||
"Cible : %s\nPropriétaire : %s\nType Labfy : %s\n"
|
||||
"Relation prévue : %s — %s → %s",
|
||||
osint_dns_proposal_get_target(proposal),
|
||||
osint_dns_proposal_get_owner(proposal),
|
||||
compatible ? entity_type : "non pris en charge"
|
||||
compatible ? entity_type : "non pris en charge",
|
||||
osint_dns_proposal_get_target(proposal),
|
||||
compatible ? relation_type : "non prise en charge",
|
||||
normalized_value != NULL ? normalized_value
|
||||
: osint_dns_proposal_get_value(proposal)
|
||||
);
|
||||
GtkWidget *selection_button = gtk_check_button_new_with_label(title);
|
||||
GtkWidget *details_label = gtk_label_new(details);
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ static void workspace_on_osint_action_clicked(
|
|||
"osint-action-identifier"
|
||||
);
|
||||
const char *target_value = NULL;
|
||||
const char *target_identifier = NULL;
|
||||
|
||||
if (workspace == NULL || workspace->osint_tools_status_label == NULL)
|
||||
{
|
||||
|
|
@ -165,9 +166,13 @@ static void workspace_on_osint_action_clicked(
|
|||
target_value = osint_selection_context_get_value(
|
||||
workspace->osint_selection_context
|
||||
);
|
||||
target_identifier = osint_selection_context_get_identifier(
|
||||
workspace->osint_selection_context
|
||||
);
|
||||
|
||||
if (workspace->osint_action_callback == NULL ||
|
||||
action_identifier == NULL || target_value == NULL)
|
||||
action_identifier == NULL || target_identifier == NULL ||
|
||||
target_value == NULL)
|
||||
{
|
||||
gtk_label_set_text(
|
||||
GTK_LABEL(workspace->osint_tools_status_label),
|
||||
|
|
@ -187,6 +192,7 @@ static void workspace_on_osint_action_clicked(
|
|||
|
||||
workspace->osint_action_callback(
|
||||
action_identifier,
|
||||
target_identifier,
|
||||
target_value,
|
||||
workspace->osint_action_user_data
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
#include "core/osint_dns_integration.h"
|
||||
|
||||
#include "dao/entity_dao.h"
|
||||
#include "dao/relation_dao.h"
|
||||
#include "database/database.h"
|
||||
#include "models/osint_dns_proposal.h"
|
||||
#include "models/relation_record.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
|
@ -19,14 +21,26 @@ static void test_integration_and_duplicates(void)
|
|||
char *database_path = g_build_filename(directory, "Enquete.sqlite", NULL);
|
||||
Database *database = NULL;
|
||||
EntityDao *entity_dao = NULL;
|
||||
RelationDao *relation_dao = NULL;
|
||||
EntityRecord *source = NULL;
|
||||
GPtrArray *proposals = NULL;
|
||||
GPtrArray *entities = NULL;
|
||||
GPtrArray *relations = NULL;
|
||||
guint inserted = 0U;
|
||||
guint skipped = 0U;
|
||||
guint inserted_relations = 0U;
|
||||
guint skipped_relations = 0U;
|
||||
|
||||
g_assert_no_error(error);
|
||||
g_assert_true(database_initialize(database_path, "DNS", directory));
|
||||
database = database_open(database_path);
|
||||
entity_dao = entity_dao_new(database, &error);
|
||||
source = entity_record_new(
|
||||
"11111111-1111-4111-8111-111111111111", "domain_name",
|
||||
"example.org", NULL, NULL, 50, "2026-01-01T00:00:00Z",
|
||||
"2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error
|
||||
);
|
||||
g_assert_true(entity_dao_insert(entity_dao, source, &error));
|
||||
proposals = osint_dns_proposal_parse(
|
||||
"example.org",
|
||||
"example.org. 60 IN A 192.0.2.10\n"
|
||||
|
|
@ -34,22 +48,33 @@ static void test_integration_and_duplicates(void)
|
|||
"example.org. 60 IN MX 10 mail.example.org.\n"
|
||||
);
|
||||
g_assert_true(osint_dns_integration_apply(
|
||||
database, proposals, &inserted, &skipped, &error
|
||||
database, "11111111-1111-4111-8111-111111111111", proposals,
|
||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||
));
|
||||
g_assert_no_error(error);
|
||||
g_assert_cmpuint(inserted, ==, 2);
|
||||
g_assert_cmpuint(skipped, ==, 1);
|
||||
g_assert_cmpuint(inserted_relations, ==, 2);
|
||||
g_assert_cmpuint(skipped_relations, ==, 1);
|
||||
|
||||
g_assert_true(osint_dns_integration_apply(
|
||||
database, proposals, &inserted, &skipped, &error
|
||||
database, "11111111-1111-4111-8111-111111111111", proposals,
|
||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||
));
|
||||
g_assert_cmpuint(inserted, ==, 0);
|
||||
g_assert_cmpuint(skipped, ==, 3);
|
||||
entity_dao = entity_dao_new(database, &error);
|
||||
g_assert_cmpuint(inserted_relations, ==, 0);
|
||||
g_assert_cmpuint(skipped_relations, ==, 3);
|
||||
entities = entity_dao_list_all(entity_dao, &error);
|
||||
g_assert_cmpuint(entities->len, ==, 2);
|
||||
g_assert_cmpuint(entities->len, ==, 3);
|
||||
relation_dao = relation_dao_new(database, &error);
|
||||
relations = relation_dao_list_all(relation_dao, &error);
|
||||
g_assert_cmpuint(relations->len, ==, 2);
|
||||
|
||||
g_ptr_array_unref(relations);
|
||||
relation_dao_free(relation_dao);
|
||||
g_ptr_array_unref(entities);
|
||||
entity_record_free(source);
|
||||
entity_dao_free(entity_dao);
|
||||
g_ptr_array_unref(proposals);
|
||||
database_close(database);
|
||||
|
|
@ -63,7 +88,7 @@ static void test_invalid_arguments(void)
|
|||
{
|
||||
GError *error = NULL;
|
||||
g_assert_false(osint_dns_integration_apply(
|
||||
NULL, NULL, NULL, NULL, &error
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error
|
||||
));
|
||||
g_assert_error(
|
||||
error, OSINT_DNS_INTEGRATION_ERROR,
|
||||
|
|
@ -83,6 +108,8 @@ static void test_normalized_existing_domain_is_skipped(void)
|
|||
GPtrArray *proposals = NULL;
|
||||
guint inserted = 0U;
|
||||
guint skipped = 0U;
|
||||
guint inserted_relations = 0U;
|
||||
guint skipped_relations = 0U;
|
||||
|
||||
g_assert_true(database_initialize(database_path, "DNS", directory));
|
||||
database = database_open(database_path);
|
||||
|
|
@ -97,10 +124,13 @@ static void test_normalized_existing_domain_is_skipped(void)
|
|||
"example.org", "example.org. 60 IN CNAME www.example.org.\n"
|
||||
);
|
||||
g_assert_true(osint_dns_integration_apply(
|
||||
database, proposals, &inserted, &skipped, &error
|
||||
database, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", proposals,
|
||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||
));
|
||||
g_assert_cmpuint(inserted, ==, 0);
|
||||
g_assert_cmpuint(skipped, ==, 1);
|
||||
g_assert_cmpuint(inserted_relations, ==, 0);
|
||||
g_assert_cmpuint(skipped_relations, ==, 1);
|
||||
|
||||
g_ptr_array_unref(proposals);
|
||||
entity_record_free(existing);
|
||||
|
|
|
|||
|
|
@ -67,10 +67,17 @@ static void test_entity_mapping_and_normalization(void)
|
|||
|
||||
g_assert_cmpstr(osint_dns_proposal_get_entity_type(ipv4), ==, "ip_address");
|
||||
g_assert_cmpstr(ipv4_value, ==, "192.0.2.10");
|
||||
g_assert_cmpstr(
|
||||
osint_dns_proposal_get_relation_type(ipv4), ==, "resolves_to"
|
||||
);
|
||||
g_assert_cmpstr(ipv6_value, ==, "2001:db8::1");
|
||||
g_assert_cmpstr(osint_dns_proposal_get_entity_type(domain), ==, "domain_name");
|
||||
g_assert_cmpstr(domain_value, ==, "www.example.org");
|
||||
g_assert_cmpstr(
|
||||
osint_dns_proposal_get_relation_type(domain), ==, "aliases_to"
|
||||
);
|
||||
g_assert_null(osint_dns_proposal_get_entity_type(unsupported));
|
||||
g_assert_null(osint_dns_proposal_get_relation_type(unsupported));
|
||||
g_assert_null(osint_dns_proposal_dup_normalized_value(unsupported));
|
||||
|
||||
g_free(ipv4_value);
|
||||
|
|
|
|||
Loading…
Reference in a new issue