feat(osint): integrate selected DNS proposals
This commit is contained in:
parent
5a196f6ac6
commit
f6f3e71002
12 changed files with 975 additions and 51 deletions
25
Makefile
25
Makefile
|
|
@ -117,6 +117,7 @@ TEST_OSINT_SELECTION_CONTEXT := tests/test_osint_selection_context
|
||||||
TEST_OSINT_ACTION_CATALOG := tests/test_osint_action_catalog
|
TEST_OSINT_ACTION_CATALOG := tests/test_osint_action_catalog
|
||||||
TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query
|
TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query
|
||||||
TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
||||||
|
TEST_OSINT_DNS_INTEGRATION := tests/test_osint_dns_integration
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -554,9 +555,24 @@ $(TEST_OSINT_DNS_QUERY): \
|
||||||
|
|
||||||
$(TEST_OSINT_DNS_PROPOSAL): \
|
$(TEST_OSINT_DNS_PROPOSAL): \
|
||||||
tests/test_osint_dns_proposal.c \
|
tests/test_osint_dns_proposal.c \
|
||||||
src/models/osint_dns_proposal.c
|
src/models/osint_dns_proposal.c \
|
||||||
|
src/models/osint_dns_query.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
$(TEST_OSINT_DNS_INTEGRATION): \
|
||||||
|
tests/test_osint_dns_integration.c \
|
||||||
|
src/core/osint_dns_integration.c \
|
||||||
|
src/models/osint_dns_proposal.c \
|
||||||
|
src/models/osint_dns_query.c \
|
||||||
|
src/models/entity_record.c \
|
||||||
|
src/dao/entity_dao.c \
|
||||||
|
src/database/database.c \
|
||||||
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
|
||||||
tests/test_investigation_graph_load_task.c \
|
tests/test_investigation_graph_load_task.c \
|
||||||
src/core/investigation_graph_load_task.c \
|
src/core/investigation_graph_load_task.c \
|
||||||
|
|
@ -628,7 +644,8 @@ test: \
|
||||||
$(TEST_OSINT_SELECTION_CONTEXT) \
|
$(TEST_OSINT_SELECTION_CONTEXT) \
|
||||||
$(TEST_OSINT_ACTION_CATALOG) \
|
$(TEST_OSINT_ACTION_CATALOG) \
|
||||||
$(TEST_OSINT_DNS_QUERY) \
|
$(TEST_OSINT_DNS_QUERY) \
|
||||||
$(TEST_OSINT_DNS_PROPOSAL)
|
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||||
|
$(TEST_OSINT_DNS_INTEGRATION)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -680,6 +697,7 @@ test: \
|
||||||
@$(TEST_OSINT_ACTION_CATALOG)
|
@$(TEST_OSINT_ACTION_CATALOG)
|
||||||
@$(TEST_OSINT_DNS_QUERY)
|
@$(TEST_OSINT_DNS_QUERY)
|
||||||
@$(TEST_OSINT_DNS_PROPOSAL)
|
@$(TEST_OSINT_DNS_PROPOSAL)
|
||||||
|
@$(TEST_OSINT_DNS_INTEGRATION)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -737,7 +755,8 @@ clean:
|
||||||
$(TEST_OSINT_SELECTION_CONTEXT) \
|
$(TEST_OSINT_SELECTION_CONTEXT) \
|
||||||
$(TEST_OSINT_ACTION_CATALOG) \
|
$(TEST_OSINT_ACTION_CATALOG) \
|
||||||
$(TEST_OSINT_DNS_QUERY) \
|
$(TEST_OSINT_DNS_QUERY) \
|
||||||
$(TEST_OSINT_DNS_PROPOSAL)
|
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||||
|
$(TEST_OSINT_DNS_INTEGRATION)
|
||||||
|
|
||||||
-include $(DEP)
|
-include $(DEP)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,9 @@ Le socle actuel comprend notamment :
|
||||||
- disponibilité des actions OSINT synchronisée avec le registre d’outils ;
|
- disponibilité des actions OSINT synchronisée avec le registre d’outils ;
|
||||||
- résolution DNS asynchrone avec `dig` depuis une entité domaine ;
|
- résolution DNS asynchrone avec `dig` depuis une entité domaine ;
|
||||||
- affichage sélectionnable des sorties standard et d’erreur, sans persistance ;
|
- affichage sélectionnable des sorties standard et d’erreur, sans persistance ;
|
||||||
- révision des réponses DNS sous forme de propositions avant intégration.
|
- 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.
|
||||||
|
|
||||||
Les outils actuellement présents dans le catalogue initial sont :
|
Les outils actuellement présents dans le catalogue initial sont :
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1106,6 +1106,27 @@ En revanche, la suppression ou la modification d'un type existant doit être
|
||||||
réalisée avec précaution afin de préserver la cohérence des données déjà
|
réalisée avec précaution afin de préserver la cohérence des données déjà
|
||||||
enregistrées.
|
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.
|
||||||
|
|
||||||
|
Cette information ne remplace pas une provenance structurée. Une migration
|
||||||
|
future devra introduire un objet de résultat OSINT capable de conserver au
|
||||||
|
minimum :
|
||||||
|
|
||||||
|
- l'outil et sa version ;
|
||||||
|
- la cible et les arguments de l'exécution ;
|
||||||
|
- la date d'exécution ;
|
||||||
|
- la sortie brute et son empreinte ;
|
||||||
|
- les propositions extraites ;
|
||||||
|
- les entités effectivement créées ou réutilisées.
|
||||||
|
|
||||||
|
Jusqu'à cette migration, l'intégration ne doit pas présenter les entités DNS
|
||||||
|
comme des faits vérifiés et ne doit pas supprimer la sortie brute affichée à
|
||||||
|
l'utilisateur.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 6. Tables de liaison
|
# 6. Tables de liaison
|
||||||
|
|
|
||||||
59
include/core/osint_dns_integration.h
Normal file
59
include/core/osint_dns_integration.h
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_integration.h
|
||||||
|
* @brief Intégration transactionnelle de propositions DNS validées.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_DNS_INTEGRATION_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_DNS_INTEGRATION_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/** @brief Codes d'erreur de l'intégration DNS. */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
OSINT_DNS_INTEGRATION_ERROR_INVALID_ARGUMENT,
|
||||||
|
OSINT_DNS_INTEGRATION_ERROR_DATABASE,
|
||||||
|
OSINT_DNS_INTEGRATION_ERROR_MODEL,
|
||||||
|
OSINT_DNS_INTEGRATION_ERROR_INSERT
|
||||||
|
} OsintDnsIntegrationError;
|
||||||
|
|
||||||
|
/** @brief Domaine d'erreur de l'intégration DNS. */
|
||||||
|
#define OSINT_DNS_INTEGRATION_ERROR osint_dns_integration_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur de l'intégration DNS.
|
||||||
|
*
|
||||||
|
* @return Quark GLib du domaine d'erreur.
|
||||||
|
*/
|
||||||
|
GQuark osint_dns_integration_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Intègre une sélection de propositions dans une transaction unique.
|
||||||
|
*
|
||||||
|
* 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 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 error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE lorsque la transaction est validée, sinon FALSE.
|
||||||
|
*/
|
||||||
|
gboolean osint_dns_integration_apply(
|
||||||
|
Database *database,
|
||||||
|
GPtrArray *selected_proposals,
|
||||||
|
guint *out_inserted_count,
|
||||||
|
guint *out_skipped_count,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -75,6 +75,35 @@ const char *osint_dns_proposal_get_target(
|
||||||
const OsintDnsProposal *proposal
|
const OsintDnsProposal *proposal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le type d'entité compatible avec la proposition.
|
||||||
|
*
|
||||||
|
* Les types actuellement pris en charge sont `ip_address` pour A et AAAA,
|
||||||
|
* ainsi que `domain_name` pour CNAME, NS et PTR.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Identifiant statique du type d'entité, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *osint_dns_proposal_get_entity_type(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Normalise la valeur avant recherche ou insertion.
|
||||||
|
*
|
||||||
|
* Les adresses IP sont converties dans leur représentation canonique. Les
|
||||||
|
* noms de domaine sont passés en minuscules et leur point final est retiré.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Nouvelle chaîne à libérer avec g_free(), ou NULL si la proposition
|
||||||
|
* n'est pas compatible ou si sa valeur est invalide.
|
||||||
|
*/
|
||||||
|
char *osint_dns_proposal_dup_normalized_value(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
47
include/views/osint_dns_review_dialog.h
Normal file
47
include/views/osint_dns_review_dialog.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_review_dialog.h
|
||||||
|
* @brief Dialogue de sélection des propositions issues d'une réponse DNS.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_DNS_REVIEW_DIALOG_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_DNS_REVIEW_DIALOG_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé après confirmation de la sélection DNS.
|
||||||
|
*
|
||||||
|
* Le tableau et les propositions sont empruntés et uniquement valides pendant
|
||||||
|
* l'appel. Le tableau contient seulement les propositions compatibles cochées.
|
||||||
|
*
|
||||||
|
* @param selected_proposals Propositions sélectionnées.
|
||||||
|
* @param user_data Données empruntées fournies par l'appelant.
|
||||||
|
*/
|
||||||
|
typedef void (*OsintDnsReviewDialogConfirmCallback)(
|
||||||
|
GPtrArray *selected_proposals,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche le dialogue de sélection avant intégration.
|
||||||
|
*
|
||||||
|
* Le dialogue conserve une référence sur proposals jusqu'à sa fermeture.
|
||||||
|
* Fermer ou annuler ne déclenche pas le callback.
|
||||||
|
*
|
||||||
|
* @param parent_window Fenêtre parente, ou NULL.
|
||||||
|
* @param proposals Tableau de OsintDnsProposal à réviser.
|
||||||
|
* @param callback Callback de confirmation facultatif.
|
||||||
|
* @param user_data Données empruntées transmises au callback.
|
||||||
|
*/
|
||||||
|
void osint_dns_review_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
GPtrArray *proposals,
|
||||||
|
OsintDnsReviewDialogConfirmCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include "core/background_task.h"
|
#include "core/background_task.h"
|
||||||
#include "core/tool_initializer.h"
|
#include "core/tool_initializer.h"
|
||||||
#include "core/tool_task.h"
|
#include "core/tool_task.h"
|
||||||
|
#include "core/osint_dns_integration.h"
|
||||||
#include "views/file_dialog.h"
|
#include "views/file_dialog.h"
|
||||||
#include "core/evidence_import_task.h"
|
#include "core/evidence_import_task.h"
|
||||||
#include "models/evidence_record.h"
|
#include "models/evidence_record.h"
|
||||||
|
|
@ -31,6 +32,7 @@
|
||||||
#include "dao/graph_node_position_dao.h"
|
#include "dao/graph_node_position_dao.h"
|
||||||
#include "views/evidence_import_dialog.h"
|
#include "views/evidence_import_dialog.h"
|
||||||
#include "views/create_relation_dialog.h"
|
#include "views/create_relation_dialog.h"
|
||||||
|
#include "views/osint_dns_review_dialog.h"
|
||||||
#include "dao/evidence_dao.h"
|
#include "dao/evidence_dao.h"
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
#include "models/evidence_type.h"
|
#include "models/evidence_type.h"
|
||||||
|
|
@ -167,70 +169,99 @@ typedef struct
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
Application *application;
|
Application *application;
|
||||||
char *proposal_summary;
|
GPtrArray *proposals;
|
||||||
} ApplicationOsintReviewContext;
|
} ApplicationOsintReviewContext;
|
||||||
|
|
||||||
|
static void application_start_graph_loading(
|
||||||
|
Application *application,
|
||||||
|
const char *database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
static void application_present_error(
|
||||||
|
Application *application,
|
||||||
|
const char *title,
|
||||||
|
const char *message
|
||||||
|
);
|
||||||
|
|
||||||
/** @brief Libère les données de révision possédées par le dialogue. */
|
/** @brief Libère les données de révision possédées par le dialogue. */
|
||||||
static void application_osint_review_context_free(gpointer user_data)
|
static void application_osint_review_context_free(gpointer user_data)
|
||||||
{
|
{
|
||||||
ApplicationOsintReviewContext *context = user_data;
|
ApplicationOsintReviewContext *context = user_data;
|
||||||
if (context == NULL) return;
|
if (context == NULL) return;
|
||||||
g_free(context->proposal_summary);
|
g_clear_pointer(&context->proposals, g_ptr_array_unref);
|
||||||
g_free(context);
|
g_free(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Affiche les propositions sans les intégrer à l'enquête. */
|
/** @brief Intègre les propositions confirmées puis recharge le graphe. */
|
||||||
static void application_on_osint_prepare_integration(gpointer user_data)
|
static void application_on_osint_integration_confirmed(
|
||||||
|
GPtrArray *selected_proposals,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
{
|
{
|
||||||
ApplicationOsintReviewContext *context = user_data;
|
Application *application = user_data;
|
||||||
if (context == NULL || context->application == NULL ||
|
const InvestigationProject *project = NULL;
|
||||||
context->application->main_window == NULL)
|
Database *database = NULL;
|
||||||
|
const char *database_path = NULL;
|
||||||
|
guint inserted_count = 0U;
|
||||||
|
guint skipped_count = 0U;
|
||||||
|
char *message = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (application == NULL || application->session == NULL ||
|
||||||
|
application->main_window == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
application_message_dialog_present_details(
|
database = investigation_session_get_database(application->session);
|
||||||
main_window_get_window(context->application->main_window),
|
project = investigation_session_get_project(application->session);
|
||||||
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
database_path = project != NULL
|
||||||
"Propositions DNS à réviser",
|
? investigation_project_get_database_path(project) : NULL;
|
||||||
"Aucune donnée n'a été enregistrée dans l'enquête.",
|
if (!osint_dns_integration_apply(
|
||||||
context->proposal_summary
|
database, selected_proposals, &inserted_count, &skipped_count,
|
||||||
);
|
&error
|
||||||
}
|
))
|
||||||
|
|
||||||
/** @brief Construit le résumé lisible des propositions DNS. */
|
|
||||||
static char *application_build_dns_proposal_summary(GPtrArray *proposals)
|
|
||||||
{
|
|
||||||
GString *summary = g_string_new(NULL);
|
|
||||||
|
|
||||||
if (summary == NULL) return NULL;
|
|
||||||
g_string_append_printf(
|
|
||||||
summary,
|
|
||||||
"Propositions détectées : %u\n\n",
|
|
||||||
proposals != NULL ? proposals->len : 0U
|
|
||||||
);
|
|
||||||
|
|
||||||
for (guint index = 0; proposals != NULL && index < proposals->len; index++)
|
|
||||||
{
|
{
|
||||||
const OsintDnsProposal *proposal = g_ptr_array_index(proposals, index);
|
application_present_error(
|
||||||
g_string_append_printf(
|
application,
|
||||||
summary,
|
"Intégration DNS impossible",
|
||||||
"%u. Cible : %s\n Propriétaire : %s\n Type : %s\n"
|
error != NULL ? error->message
|
||||||
" Valeur : %s\n\n",
|
: "La transaction DNS a échoué."
|
||||||
index + 1U,
|
|
||||||
osint_dns_proposal_get_target(proposal),
|
|
||||||
osint_dns_proposal_get_owner(proposal),
|
|
||||||
osint_dns_proposal_get_record_type(proposal),
|
|
||||||
osint_dns_proposal_get_value(proposal)
|
|
||||||
);
|
);
|
||||||
|
g_clear_error(&error);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_string_append(
|
message = g_strdup_printf(
|
||||||
summary,
|
"%u entité(s) créée(s), %u proposition(s) ignorée(s).\n\n"
|
||||||
"Ces propositions sont uniquement préparées pour révision. "
|
"La provenance complète devra être ajoutée par une future migration "
|
||||||
"Elles ne sont pas enregistrées dans SQLite."
|
"du schéma OSINT.",
|
||||||
|
inserted_count, skipped_count
|
||||||
|
);
|
||||||
|
application_message_dialog_present(
|
||||||
|
main_window_get_window(application->main_window),
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||||
|
"Intégration DNS terminée",
|
||||||
|
message
|
||||||
|
);
|
||||||
|
g_free(message);
|
||||||
|
if (database_path != NULL && database_path[0] != '\0')
|
||||||
|
application_start_graph_loading(application, database_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Affiche les propositions et demande une sélection explicite. */
|
||||||
|
static void application_on_osint_prepare_integration(gpointer user_data)
|
||||||
|
{
|
||||||
|
ApplicationOsintReviewContext *context = user_data;
|
||||||
|
if (context == NULL || context->application == NULL ||
|
||||||
|
context->application->main_window == NULL || context->proposals == NULL)
|
||||||
|
return;
|
||||||
|
osint_dns_review_dialog_present(
|
||||||
|
main_window_get_window(context->application->main_window),
|
||||||
|
context->proposals,
|
||||||
|
application_on_osint_integration_confirmed,
|
||||||
|
context->application
|
||||||
);
|
);
|
||||||
return g_string_free(summary, FALSE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1147,10 +1178,9 @@ static void application_on_dns_lookup_completed(
|
||||||
if (review_context != NULL)
|
if (review_context != NULL)
|
||||||
{
|
{
|
||||||
review_context->application = application;
|
review_context->application = application;
|
||||||
review_context->proposal_summary =
|
review_context->proposals = g_ptr_array_ref(proposals);
|
||||||
application_build_dns_proposal_summary(proposals);
|
|
||||||
}
|
}
|
||||||
if (review_context == NULL || review_context->proposal_summary == NULL)
|
if (review_context == NULL || review_context->proposals == NULL)
|
||||||
{
|
{
|
||||||
application_osint_review_context_free(review_context);
|
application_osint_review_context_free(review_context);
|
||||||
review_context = NULL;
|
review_context = NULL;
|
||||||
|
|
|
||||||
228
src/core/osint_dns_integration.c
Normal file
228
src/core/osint_dns_integration.c
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_integration.c
|
||||||
|
* @brief Intégration transactionnelle de propositions DNS validées.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "core/osint_dns_integration.h"
|
||||||
|
|
||||||
|
#include "dao/entity_dao.h"
|
||||||
|
#include "database/error.h"
|
||||||
|
#include "database/transaction.h"
|
||||||
|
#include "models/entity_record.h"
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/** @brief Normalise une valeur déjà persistée pour la comparaison. */
|
||||||
|
static char *osint_dns_integration_normalize_existing_value(
|
||||||
|
const char *entity_type,
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (g_strcmp0(entity_type, "ip_address") == 0)
|
||||||
|
{
|
||||||
|
GInetAddress *address = g_inet_address_new_from_string(value);
|
||||||
|
char *normalized_value = NULL;
|
||||||
|
if (address == NULL) return g_strdup(value);
|
||||||
|
normalized_value = g_inet_address_to_string(address);
|
||||||
|
g_object_unref(address);
|
||||||
|
return normalized_value;
|
||||||
|
}
|
||||||
|
if (g_strcmp0(entity_type, "domain_name") == 0)
|
||||||
|
{
|
||||||
|
char *normalized_value = g_ascii_strdown(value, -1);
|
||||||
|
gsize value_length = normalized_value != NULL
|
||||||
|
? strlen(normalized_value) : 0U;
|
||||||
|
if (value_length > 0U && normalized_value[value_length - 1U] == '.')
|
||||||
|
normalized_value[value_length - 1U] = '\0';
|
||||||
|
return normalized_value;
|
||||||
|
}
|
||||||
|
return g_strdup(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Construit la clé anti-doublon d'une entité. */
|
||||||
|
static char *osint_dns_integration_build_key(
|
||||||
|
const char *entity_type,
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return g_strdup_printf("%s\n%s", entity_type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Renseigne une erreur littérale si possible. */
|
||||||
|
static void osint_dns_integration_set_error(
|
||||||
|
GError **error,
|
||||||
|
OsintDnsIntegrationError code,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error != NULL && *error == NULL)
|
||||||
|
g_set_error_literal(error, OSINT_DNS_INTEGRATION_ERROR, code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark osint_dns_integration_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("osint-dns-integration-error-quark");
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean osint_dns_integration_apply(
|
||||||
|
Database *database,
|
||||||
|
GPtrArray *selected_proposals,
|
||||||
|
guint *out_inserted_count,
|
||||||
|
guint *out_skipped_count,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
EntityDao *entity_dao = NULL;
|
||||||
|
GPtrArray *existing_entities = NULL;
|
||||||
|
GHashTable *known_entities = NULL;
|
||||||
|
GDateTime *now = NULL;
|
||||||
|
char *timestamp = NULL;
|
||||||
|
guint inserted_count = 0U;
|
||||||
|
guint skipped_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 ||
|
||||||
|
selected_proposals->len == 0U)
|
||||||
|
{
|
||||||
|
osint_dns_integration_set_error(
|
||||||
|
error, OSINT_DNS_INTEGRATION_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La sélection DNS à intégrer est invalide."
|
||||||
|
);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_dao = entity_dao_new(database, error);
|
||||||
|
if (entity_dao == NULL) return FALSE;
|
||||||
|
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);
|
||||||
|
for (guint index = 0; index < existing_entities->len; index++)
|
||||||
|
{
|
||||||
|
const EntityRecord *entity = g_ptr_array_index(existing_entities, index);
|
||||||
|
const char *entity_type = entity_record_get_type_identifier(entity);
|
||||||
|
char *normalized_value = osint_dns_integration_normalize_existing_value(
|
||||||
|
entity_type, entity_record_get_value(entity)
|
||||||
|
);
|
||||||
|
g_hash_table_add(
|
||||||
|
known_entities,
|
||||||
|
osint_dns_integration_build_key(
|
||||||
|
entity_type,
|
||||||
|
normalized_value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
g_free(normalized_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
now = g_date_time_new_now_utc();
|
||||||
|
timestamp = now != NULL
|
||||||
|
? g_date_time_format(now, "%Y-%m-%dT%H:%M:%SZ") : NULL;
|
||||||
|
if (timestamp == NULL)
|
||||||
|
{
|
||||||
|
osint_dns_integration_set_error(
|
||||||
|
error, OSINT_DNS_INTEGRATION_ERROR_MODEL,
|
||||||
|
"Impossible de dater les entités DNS."
|
||||||
|
);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
if (!database_transaction_begin(database))
|
||||||
|
{
|
||||||
|
osint_dns_integration_set_error(
|
||||||
|
error, OSINT_DNS_INTEGRATION_ERROR_DATABASE,
|
||||||
|
database_error_get_message(database) != NULL
|
||||||
|
? database_error_get_message(database)
|
||||||
|
: "Impossible de démarrer la transaction DNS."
|
||||||
|
);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (guint index = 0; index < selected_proposals->len; index++)
|
||||||
|
{
|
||||||
|
const OsintDnsProposal *proposal = g_ptr_array_index(
|
||||||
|
selected_proposals, index
|
||||||
|
);
|
||||||
|
const char *entity_type = osint_dns_proposal_get_entity_type(proposal);
|
||||||
|
char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal);
|
||||||
|
char *entity_key = NULL;
|
||||||
|
char *identifier = NULL;
|
||||||
|
char *description = NULL;
|
||||||
|
EntityRecord *entity = NULL;
|
||||||
|
GError *model_error = NULL;
|
||||||
|
|
||||||
|
if (entity_type == NULL || normalized_value == NULL)
|
||||||
|
{
|
||||||
|
skipped_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))
|
||||||
|
{
|
||||||
|
skipped_count++;
|
||||||
|
g_free(entity_key);
|
||||||
|
g_free(normalized_value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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(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);
|
||||||
|
g_free(normalized_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_commit(database))
|
||||||
|
{
|
||||||
|
osint_dns_integration_set_error(
|
||||||
|
error, OSINT_DNS_INTEGRATION_ERROR_DATABASE,
|
||||||
|
database_error_get_message(database) != NULL
|
||||||
|
? database_error_get_message(database)
|
||||||
|
: "Impossible de valider la transaction DNS."
|
||||||
|
);
|
||||||
|
database_transaction_rollback(database);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
success = TRUE;
|
||||||
|
if (out_inserted_count != NULL) *out_inserted_count = inserted_count;
|
||||||
|
if (out_skipped_count != NULL) *out_skipped_count = skipped_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(&existing_entities, g_ptr_array_unref);
|
||||||
|
entity_dao_free(entity_dao);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "models/osint_dns_proposal.h"
|
#include "models/osint_dns_proposal.h"
|
||||||
|
#include "models/osint_dns_query.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
struct OsintDnsProposal
|
struct OsintDnsProposal
|
||||||
|
|
@ -155,3 +157,56 @@ const char *osint_dns_proposal_get_value(const OsintDnsProposal *proposal)
|
||||||
{ return proposal != NULL ? proposal->value : NULL; }
|
{ return proposal != NULL ? proposal->value : NULL; }
|
||||||
const char *osint_dns_proposal_get_target(const OsintDnsProposal *proposal)
|
const char *osint_dns_proposal_get_target(const OsintDnsProposal *proposal)
|
||||||
{ return proposal != NULL ? proposal->target : NULL; }
|
{ return proposal != NULL ? proposal->target : NULL; }
|
||||||
|
|
||||||
|
const char *osint_dns_proposal_get_entity_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)
|
||||||
|
{
|
||||||
|
return "ip_address";
|
||||||
|
}
|
||||||
|
if (g_strcmp0(record_type, "CNAME") == 0 ||
|
||||||
|
g_strcmp0(record_type, "NS") == 0 ||
|
||||||
|
g_strcmp0(record_type, "PTR") == 0)
|
||||||
|
{
|
||||||
|
return "domain_name";
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *osint_dns_proposal_dup_normalized_value(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *entity_type = osint_dns_proposal_get_entity_type(proposal);
|
||||||
|
const char *value = osint_dns_proposal_get_value(proposal);
|
||||||
|
|
||||||
|
if (entity_type == NULL || value == NULL) return NULL;
|
||||||
|
if (g_strcmp0(entity_type, "ip_address") == 0)
|
||||||
|
{
|
||||||
|
GInetAddress *address = g_inet_address_new_from_string(value);
|
||||||
|
char *normalized_value = NULL;
|
||||||
|
if (address == NULL) return NULL;
|
||||||
|
normalized_value = g_inet_address_to_string(address);
|
||||||
|
g_object_unref(address);
|
||||||
|
return normalized_value;
|
||||||
|
}
|
||||||
|
if (g_strcmp0(entity_type, "domain_name") == 0)
|
||||||
|
{
|
||||||
|
char *normalized_value = g_ascii_strdown(value, -1);
|
||||||
|
gsize value_length = normalized_value != NULL
|
||||||
|
? strlen(normalized_value) : 0U;
|
||||||
|
if (value_length > 0U && normalized_value[value_length - 1U] == '.')
|
||||||
|
normalized_value[value_length - 1U] = '\0';
|
||||||
|
if (!osint_dns_query_is_valid_target(normalized_value))
|
||||||
|
{
|
||||||
|
g_free(normalized_value);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return normalized_value;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
|
||||||
276
src/views/osint_dns_review_dialog.c
Normal file
276
src/views/osint_dns_review_dialog.c
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_review_dialog.c
|
||||||
|
* @brief Dialogue de sélection des propositions issues d'une réponse DNS.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "views/osint_dns_review_dialog.h"
|
||||||
|
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GtkWindow *window;
|
||||||
|
GtkWidget *select_all_button;
|
||||||
|
GtkWidget *confirm_button;
|
||||||
|
GPtrArray *proposals;
|
||||||
|
GPtrArray *selection_buttons;
|
||||||
|
OsintDnsReviewDialogConfirmCallback callback;
|
||||||
|
gpointer user_data;
|
||||||
|
} OsintDnsReviewDialogContext;
|
||||||
|
|
||||||
|
/** @brief Libère le contexte possédé par la fenêtre. */
|
||||||
|
static void osint_dns_review_dialog_context_free(gpointer data)
|
||||||
|
{
|
||||||
|
OsintDnsReviewDialogContext *context = data;
|
||||||
|
if (context == NULL) return;
|
||||||
|
g_clear_pointer(&context->selection_buttons, g_ptr_array_unref);
|
||||||
|
g_clear_pointer(&context->proposals, g_ptr_array_unref);
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Ferme le dialogue sans intégrer de proposition. */
|
||||||
|
static void osint_dns_review_dialog_on_cancel_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintDnsReviewDialogContext *context = user_data;
|
||||||
|
(void) button;
|
||||||
|
if (context != NULL) gtk_window_destroy(context->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Actualise l'état du bouton de confirmation. */
|
||||||
|
static void osint_dns_review_dialog_update_confirmation(
|
||||||
|
OsintDnsReviewDialogContext *context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
gboolean has_selection = FALSE;
|
||||||
|
for (guint index = 0;
|
||||||
|
context != NULL && index < context->selection_buttons->len;
|
||||||
|
index++)
|
||||||
|
{
|
||||||
|
GtkCheckButton *button = g_ptr_array_index(
|
||||||
|
context->selection_buttons, index
|
||||||
|
);
|
||||||
|
if (gtk_widget_get_sensitive(GTK_WIDGET(button)) &&
|
||||||
|
gtk_check_button_get_active(button))
|
||||||
|
{
|
||||||
|
has_selection = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context != NULL)
|
||||||
|
gtk_widget_set_sensitive(context->confirm_button, has_selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Réagit à la sélection individuelle d'une proposition. */
|
||||||
|
static void osint_dns_review_dialog_on_proposal_toggled(
|
||||||
|
GtkCheckButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
(void) button;
|
||||||
|
osint_dns_review_dialog_update_confirmation(user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Sélectionne ou désélectionne toutes les propositions compatibles. */
|
||||||
|
static void osint_dns_review_dialog_on_select_all_toggled(
|
||||||
|
GtkCheckButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintDnsReviewDialogContext *context = user_data;
|
||||||
|
const gboolean selected = gtk_check_button_get_active(button);
|
||||||
|
for (guint index = 0;
|
||||||
|
context != NULL && index < context->selection_buttons->len;
|
||||||
|
index++)
|
||||||
|
{
|
||||||
|
GtkWidget *selection_button = g_ptr_array_index(
|
||||||
|
context->selection_buttons, index
|
||||||
|
);
|
||||||
|
if (gtk_widget_get_sensitive(selection_button))
|
||||||
|
gtk_check_button_set_active(
|
||||||
|
GTK_CHECK_BUTTON(selection_button), selected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
osint_dns_review_dialog_update_confirmation(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Transmet les propositions cochées puis ferme le dialogue. */
|
||||||
|
static void osint_dns_review_dialog_on_confirm_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintDnsReviewDialogContext *context = user_data;
|
||||||
|
GPtrArray *selected_proposals = g_ptr_array_new();
|
||||||
|
(void) button;
|
||||||
|
|
||||||
|
for (guint index = 0;
|
||||||
|
context != NULL && index < context->selection_buttons->len;
|
||||||
|
index++)
|
||||||
|
{
|
||||||
|
GtkCheckButton *selection_button = g_ptr_array_index(
|
||||||
|
context->selection_buttons, index
|
||||||
|
);
|
||||||
|
if (gtk_widget_get_sensitive(GTK_WIDGET(selection_button)) &&
|
||||||
|
gtk_check_button_get_active(selection_button))
|
||||||
|
{
|
||||||
|
g_ptr_array_add(
|
||||||
|
selected_proposals,
|
||||||
|
g_object_get_data(
|
||||||
|
G_OBJECT(selection_button), "osint-dns-proposal"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context != NULL && context->callback != NULL &&
|
||||||
|
selected_proposals->len > 0U)
|
||||||
|
{
|
||||||
|
context->callback(selected_proposals, context->user_data);
|
||||||
|
}
|
||||||
|
g_ptr_array_unref(selected_proposals);
|
||||||
|
if (context != NULL) gtk_window_destroy(context->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Crée une ligne décrivant une proposition DNS. */
|
||||||
|
static GtkWidget *osint_dns_review_dialog_create_row(
|
||||||
|
OsintDnsReviewDialogContext *context,
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GtkWidget *row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
||||||
|
const char *entity_type = osint_dns_proposal_get_entity_type(proposal);
|
||||||
|
char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal);
|
||||||
|
const gboolean compatible = entity_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",
|
||||||
|
osint_dns_proposal_get_target(proposal),
|
||||||
|
osint_dns_proposal_get_owner(proposal),
|
||||||
|
compatible ? entity_type : "non pris en charge"
|
||||||
|
);
|
||||||
|
GtkWidget *selection_button = gtk_check_button_new_with_label(title);
|
||||||
|
GtkWidget *details_label = gtk_label_new(details);
|
||||||
|
|
||||||
|
gtk_widget_set_sensitive(selection_button, compatible);
|
||||||
|
gtk_widget_set_margin_start(details_label, 28);
|
||||||
|
gtk_widget_set_halign(details_label, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(details_label), 0.0F);
|
||||||
|
gtk_label_set_selectable(GTK_LABEL(details_label), TRUE);
|
||||||
|
g_object_set_data(
|
||||||
|
G_OBJECT(selection_button), "osint-dns-proposal", (gpointer) proposal
|
||||||
|
);
|
||||||
|
g_signal_connect(
|
||||||
|
selection_button, "toggled",
|
||||||
|
G_CALLBACK(osint_dns_review_dialog_on_proposal_toggled), context
|
||||||
|
);
|
||||||
|
g_ptr_array_add(context->selection_buttons, selection_button);
|
||||||
|
gtk_box_append(GTK_BOX(row), selection_button);
|
||||||
|
gtk_box_append(GTK_BOX(row), details_label);
|
||||||
|
g_free(normalized_value);
|
||||||
|
g_free(title);
|
||||||
|
g_free(details);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void osint_dns_review_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
GPtrArray *proposals,
|
||||||
|
OsintDnsReviewDialogConfirmCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintDnsReviewDialogContext *context = NULL;
|
||||||
|
GtkWidget *main_box = NULL;
|
||||||
|
GtkWidget *title_label = NULL;
|
||||||
|
GtkWidget *notice_label = NULL;
|
||||||
|
GtkWidget *scrolled_window = NULL;
|
||||||
|
GtkWidget *proposal_box = NULL;
|
||||||
|
GtkWidget *button_box = NULL;
|
||||||
|
GtkWidget *cancel_button = NULL;
|
||||||
|
|
||||||
|
if (proposals == NULL || proposals->len == 0U) return;
|
||||||
|
context = g_new0(OsintDnsReviewDialogContext, 1);
|
||||||
|
context->window = GTK_WINDOW(gtk_window_new());
|
||||||
|
context->proposals = g_ptr_array_ref(proposals);
|
||||||
|
context->selection_buttons = g_ptr_array_new();
|
||||||
|
context->callback = callback;
|
||||||
|
context->user_data = user_data;
|
||||||
|
|
||||||
|
gtk_window_set_title(context->window, "Intégrer les propositions DNS");
|
||||||
|
gtk_window_set_default_size(context->window, 720, 560);
|
||||||
|
gtk_window_set_modal(context->window, TRUE);
|
||||||
|
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
||||||
|
if (parent_window != NULL)
|
||||||
|
gtk_window_set_transient_for(context->window, parent_window);
|
||||||
|
g_object_set_data_full(
|
||||||
|
G_OBJECT(context->window), "osint-dns-review-context", context,
|
||||||
|
osint_dns_review_dialog_context_free
|
||||||
|
);
|
||||||
|
|
||||||
|
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
|
gtk_widget_set_margin_start(main_box, 20);
|
||||||
|
gtk_widget_set_margin_end(main_box, 20);
|
||||||
|
gtk_widget_set_margin_top(main_box, 20);
|
||||||
|
gtk_widget_set_margin_bottom(main_box, 20);
|
||||||
|
title_label = gtk_label_new("Sélectionner les propositions à intégrer");
|
||||||
|
gtk_widget_set_halign(title_label, GTK_ALIGN_START);
|
||||||
|
gtk_widget_add_css_class(title_label, "title-2");
|
||||||
|
notice_label = gtk_label_new(
|
||||||
|
"Aucune écriture ne sera effectuée avant confirmation. "
|
||||||
|
"Les types non pris en charge restent visibles mais désactivés."
|
||||||
|
);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(notice_label), TRUE);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(notice_label), 0.0F);
|
||||||
|
|
||||||
|
context->select_all_button = gtk_check_button_new_with_label(
|
||||||
|
"Sélectionner toutes les propositions compatibles"
|
||||||
|
);
|
||||||
|
g_signal_connect(
|
||||||
|
context->select_all_button, "toggled",
|
||||||
|
G_CALLBACK(osint_dns_review_dialog_on_select_all_toggled), context
|
||||||
|
);
|
||||||
|
proposal_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
|
||||||
|
for (guint index = 0; index < proposals->len; index++)
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(proposal_box),
|
||||||
|
osint_dns_review_dialog_create_row(
|
||||||
|
context, g_ptr_array_index(proposals, index)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
scrolled_window = gtk_scrolled_window_new();
|
||||||
|
gtk_widget_set_vexpand(scrolled_window, TRUE);
|
||||||
|
gtk_scrolled_window_set_child(
|
||||||
|
GTK_SCROLLED_WINDOW(scrolled_window), proposal_box
|
||||||
|
);
|
||||||
|
|
||||||
|
button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
gtk_widget_set_halign(button_box, GTK_ALIGN_END);
|
||||||
|
cancel_button = gtk_button_new_with_label("Annuler");
|
||||||
|
context->confirm_button = gtk_button_new_with_label("Intégrer la sélection");
|
||||||
|
gtk_widget_add_css_class(context->confirm_button, "suggested-action");
|
||||||
|
gtk_widget_set_sensitive(context->confirm_button, FALSE);
|
||||||
|
g_signal_connect(
|
||||||
|
cancel_button, "clicked",
|
||||||
|
G_CALLBACK(osint_dns_review_dialog_on_cancel_clicked), context
|
||||||
|
);
|
||||||
|
g_signal_connect(
|
||||||
|
context->confirm_button, "clicked",
|
||||||
|
G_CALLBACK(osint_dns_review_dialog_on_confirm_clicked), context
|
||||||
|
);
|
||||||
|
gtk_box_append(GTK_BOX(button_box), cancel_button);
|
||||||
|
gtk_box_append(GTK_BOX(button_box), context->confirm_button);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), title_label);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), notice_label);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), context->select_all_button);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), scrolled_window);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), button_box);
|
||||||
|
gtk_window_set_child(context->window, main_box);
|
||||||
|
gtk_window_present(context->window);
|
||||||
|
}
|
||||||
125
tests/test_osint_dns_integration.c
Normal file
125
tests/test_osint_dns_integration.c
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_osint_dns_integration.c
|
||||||
|
* @brief Tests de l'intégration transactionnelle des propositions DNS.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "core/osint_dns_integration.h"
|
||||||
|
|
||||||
|
#include "dao/entity_dao.h"
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
|
static void test_integration_and_duplicates(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *directory = g_dir_make_tmp("labfy-dns-integration-XXXXXX", &error);
|
||||||
|
char *database_path = g_build_filename(directory, "Enquete.sqlite", NULL);
|
||||||
|
Database *database = NULL;
|
||||||
|
EntityDao *entity_dao = NULL;
|
||||||
|
GPtrArray *proposals = NULL;
|
||||||
|
GPtrArray *entities = NULL;
|
||||||
|
guint inserted = 0U;
|
||||||
|
guint skipped = 0U;
|
||||||
|
|
||||||
|
g_assert_no_error(error);
|
||||||
|
g_assert_true(database_initialize(database_path, "DNS", directory));
|
||||||
|
database = database_open(database_path);
|
||||||
|
proposals = osint_dns_proposal_parse(
|
||||||
|
"example.org",
|
||||||
|
"example.org. 60 IN A 192.0.2.10\n"
|
||||||
|
"example.org. 60 IN CNAME WWW.Example.ORG.\n"
|
||||||
|
"example.org. 60 IN MX 10 mail.example.org.\n"
|
||||||
|
);
|
||||||
|
g_assert_true(osint_dns_integration_apply(
|
||||||
|
database, proposals, &inserted, &skipped, &error
|
||||||
|
));
|
||||||
|
g_assert_no_error(error);
|
||||||
|
g_assert_cmpuint(inserted, ==, 2);
|
||||||
|
g_assert_cmpuint(skipped, ==, 1);
|
||||||
|
|
||||||
|
g_assert_true(osint_dns_integration_apply(
|
||||||
|
database, proposals, &inserted, &skipped, &error
|
||||||
|
));
|
||||||
|
g_assert_cmpuint(inserted, ==, 0);
|
||||||
|
g_assert_cmpuint(skipped, ==, 3);
|
||||||
|
entity_dao = entity_dao_new(database, &error);
|
||||||
|
entities = entity_dao_list_all(entity_dao, &error);
|
||||||
|
g_assert_cmpuint(entities->len, ==, 2);
|
||||||
|
|
||||||
|
g_ptr_array_unref(entities);
|
||||||
|
entity_dao_free(entity_dao);
|
||||||
|
g_ptr_array_unref(proposals);
|
||||||
|
database_close(database);
|
||||||
|
g_assert_cmpint(g_remove(database_path), ==, 0);
|
||||||
|
g_assert_cmpint(g_rmdir(directory), ==, 0);
|
||||||
|
g_free(database_path);
|
||||||
|
g_free(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_invalid_arguments(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
g_assert_false(osint_dns_integration_apply(
|
||||||
|
NULL, NULL, NULL, NULL, &error
|
||||||
|
));
|
||||||
|
g_assert_error(
|
||||||
|
error, OSINT_DNS_INTEGRATION_ERROR,
|
||||||
|
OSINT_DNS_INTEGRATION_ERROR_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_normalized_existing_domain_is_skipped(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *directory = g_dir_make_tmp("labfy-dns-existing-XXXXXX", &error);
|
||||||
|
char *database_path = g_build_filename(directory, "Enquete.sqlite", NULL);
|
||||||
|
Database *database = NULL;
|
||||||
|
EntityDao *entity_dao = NULL;
|
||||||
|
EntityRecord *existing = NULL;
|
||||||
|
GPtrArray *proposals = NULL;
|
||||||
|
guint inserted = 0U;
|
||||||
|
guint skipped = 0U;
|
||||||
|
|
||||||
|
g_assert_true(database_initialize(database_path, "DNS", directory));
|
||||||
|
database = database_open(database_path);
|
||||||
|
entity_dao = entity_dao_new(database, &error);
|
||||||
|
existing = entity_record_new(
|
||||||
|
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "domain_name",
|
||||||
|
"WWW.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, existing, &error));
|
||||||
|
proposals = osint_dns_proposal_parse(
|
||||||
|
"example.org", "example.org. 60 IN CNAME www.example.org.\n"
|
||||||
|
);
|
||||||
|
g_assert_true(osint_dns_integration_apply(
|
||||||
|
database, proposals, &inserted, &skipped, &error
|
||||||
|
));
|
||||||
|
g_assert_cmpuint(inserted, ==, 0);
|
||||||
|
g_assert_cmpuint(skipped, ==, 1);
|
||||||
|
|
||||||
|
g_ptr_array_unref(proposals);
|
||||||
|
entity_record_free(existing);
|
||||||
|
entity_dao_free(entity_dao);
|
||||||
|
database_close(database);
|
||||||
|
g_assert_cmpint(g_remove(database_path), ==, 0);
|
||||||
|
g_assert_cmpint(g_rmdir(directory), ==, 0);
|
||||||
|
g_free(database_path);
|
||||||
|
g_free(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
g_test_init(&argc, &argv, NULL);
|
||||||
|
g_test_add_func("/osint-dns-integration/apply", test_integration_and_duplicates);
|
||||||
|
g_test_add_func("/osint-dns-integration/invalid", test_invalid_arguments);
|
||||||
|
g_test_add_func(
|
||||||
|
"/osint-dns-integration/normalized-duplicate",
|
||||||
|
test_normalized_existing_domain_is_skipped
|
||||||
|
);
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
|
|
@ -49,11 +49,44 @@ static void test_invalid_arguments(void)
|
||||||
g_assert_null(osint_dns_proposal_parse("example.org", NULL));
|
g_assert_null(osint_dns_proposal_parse("example.org", NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_entity_mapping_and_normalization(void)
|
||||||
|
{
|
||||||
|
const char *output =
|
||||||
|
"example.org. 60 IN A 192.0.2.10\n"
|
||||||
|
"example.org. 60 IN AAAA 2001:0db8:0:0:0:0:0:1\n"
|
||||||
|
"example.org. 60 IN CNAME WWW.Example.ORG.\n"
|
||||||
|
"example.org. 60 IN MX 10 mail.example.org.\n";
|
||||||
|
GPtrArray *proposals = osint_dns_proposal_parse("example.org", output);
|
||||||
|
const OsintDnsProposal *ipv4 = g_ptr_array_index(proposals, 0);
|
||||||
|
const OsintDnsProposal *ipv6 = g_ptr_array_index(proposals, 1);
|
||||||
|
const OsintDnsProposal *domain = g_ptr_array_index(proposals, 2);
|
||||||
|
const OsintDnsProposal *unsupported = g_ptr_array_index(proposals, 3);
|
||||||
|
char *ipv4_value = osint_dns_proposal_dup_normalized_value(ipv4);
|
||||||
|
char *ipv6_value = osint_dns_proposal_dup_normalized_value(ipv6);
|
||||||
|
char *domain_value = osint_dns_proposal_dup_normalized_value(domain);
|
||||||
|
|
||||||
|
g_assert_cmpstr(osint_dns_proposal_get_entity_type(ipv4), ==, "ip_address");
|
||||||
|
g_assert_cmpstr(ipv4_value, ==, "192.0.2.10");
|
||||||
|
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_null(osint_dns_proposal_get_entity_type(unsupported));
|
||||||
|
g_assert_null(osint_dns_proposal_dup_normalized_value(unsupported));
|
||||||
|
|
||||||
|
g_free(ipv4_value);
|
||||||
|
g_free(ipv6_value);
|
||||||
|
g_free(domain_value);
|
||||||
|
g_ptr_array_unref(proposals);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
g_test_init(&argc, &argv, NULL);
|
g_test_init(&argc, &argv, NULL);
|
||||||
g_test_add_func("/osint-dns-proposal/recognized", test_parse_recognized_records);
|
g_test_add_func("/osint-dns-proposal/recognized", test_parse_recognized_records);
|
||||||
g_test_add_func("/osint-dns-proposal/ignored", test_ignore_unrecognized_lines);
|
g_test_add_func("/osint-dns-proposal/ignored", test_ignore_unrecognized_lines);
|
||||||
g_test_add_func("/osint-dns-proposal/invalid", test_invalid_arguments);
|
g_test_add_func("/osint-dns-proposal/invalid", test_invalid_arguments);
|
||||||
|
g_test_add_func(
|
||||||
|
"/osint-dns-proposal/mapping", test_entity_mapping_and_normalization
|
||||||
|
);
|
||||||
return g_test_run();
|
return g_test_run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue