feat(osint): add DNS result review
This commit is contained in:
parent
6c8113dfea
commit
5a196f6ac6
8 changed files with 551 additions and 10 deletions
13
Makefile
13
Makefile
|
|
@ -116,6 +116,7 @@ TEST_GRAPH_NODE_POSITION_DAO := tests/test_graph_node_position_dao
|
||||||
TEST_OSINT_SELECTION_CONTEXT := tests/test_osint_selection_context
|
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
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -551,6 +552,11 @@ $(TEST_OSINT_DNS_QUERY): \
|
||||||
src/models/osint_dns_query.c
|
src/models/osint_dns_query.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
|
$(TEST_OSINT_DNS_PROPOSAL): \
|
||||||
|
tests/test_osint_dns_proposal.c \
|
||||||
|
src/models/osint_dns_proposal.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||||
|
|
||||||
$(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 \
|
||||||
|
|
@ -621,7 +627,8 @@ test: \
|
||||||
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
||||||
$(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)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -672,6 +679,7 @@ 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)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -728,7 +736,8 @@ clean:
|
||||||
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
||||||
$(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)
|
||||||
|
|
||||||
-include $(DEP)
|
-include $(DEP)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,8 @@ Le socle actuel comprend notamment :
|
||||||
- catalogue déterministe des actions compatibles avec la sélection OSINT ;
|
- catalogue déterministe des actions compatibles avec la sélection OSINT ;
|
||||||
- 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.
|
||||||
|
|
||||||
Les outils actuellement présents dans le catalogue initial sont :
|
Les outils actuellement présents dans le catalogue initial sont :
|
||||||
|
|
||||||
|
|
|
||||||
80
include/models/osint_dns_proposal.h
Normal file
80
include/models/osint_dns_proposal.h
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_proposal.h
|
||||||
|
* @brief Propositions structurées extraites d'une sortie DNS brute.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_DNS_PROPOSAL_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_DNS_PROPOSAL_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/** @brief Proposition DNS opaque produite avant toute intégration. */
|
||||||
|
typedef struct OsintDnsProposal OsintDnsProposal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extrait les enregistrements reconnus d'une sortie `dig +answer`.
|
||||||
|
*
|
||||||
|
* Le tableau retourné possède ses propositions et doit être libéré avec
|
||||||
|
* g_ptr_array_unref(). Les lignes vides, les commentaires et les lignes
|
||||||
|
* invalides sont ignorés sans modifier la sortie brute d'origine.
|
||||||
|
*
|
||||||
|
* @param target_value Cible DNS ayant produit la sortie.
|
||||||
|
* @param raw_output Sortie standard UTF-8 de dig.
|
||||||
|
*
|
||||||
|
* @return Nouveau tableau, éventuellement vide, ou NULL si les arguments
|
||||||
|
* sont invalides.
|
||||||
|
*/
|
||||||
|
GPtrArray *osint_dns_proposal_parse(
|
||||||
|
const char *target_value,
|
||||||
|
const char *raw_output
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le propriétaire de l'enregistrement.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Chaîne empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *osint_dns_proposal_get_owner(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le type d'enregistrement DNS.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Chaîne empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *osint_dns_proposal_get_record_type(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la valeur brute de l'enregistrement.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Chaîne empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *osint_dns_proposal_get_value(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne la cible initialement interrogée.
|
||||||
|
*
|
||||||
|
* @param proposal Proposition consultée.
|
||||||
|
*
|
||||||
|
* @return Chaîne empruntée, ou NULL.
|
||||||
|
*/
|
||||||
|
const char *osint_dns_proposal_get_target(
|
||||||
|
const OsintDnsProposal *proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -32,6 +32,15 @@ typedef void (*ApplicationMessageDialogConfirmationCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback appelé par l'action facultative d'un dialogue détaillé.
|
||||||
|
*
|
||||||
|
* @param user_data Données associées à l'action.
|
||||||
|
*/
|
||||||
|
typedef void (*ApplicationMessageDialogActionCallback)(
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche une fenêtre modale contenant un message.
|
* @brief Affiche une fenêtre modale contenant un message.
|
||||||
*
|
*
|
||||||
|
|
@ -69,6 +78,34 @@ void application_message_dialog_present_details(
|
||||||
const char *details
|
const char *details
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche un message détaillé avec une action facultative.
|
||||||
|
*
|
||||||
|
* Le dialogue devient propriétaire de action_data lorsque l'affichage est
|
||||||
|
* créé. Les données sont libérées avec action_data_destroy à sa fermeture.
|
||||||
|
*
|
||||||
|
* @param parent_window Fenêtre parente, ou NULL.
|
||||||
|
* @param message_type Type de message.
|
||||||
|
* @param title Titre de la fenêtre, ou NULL.
|
||||||
|
* @param message Message principal, ou NULL.
|
||||||
|
* @param details Contenu détaillé, ou NULL.
|
||||||
|
* @param action_label Libellé de l'action, ou NULL.
|
||||||
|
* @param action_callback Callback de l'action, ou NULL.
|
||||||
|
* @param action_data Données transmises au callback.
|
||||||
|
* @param action_data_destroy Destructeur des données, ou NULL.
|
||||||
|
*/
|
||||||
|
void application_message_dialog_present_details_action(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title,
|
||||||
|
const char *message,
|
||||||
|
const char *details,
|
||||||
|
const char *action_label,
|
||||||
|
ApplicationMessageDialogActionCallback action_callback,
|
||||||
|
gpointer action_data,
|
||||||
|
GDestroyNotify action_data_destroy
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche un dialogue modal demandant une confirmation.
|
* @brief Affiche un dialogue modal demandant une confirmation.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "models/investigation_graph_model.h"
|
#include "models/investigation_graph_model.h"
|
||||||
#include "models/investigation_record.h"
|
#include "models/investigation_record.h"
|
||||||
#include "models/osint_dns_query.h"
|
#include "models/osint_dns_query.h"
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
#include "core/investigation_tree_builder.h"
|
#include "core/investigation_tree_builder.h"
|
||||||
#include "views/create_investigation_dialog.h"
|
#include "views/create_investigation_dialog.h"
|
||||||
#include "views/folder_dialog.h"
|
#include "views/folder_dialog.h"
|
||||||
|
|
@ -162,6 +163,76 @@ typedef struct
|
||||||
char *target_value;
|
char *target_value;
|
||||||
} ApplicationOsintActionContext;
|
} ApplicationOsintActionContext;
|
||||||
|
|
||||||
|
/** @brief Données possédées par l'étape de révision OSINT. */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Application *application;
|
||||||
|
char *proposal_summary;
|
||||||
|
} ApplicationOsintReviewContext;
|
||||||
|
|
||||||
|
/** @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)
|
||||||
|
{
|
||||||
|
ApplicationOsintReviewContext *context = user_data;
|
||||||
|
if (context == NULL) return;
|
||||||
|
g_free(context->proposal_summary);
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Affiche les propositions sans les intégrer à l'enquête. */
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
application_message_dialog_present_details(
|
||||||
|
main_window_get_window(context->application->main_window),
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||||
|
"Propositions DNS à réviser",
|
||||||
|
"Aucune donnée n'a été enregistrée dans l'enquête.",
|
||||||
|
context->proposal_summary
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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);
|
||||||
|
g_string_append_printf(
|
||||||
|
summary,
|
||||||
|
"%u. Cible : %s\n Propriétaire : %s\n Type : %s\n"
|
||||||
|
" Valeur : %s\n\n",
|
||||||
|
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_string_append(
|
||||||
|
summary,
|
||||||
|
"Ces propositions sont uniquement préparées pour révision. "
|
||||||
|
"Elles ne sont pas enregistrées dans SQLite."
|
||||||
|
);
|
||||||
|
return g_string_free(summary, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Libère le contexte d'une action OSINT.
|
* @brief Libère le contexte d'une action OSINT.
|
||||||
*/
|
*/
|
||||||
|
|
@ -989,6 +1060,8 @@ static void application_on_dns_lookup_completed(
|
||||||
char *stderr_text = NULL;
|
char *stderr_text = NULL;
|
||||||
char *details = NULL;
|
char *details = NULL;
|
||||||
char *message = NULL;
|
char *message = NULL;
|
||||||
|
GPtrArray *proposals = NULL;
|
||||||
|
ApplicationOsintReviewContext *review_context = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
if (task == NULL || context == NULL || context->application == NULL)
|
if (task == NULL || context == NULL || context->application == NULL)
|
||||||
|
|
@ -1002,6 +1075,17 @@ static void application_on_dns_lookup_completed(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (background_task_get_state(task) == BACKGROUND_TASK_STATE_CANCELLED)
|
||||||
|
{
|
||||||
|
application_message_dialog_present(
|
||||||
|
main_window_get_window(application->main_window),
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||||
|
"Résolution DNS annulée",
|
||||||
|
"La tâche a été annulée. Aucun résultat n'a été enregistré."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
||||||
{
|
{
|
||||||
error = background_task_dup_error(task);
|
error = background_task_dup_error(task);
|
||||||
|
|
@ -1055,20 +1139,45 @@ static void application_on_dns_lookup_completed(
|
||||||
details = g_strdup("Aucun enregistrement DNS retourné.");
|
details = g_strdup("Aucun enregistrement DNS retourné.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proposals = osint_dns_proposal_parse(context->target_value, stdout_text);
|
||||||
|
if (tool_process_result_is_success(process_result) &&
|
||||||
|
proposals != NULL && proposals->len > 0U)
|
||||||
|
{
|
||||||
|
review_context = g_try_new0(ApplicationOsintReviewContext, 1);
|
||||||
|
if (review_context != NULL)
|
||||||
|
{
|
||||||
|
review_context->application = application;
|
||||||
|
review_context->proposal_summary =
|
||||||
|
application_build_dns_proposal_summary(proposals);
|
||||||
|
}
|
||||||
|
if (review_context == NULL || review_context->proposal_summary == NULL)
|
||||||
|
{
|
||||||
|
application_osint_review_context_free(review_context);
|
||||||
|
review_context = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message = g_strdup_printf(
|
message = g_strdup_printf(
|
||||||
"Cible : %s — code de sortie : %d",
|
"Cible : %s\nOutil : dns.dig\nCode de sortie : %d\n"
|
||||||
|
"Résultat brut non enregistré dans l'enquête.",
|
||||||
context->target_value,
|
context->target_value,
|
||||||
tool_process_result_get_exit_status(process_result)
|
tool_process_result_get_exit_status(process_result)
|
||||||
);
|
);
|
||||||
|
|
||||||
application_message_dialog_present_details(
|
application_message_dialog_present_details_action(
|
||||||
main_window_get_window(application->main_window),
|
main_window_get_window(application->main_window),
|
||||||
tool_process_result_is_success(process_result)
|
tool_process_result_is_success(process_result)
|
||||||
? APPLICATION_MESSAGE_DIALOG_INFORMATION
|
? APPLICATION_MESSAGE_DIALOG_INFORMATION
|
||||||
: APPLICATION_MESSAGE_DIALOG_WARNING,
|
: APPLICATION_MESSAGE_DIALOG_WARNING,
|
||||||
"Résultat de la résolution DNS",
|
"Résultat de la résolution DNS",
|
||||||
message,
|
message,
|
||||||
details
|
details,
|
||||||
|
review_context != NULL ? "Préparer l'intégration" : NULL,
|
||||||
|
review_context != NULL
|
||||||
|
? application_on_osint_prepare_integration
|
||||||
|
: NULL,
|
||||||
|
review_context,
|
||||||
|
application_osint_review_context_free
|
||||||
);
|
);
|
||||||
|
|
||||||
g_clear_pointer(&stdout_bytes, g_bytes_unref);
|
g_clear_pointer(&stdout_bytes, g_bytes_unref);
|
||||||
|
|
@ -1077,6 +1186,7 @@ static void application_on_dns_lookup_completed(
|
||||||
g_free(stderr_text);
|
g_free(stderr_text);
|
||||||
g_free(details);
|
g_free(details);
|
||||||
g_free(message);
|
g_free(message);
|
||||||
|
g_clear_pointer(&proposals, g_ptr_array_unref);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
157
src/models/osint_dns_proposal.c
Normal file
157
src/models/osint_dns_proposal.c
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_dns_proposal.c
|
||||||
|
* @brief Propositions structurées extraites d'une sortie DNS brute.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct OsintDnsProposal
|
||||||
|
{
|
||||||
|
char *owner;
|
||||||
|
char *record_type;
|
||||||
|
char *value;
|
||||||
|
char *target;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Libère une proposition possédée par un tableau. */
|
||||||
|
static void osint_dns_proposal_free(gpointer data)
|
||||||
|
{
|
||||||
|
OsintDnsProposal *proposal = data;
|
||||||
|
if (proposal == NULL) return;
|
||||||
|
g_free(proposal->target);
|
||||||
|
g_free(proposal->value);
|
||||||
|
g_free(proposal->record_type);
|
||||||
|
g_free(proposal->owner);
|
||||||
|
g_free(proposal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Indique si un type DNS produit une proposition structurée. */
|
||||||
|
static gboolean osint_dns_proposal_type_is_recognized(const char *record_type)
|
||||||
|
{
|
||||||
|
static const char *recognized_types[] = {
|
||||||
|
"A", "AAAA", "CAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV",
|
||||||
|
"TXT", NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
for (gsize index = 0; recognized_types[index] != NULL; index++)
|
||||||
|
{
|
||||||
|
if (g_strcmp0(record_type, recognized_types[index]) == 0) return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Extrait le prochain champ séparé par des espaces ASCII. */
|
||||||
|
static char *osint_dns_proposal_take_field(char **cursor)
|
||||||
|
{
|
||||||
|
char *field_start = NULL;
|
||||||
|
char *field_end = NULL;
|
||||||
|
|
||||||
|
if (cursor == NULL || *cursor == NULL) return NULL;
|
||||||
|
field_start = *cursor;
|
||||||
|
while (*field_start != '\0' && g_ascii_isspace(*field_start)) field_start++;
|
||||||
|
if (*field_start == '\0')
|
||||||
|
{
|
||||||
|
*cursor = field_start;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
field_end = field_start;
|
||||||
|
while (*field_end != '\0' && !g_ascii_isspace(*field_end)) field_end++;
|
||||||
|
*cursor = field_end;
|
||||||
|
return g_strndup(field_start, (gsize) (field_end - field_start));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Parse une ligne de réponse DNS, ou retourne NULL. */
|
||||||
|
static OsintDnsProposal *osint_dns_proposal_parse_line(
|
||||||
|
const char *target_value,
|
||||||
|
const char *line
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *line_copy = g_strdup(line);
|
||||||
|
char *cursor = line_copy;
|
||||||
|
char *owner = NULL;
|
||||||
|
char *time_to_live = NULL;
|
||||||
|
char *record_class = NULL;
|
||||||
|
char *record_type = NULL;
|
||||||
|
char *value = NULL;
|
||||||
|
OsintDnsProposal *proposal = NULL;
|
||||||
|
|
||||||
|
if (line_copy == NULL) return NULL;
|
||||||
|
g_strstrip(line_copy);
|
||||||
|
if (line_copy[0] == '\0' || line_copy[0] == ';') goto cleanup;
|
||||||
|
|
||||||
|
owner = osint_dns_proposal_take_field(&cursor);
|
||||||
|
time_to_live = osint_dns_proposal_take_field(&cursor);
|
||||||
|
record_class = osint_dns_proposal_take_field(&cursor);
|
||||||
|
record_type = osint_dns_proposal_take_field(&cursor);
|
||||||
|
while (cursor != NULL && *cursor != '\0' && g_ascii_isspace(*cursor)) cursor++;
|
||||||
|
value = cursor != NULL ? g_strdup(cursor) : NULL;
|
||||||
|
if (value != NULL) g_strstrip(value);
|
||||||
|
|
||||||
|
if (owner == NULL || time_to_live == NULL || record_class == NULL ||
|
||||||
|
record_type == NULL || value == NULL || value[0] == '\0' ||
|
||||||
|
!g_ascii_string_to_unsigned(time_to_live, 10, 0, G_MAXUINT32, NULL, NULL) ||
|
||||||
|
g_strcmp0(record_class, "IN") != 0 ||
|
||||||
|
!osint_dns_proposal_type_is_recognized(record_type))
|
||||||
|
{
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
proposal = g_try_new0(OsintDnsProposal, 1);
|
||||||
|
if (proposal != NULL)
|
||||||
|
{
|
||||||
|
proposal->owner = g_strdup(owner);
|
||||||
|
proposal->record_type = g_strdup(record_type);
|
||||||
|
proposal->value = g_strdup(value);
|
||||||
|
proposal->target = g_strdup(target_value);
|
||||||
|
}
|
||||||
|
if (proposal == NULL || proposal->owner == NULL ||
|
||||||
|
proposal->record_type == NULL || proposal->value == NULL ||
|
||||||
|
proposal->target == NULL)
|
||||||
|
{
|
||||||
|
osint_dns_proposal_free(proposal);
|
||||||
|
proposal = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
g_free(value);
|
||||||
|
g_free(record_type);
|
||||||
|
g_free(record_class);
|
||||||
|
g_free(time_to_live);
|
||||||
|
g_free(owner);
|
||||||
|
g_free(line_copy);
|
||||||
|
return proposal;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPtrArray *osint_dns_proposal_parse(
|
||||||
|
const char *target_value,
|
||||||
|
const char *raw_output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GPtrArray *proposals = NULL;
|
||||||
|
char **lines = NULL;
|
||||||
|
|
||||||
|
if (target_value == NULL || target_value[0] == '\0' || raw_output == NULL)
|
||||||
|
return NULL;
|
||||||
|
proposals = g_ptr_array_new_with_free_func(osint_dns_proposal_free);
|
||||||
|
lines = g_strsplit(raw_output, "\n", -1);
|
||||||
|
for (gsize index = 0; lines != NULL && lines[index] != NULL; index++)
|
||||||
|
{
|
||||||
|
OsintDnsProposal *proposal = osint_dns_proposal_parse_line(
|
||||||
|
target_value, lines[index]
|
||||||
|
);
|
||||||
|
if (proposal != NULL) g_ptr_array_add(proposals, proposal);
|
||||||
|
}
|
||||||
|
g_strfreev(lines);
|
||||||
|
return proposals;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *osint_dns_proposal_get_owner(const OsintDnsProposal *proposal)
|
||||||
|
{ return proposal != NULL ? proposal->owner : NULL; }
|
||||||
|
const char *osint_dns_proposal_get_record_type(const OsintDnsProposal *proposal)
|
||||||
|
{ return proposal != NULL ? proposal->record_type : NULL; }
|
||||||
|
const char *osint_dns_proposal_get_value(const OsintDnsProposal *proposal)
|
||||||
|
{ return proposal != NULL ? proposal->value : NULL; }
|
||||||
|
const char *osint_dns_proposal_get_target(const OsintDnsProposal *proposal)
|
||||||
|
{ return proposal != NULL ? proposal->target : NULL; }
|
||||||
|
|
@ -35,6 +35,36 @@ typedef struct
|
||||||
gboolean completed;
|
gboolean completed;
|
||||||
} ApplicationMessageDialogConfirmationContext;
|
} ApplicationMessageDialogConfirmationContext;
|
||||||
|
|
||||||
|
/** @brief Données possédées par l'action d'un dialogue détaillé. */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ApplicationMessageDialogActionCallback callback;
|
||||||
|
gpointer user_data;
|
||||||
|
GDestroyNotify user_data_destroy;
|
||||||
|
} ApplicationMessageDialogActionContext;
|
||||||
|
|
||||||
|
/** @brief Libère les données possédées par une action détaillée. */
|
||||||
|
static void application_message_dialog_action_context_free(gpointer data)
|
||||||
|
{
|
||||||
|
ApplicationMessageDialogActionContext *context = data;
|
||||||
|
if (context == NULL) return;
|
||||||
|
if (context->user_data_destroy != NULL)
|
||||||
|
context->user_data_destroy(context->user_data);
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Déclenche l'action facultative du dialogue détaillé. */
|
||||||
|
static void application_message_dialog_on_action_clicked(
|
||||||
|
GtkButton *button,
|
||||||
|
gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ApplicationMessageDialogActionContext *context = user_data;
|
||||||
|
(void) button;
|
||||||
|
if (context != NULL && context->callback != NULL)
|
||||||
|
context->callback(context->user_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retourne le libellé correspondant au type de message.
|
* @brief Retourne le libellé correspondant au type de message.
|
||||||
*
|
*
|
||||||
|
|
@ -355,12 +385,16 @@ void application_message_dialog_present(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void application_message_dialog_present_details(
|
void application_message_dialog_present_details_action(
|
||||||
GtkWindow *parent_window,
|
GtkWindow *parent_window,
|
||||||
ApplicationMessageDialogType message_type,
|
ApplicationMessageDialogType message_type,
|
||||||
const char *title,
|
const char *title,
|
||||||
const char *message,
|
const char *message,
|
||||||
const char *details
|
const char *details,
|
||||||
|
const char *action_label,
|
||||||
|
ApplicationMessageDialogActionCallback action_callback,
|
||||||
|
gpointer action_data,
|
||||||
|
GDestroyNotify action_data_destroy
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
GtkWindow *dialog_window = GTK_WINDOW(gtk_window_new());
|
GtkWindow *dialog_window = GTK_WINDOW(gtk_window_new());
|
||||||
|
|
@ -374,6 +408,9 @@ void application_message_dialog_present_details(
|
||||||
GtkWidget *scrolled_window = gtk_scrolled_window_new();
|
GtkWidget *scrolled_window = gtk_scrolled_window_new();
|
||||||
GtkWidget *details_view = gtk_text_view_new();
|
GtkWidget *details_view = gtk_text_view_new();
|
||||||
GtkWidget *close_button = gtk_button_new_with_label("Fermer");
|
GtkWidget *close_button = gtk_button_new_with_label("Fermer");
|
||||||
|
GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
GtkWidget *action_button = NULL;
|
||||||
|
ApplicationMessageDialogActionContext *action_context = NULL;
|
||||||
GtkTextBuffer *details_buffer = gtk_text_view_get_buffer(
|
GtkTextBuffer *details_buffer = gtk_text_view_get_buffer(
|
||||||
GTK_TEXT_VIEW(details_view)
|
GTK_TEXT_VIEW(details_view)
|
||||||
);
|
);
|
||||||
|
|
@ -426,7 +463,7 @@ void application_message_dialog_present_details(
|
||||||
details_view
|
details_view
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_widget_set_halign(close_button, GTK_ALIGN_END);
|
gtk_widget_set_halign(button_box, GTK_ALIGN_END);
|
||||||
g_signal_connect(
|
g_signal_connect(
|
||||||
close_button,
|
close_button,
|
||||||
"clicked",
|
"clicked",
|
||||||
|
|
@ -434,14 +471,65 @@ void application_message_dialog_present_details(
|
||||||
dialog_window
|
dialog_window
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (action_label != NULL && action_label[0] != '\0' &&
|
||||||
|
action_callback != NULL)
|
||||||
|
{
|
||||||
|
action_context = g_new0(ApplicationMessageDialogActionContext, 1);
|
||||||
|
action_context->callback = action_callback;
|
||||||
|
action_context->user_data = action_data;
|
||||||
|
action_context->user_data_destroy = action_data_destroy;
|
||||||
|
g_object_set_data_full(
|
||||||
|
G_OBJECT(dialog_window),
|
||||||
|
"application-message-dialog-action-context",
|
||||||
|
action_context,
|
||||||
|
application_message_dialog_action_context_free
|
||||||
|
);
|
||||||
|
action_button = gtk_button_new_with_label(action_label);
|
||||||
|
gtk_widget_add_css_class(action_button, "suggested-action");
|
||||||
|
g_signal_connect(
|
||||||
|
action_button,
|
||||||
|
"clicked",
|
||||||
|
G_CALLBACK(application_message_dialog_on_action_clicked),
|
||||||
|
action_context
|
||||||
|
);
|
||||||
|
gtk_box_append(GTK_BOX(button_box), action_button);
|
||||||
|
}
|
||||||
|
else if (action_data_destroy != NULL)
|
||||||
|
{
|
||||||
|
action_data_destroy(action_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(button_box), close_button);
|
||||||
|
|
||||||
gtk_box_append(GTK_BOX(main_box), title_label);
|
gtk_box_append(GTK_BOX(main_box), title_label);
|
||||||
gtk_box_append(GTK_BOX(main_box), message_label);
|
gtk_box_append(GTK_BOX(main_box), message_label);
|
||||||
gtk_box_append(GTK_BOX(main_box), scrolled_window);
|
gtk_box_append(GTK_BOX(main_box), scrolled_window);
|
||||||
gtk_box_append(GTK_BOX(main_box), close_button);
|
gtk_box_append(GTK_BOX(main_box), button_box);
|
||||||
gtk_window_set_child(dialog_window, main_box);
|
gtk_window_set_child(dialog_window, main_box);
|
||||||
gtk_window_present(dialog_window);
|
gtk_window_present(dialog_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void application_message_dialog_present_details(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
ApplicationMessageDialogType message_type,
|
||||||
|
const char *title,
|
||||||
|
const char *message,
|
||||||
|
const char *details
|
||||||
|
)
|
||||||
|
{
|
||||||
|
application_message_dialog_present_details_action(
|
||||||
|
parent_window,
|
||||||
|
message_type,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
details,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Libère le contexte associé à une fenêtre de confirmation.
|
* @brief Libère le contexte associé à une fenêtre de confirmation.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
59
tests/test_osint_dns_proposal.c
Normal file
59
tests/test_osint_dns_proposal.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_osint_dns_proposal.c
|
||||||
|
* @brief Tests des propositions structurées extraites de dig.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/osint_dns_proposal.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
static void test_parse_recognized_records(void)
|
||||||
|
{
|
||||||
|
const char *output =
|
||||||
|
"example.org. 300 IN A 93.184.216.34\n"
|
||||||
|
"example.org. 300 IN MX 10 mail.example.org.\n"
|
||||||
|
"example.org. 300 IN TXT \"texte avec espaces\"\n";
|
||||||
|
GPtrArray *proposals = osint_dns_proposal_parse("example.org", output);
|
||||||
|
const OsintDnsProposal *first = g_ptr_array_index(proposals, 0);
|
||||||
|
const OsintDnsProposal *third = g_ptr_array_index(proposals, 2);
|
||||||
|
|
||||||
|
g_assert_cmpuint(proposals->len, ==, 3);
|
||||||
|
g_assert_cmpstr(osint_dns_proposal_get_owner(first), ==, "example.org.");
|
||||||
|
g_assert_cmpstr(osint_dns_proposal_get_record_type(first), ==, "A");
|
||||||
|
g_assert_cmpstr(osint_dns_proposal_get_value(first), ==, "93.184.216.34");
|
||||||
|
g_assert_cmpstr(osint_dns_proposal_get_target(first), ==, "example.org");
|
||||||
|
g_assert_cmpstr(
|
||||||
|
osint_dns_proposal_get_value(third), ==, "\"texte avec espaces\""
|
||||||
|
);
|
||||||
|
g_ptr_array_unref(proposals);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_ignore_unrecognized_lines(void)
|
||||||
|
{
|
||||||
|
const char *output =
|
||||||
|
";; comment\n"
|
||||||
|
"invalid line\n"
|
||||||
|
"example.org. nope IN A 93.184.216.34\n"
|
||||||
|
"example.org. 60 CH A 93.184.216.34\n"
|
||||||
|
"example.org. 60 IN TYPE999 opaque\n";
|
||||||
|
GPtrArray *proposals = osint_dns_proposal_parse("example.org", output);
|
||||||
|
g_assert_nonnull(proposals);
|
||||||
|
g_assert_cmpuint(proposals->len, ==, 0);
|
||||||
|
g_ptr_array_unref(proposals);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_invalid_arguments(void)
|
||||||
|
{
|
||||||
|
g_assert_null(osint_dns_proposal_parse(NULL, ""));
|
||||||
|
g_assert_null(osint_dns_proposal_parse("", ""));
|
||||||
|
g_assert_null(osint_dns_proposal_parse("example.org", NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
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/ignored", test_ignore_unrecognized_lines);
|
||||||
|
g_test_add_func("/osint-dns-proposal/invalid", test_invalid_arguments);
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue