feat(osint): create DNS result relations

This commit is contained in:
grayTerminal-sh 2026-07-22 12:29:17 +02:00
parent f6f3e71002
commit 16a5d81e95
15 changed files with 340 additions and 75 deletions

View file

@ -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 \

View file

@ -149,7 +149,9 @@ Le socle actuel comprend notamment :
- affichage sélectionnable des sorties standard et derreur, 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 :

View file

@ -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

View file

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

View file

@ -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

View file

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

View file

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

View file

@ -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;
}

View file

@ -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,60 +209,131 @@ 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();
description = g_strdup_printf(
"Proposition 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)
);
entity = entity_record_new(
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))
else
{
if (error != NULL && *error == NULL && model_error != NULL)
g_propagate_error(error, g_steal_pointer(&model_error));
target_identifier = g_uuid_string_random();
description = g_strdup_printf(
"Proposition 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)
);
entity = entity_record_new(
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))
{
if (error != NULL && *error == NULL && model_error != NULL)
g_propagate_error(error, g_steal_pointer(&model_error));
entity_record_free(entity);
g_clear_error(&model_error);
g_free(description);
g_free(target_identifier);
g_free(entity_key);
g_free(normalized_value);
database_transaction_rollback(database);
goto cleanup;
}
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);
g_free(entity_key);
g_free(normalized_value);
database_transaction_rollback(database);
goto cleanup;
}
g_hash_table_add(known_entities, entity_key);
inserted_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;
}

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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