515 lines
13 KiB
Markdown
515 lines
13 KiB
Markdown
# TICKET-039 — Catalogue initial des outils externes et détection de leurs versions
|
||
|
||
## Statut
|
||
|
||
À faire
|
||
|
||
## Priorité
|
||
|
||
Haute
|
||
|
||
## Objectif
|
||
|
||
Créer le catalogue initial des outils externes utilisés par Labfy Investigation et fournir une détection fiable de leur version.
|
||
|
||
Ce ticket complète les modules déjà validés :
|
||
|
||
```text
|
||
ToolRegistry
|
||
ToolProcess
|
||
ToolTask
|
||
```
|
||
|
||
Le catalogue décrit les outils connus par l’application. Le registre décrit leur état sur la machine courante.
|
||
|
||
```text
|
||
ToolCatalog
|
||
description statique des outils connus
|
||
↓
|
||
ToolRegistry
|
||
disponibilité, chemin résolu, version détectée
|
||
↓
|
||
ToolProcess
|
||
exécution sécurisée de la commande de version
|
||
```
|
||
|
||
## Contexte
|
||
|
||
`ToolRegistry` permet déjà d’enregistrer un outil, de rechercher son exécutable dans le `PATH`, de conserver son chemin résolu et sa version détectée, et de distinguer les états `UNKNOWN`, `AVAILABLE` et `MISSING`.
|
||
|
||
Il manque une source centrale définissant :
|
||
|
||
- les outils reconnus par l’application ;
|
||
- leur identifiant interne ;
|
||
- leur nom affiché ;
|
||
- leur exécutable ;
|
||
- leur importance ;
|
||
- les arguments permettant de demander leur version ;
|
||
- la manière de normaliser la sortie obtenue.
|
||
|
||
Sans catalogue central, chaque fonctionnalité finirait par enregistrer elle-même ses dépendances.
|
||
|
||
## Modules attendus
|
||
|
||
```text
|
||
include/core/tool_catalog.h
|
||
src/core/tool_catalog.c
|
||
tests/test_tool_catalog.c
|
||
```
|
||
|
||
Le ticket ne doit pas modifier l’API publique de `ToolRegistry`, sauf nécessité démontrée pendant l’implémentation.
|
||
|
||
## Responsabilités de ToolCatalog
|
||
|
||
Le module doit :
|
||
|
||
1. conserver une liste statique et immuable des outils connus ;
|
||
2. garantir l’unicité de leurs identifiants ;
|
||
3. exposer leurs informations descriptives ;
|
||
4. enregistrer l’ensemble du catalogue dans un `ToolRegistry` ;
|
||
5. définir les arguments de détection de version ;
|
||
6. exécuter ces arguments avec `ToolProcess` ;
|
||
7. normaliser la sortie de version ;
|
||
8. retourner la version détectée sans modifier directement le registre ;
|
||
9. gérer l’annulation ;
|
||
10. rester indépendant de GTK.
|
||
|
||
Le module ne doit pas installer de dépendance, modifier le `PATH`, utiliser un shell, analyser un résultat OSINT ni modifier l’interface graphique.
|
||
|
||
## Catalogue initial
|
||
|
||
| Identifiant interne | Nom affiché | Exécutable | Importance | Arguments de version |
|
||
|---|---|---|---|---|
|
||
| `dns.dig` | `dig` | `dig` | optionnel | `-v` |
|
||
| `dns.host` | `host` | `host` | optionnel | `-V` |
|
||
| `network.whois` | `whois` | `whois` | optionnel | `--version` |
|
||
| `http.curl` | `curl` | `curl` | optionnel | `--version` |
|
||
| `tls.openssl` | `OpenSSL` | `openssl` | optionnel | `version` |
|
||
|
||
Tous ces outils sont optionnels au niveau de l’application entière.
|
||
|
||
Ne pas ajouter encore `nslookup`, `traceroute`, `jq`, `file`, `exiftool`, `nmap`, `subfinder`, `amass`, les outils Python ou les outils installés depuis GitHub.
|
||
|
||
## Structure ToolCatalogEntry
|
||
|
||
Créer une structure opaque :
|
||
|
||
```c
|
||
typedef struct ToolCatalogEntry ToolCatalogEntry;
|
||
```
|
||
|
||
Elle conserve au minimum :
|
||
|
||
```text
|
||
identifier
|
||
display_name
|
||
executable_name
|
||
requirement
|
||
version_arguments
|
||
```
|
||
|
||
Les données du catalogue sont statiques et immuables. Elles ne doivent jamais être libérées par l’appelant.
|
||
|
||
## Domaine d’erreur
|
||
|
||
```c
|
||
#define TOOL_CATALOG_ERROR \
|
||
tool_catalog_error_quark()
|
||
```
|
||
|
||
```c
|
||
typedef enum
|
||
{
|
||
TOOL_CATALOG_ERROR_INVALID_ARGUMENT,
|
||
TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND,
|
||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||
TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED,
|
||
TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED,
|
||
TOOL_CATALOG_ERROR_TOOL_MISSING,
|
||
TOOL_CATALOG_ERROR_INVALID_TOOL_STATE,
|
||
TOOL_CATALOG_ERROR_PROCESS,
|
||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||
TOOL_CATALOG_ERROR_VERSION_OUTPUT,
|
||
TOOL_CATALOG_ERROR_CANCELLED
|
||
} ToolCatalogError;
|
||
```
|
||
|
||
```c
|
||
GQuark tool_catalog_error_quark(void);
|
||
```
|
||
|
||
## API publique proposée
|
||
|
||
### Consultation
|
||
|
||
```c
|
||
gsize tool_catalog_get_count(void);
|
||
```
|
||
|
||
```c
|
||
const ToolCatalogEntry *tool_catalog_get_entry(
|
||
gsize index
|
||
);
|
||
```
|
||
|
||
```c
|
||
const ToolCatalogEntry *tool_catalog_find(
|
||
const char *identifier
|
||
);
|
||
```
|
||
|
||
### Accesseurs
|
||
|
||
```c
|
||
const char *tool_catalog_entry_get_identifier(
|
||
const ToolCatalogEntry *entry
|
||
);
|
||
```
|
||
|
||
```c
|
||
const char *tool_catalog_entry_get_display_name(
|
||
const ToolCatalogEntry *entry
|
||
);
|
||
```
|
||
|
||
```c
|
||
const char *tool_catalog_entry_get_executable_name(
|
||
const ToolCatalogEntry *entry
|
||
);
|
||
```
|
||
|
||
```c
|
||
ToolRequirement tool_catalog_entry_get_requirement(
|
||
const ToolCatalogEntry *entry
|
||
);
|
||
```
|
||
|
||
```c
|
||
gsize tool_catalog_entry_get_version_argument_count(
|
||
const ToolCatalogEntry *entry
|
||
);
|
||
```
|
||
|
||
```c
|
||
const char *tool_catalog_entry_get_version_argument(
|
||
const ToolCatalogEntry *entry,
|
||
gsize index
|
||
);
|
||
```
|
||
|
||
### Enregistrement
|
||
|
||
```c
|
||
gboolean tool_catalog_register_defaults(
|
||
ToolRegistry *tool_registry,
|
||
GError **error
|
||
);
|
||
```
|
||
|
||
### Détection d’une version
|
||
|
||
```c
|
||
gboolean tool_catalog_detect_version(
|
||
const ToolRegistry *tool_registry,
|
||
const char *identifier,
|
||
GCancellable *cancellable,
|
||
char **out_version,
|
||
GError **error
|
||
);
|
||
```
|
||
|
||
`out_version` reçoit une chaîne nouvellement allouée, à libérer avec `g_free()`.
|
||
|
||
La fonction ne doit pas appeler `tool_registry_set_version()`.
|
||
|
||
## Règles d’enregistrement
|
||
|
||
`tool_catalog_register_defaults()` doit vérifier avant toute insertion qu’aucun identifiant du catalogue n’existe déjà dans le registre.
|
||
|
||
En cas de doublon :
|
||
|
||
- aucune entrée ne doit être ajoutée ;
|
||
- la fonction retourne `FALSE` ;
|
||
- l’erreur vaut `TOOL_CATALOG_ERROR_REGISTRATION`.
|
||
|
||
Cette prévalidation évite une insertion partielle, car `ToolRegistry` ne fournit pas de suppression.
|
||
|
||
## Validation de la détection
|
||
|
||
La fonction doit refuser :
|
||
|
||
- un registre `NULL` ;
|
||
- un identifiant `NULL` ou vide ;
|
||
- un `out_version` égal à `NULL` ;
|
||
- un `out_version` pointant déjà vers une chaîne ;
|
||
- un `GError` déjà initialisé.
|
||
|
||
Au début d’un appel valide :
|
||
|
||
```c
|
||
*out_version = NULL;
|
||
```
|
||
|
||
Cas d’erreur :
|
||
|
||
```text
|
||
entrée absente du catalogue → TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND
|
||
outil absent du registre → TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED
|
||
état UNKNOWN → TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED
|
||
état MISSING → TOOL_CATALOG_ERROR_TOOL_MISSING
|
||
AVAILABLE sans chemin résolu → TOOL_CATALOG_ERROR_INVALID_TOOL_STATE
|
||
```
|
||
|
||
## Exécution de la commande de version
|
||
|
||
Utiliser exclusivement :
|
||
|
||
```c
|
||
tool_process_run()
|
||
```
|
||
|
||
avec :
|
||
|
||
```text
|
||
executable_path = chemin résolu du ToolRegistry
|
||
arguments = arguments statiques du ToolCatalogEntry
|
||
working_directory = NULL
|
||
cancellable = paramètre reçu
|
||
```
|
||
|
||
Interdictions :
|
||
|
||
- `/bin/sh -c` ;
|
||
- `system()` ;
|
||
- `popen()` ;
|
||
- concaténation d’une ligne de commande ;
|
||
- redirections interprétées ;
|
||
- ajout ou transformation des arguments.
|
||
|
||
## Annulation
|
||
|
||
Si `ToolProcess` retourne `TOOL_PROCESS_ERROR_CANCELLED`, la fonction doit produire :
|
||
|
||
```text
|
||
G_IO_ERROR
|
||
G_IO_ERROR_CANCELLED
|
||
```
|
||
|
||
Aucune version partielle ne doit être retournée.
|
||
|
||
## Code de sortie
|
||
|
||
Pour une commande de version, un code de sortie non nul est un échec :
|
||
|
||
```text
|
||
TOOL_CATALOG_ERROR_VERSION_COMMAND
|
||
```
|
||
|
||
Une terminaison par signal produit la même catégorie d’erreur.
|
||
|
||
La sortie ne doit pas être interprétée lorsque le processus n’a pas terminé normalement avec le code zéro.
|
||
|
||
## Sélection de la sortie
|
||
|
||
Règle générique :
|
||
|
||
1. prendre la première ligne non vide de stdout ;
|
||
2. si stdout ne contient rien d’exploitable, essayer stderr ;
|
||
3. si aucun flux ne contient de texte exploitable, échouer.
|
||
|
||
Cette règle évite une logique spéciale par outil pendant ce premier ticket.
|
||
|
||
## Normalisation
|
||
|
||
Le catalogue ne doit pas extraire seulement un numéro sémantique.
|
||
|
||
La version conservée est la première ligne descriptive exploitable, par exemple :
|
||
|
||
```text
|
||
DiG 9.20.8
|
||
curl 8.14.1 (x86_64-pc-linux-gnu)
|
||
OpenSSL 3.5.1 1 Jul 2025
|
||
```
|
||
|
||
Algorithme :
|
||
|
||
1. obtenir les octets du flux ;
|
||
2. inspecter au maximum 4096 octets par flux ;
|
||
3. convertir les octets invalides avec `g_utf8_make_valid()` ;
|
||
4. séparer les lignes ;
|
||
5. ignorer les lignes vides ;
|
||
6. choisir la première ligne non vide ;
|
||
7. retirer espaces et tabulations en début et fin ;
|
||
8. retirer `\r` et `\n` ;
|
||
9. refuser un résultat vide ;
|
||
10. retourner une nouvelle chaîne.
|
||
|
||
## Propriété des données
|
||
|
||
Les entrées et chaînes du catalogue sont empruntées et statiques.
|
||
|
||
La chaîne placée dans `*out_version` appartient à l’appelant et doit être libérée avec `g_free()`.
|
||
|
||
`ToolCatalog` ne devient jamais propriétaire du registre.
|
||
|
||
## Contraintes de thread
|
||
|
||
Le catalogue statique est immuable.
|
||
|
||
`tool_catalog_detect_version()` :
|
||
|
||
- ne modifie pas le catalogue ;
|
||
- ne modifie pas le registre ;
|
||
- copie le chemin nécessaire avant l’exécution ;
|
||
- ne conserve aucun pointeur vers `ToolInfo` après la préparation.
|
||
|
||
Le stockage avec `tool_registry_set_version()` reste la responsabilité du propriétaire du registre, idéalement dans le thread principal.
|
||
|
||
## Tests unitaires obligatoires
|
||
|
||
Les tests utilisent uniquement de faux exécutables temporaires.
|
||
|
||
Ils ne doivent pas dépendre des outils réellement installés.
|
||
|
||
### Tests du catalogue
|
||
|
||
1. nombre exact d’entrées ;
|
||
2. contenu exact des cinq entrées ;
|
||
3. index invalide ;
|
||
4. recherche par identifiant ;
|
||
5. identifiant inconnu ;
|
||
6. unicité des identifiants ;
|
||
7. arguments de version ;
|
||
8. enregistrement complet dans un registre vide ;
|
||
9. état initial `UNKNOWN` ;
|
||
10. prévalidation d’un doublon sans insertion partielle.
|
||
|
||
### Tests de détection
|
||
|
||
11. arguments invalides ;
|
||
12. entrée inconnue ;
|
||
13. outil non enregistré ;
|
||
14. outil non vérifié ;
|
||
15. outil absent ;
|
||
16. version sur stdout ;
|
||
17. version sur stderr ;
|
||
18. première ligne non vide ;
|
||
19. normalisation des espaces et fins de ligne ;
|
||
20. sortie non UTF-8 ;
|
||
21. sortie vide ;
|
||
22. code de sortie non nul ;
|
||
23. terminaison par signal ;
|
||
24. annulation ;
|
||
25. indépendance du registre ;
|
||
26. plusieurs détections successives.
|
||
|
||
## Noms de tests suggérés
|
||
|
||
```text
|
||
/tool_catalog/entries
|
||
/tool_catalog/invalid_index
|
||
/tool_catalog/find
|
||
/tool_catalog/unique_entries
|
||
/tool_catalog/register_defaults
|
||
/tool_catalog/register_duplicate
|
||
/tool_catalog/detect_invalid_arguments
|
||
/tool_catalog/detect_unknown_entry
|
||
/tool_catalog/detect_unregistered_tool
|
||
/tool_catalog/detect_unchecked_tool
|
||
/tool_catalog/detect_missing_tool
|
||
/tool_catalog/version_stdout
|
||
/tool_catalog/version_stderr
|
||
/tool_catalog/version_first_nonempty_line
|
||
/tool_catalog/version_normalization
|
||
/tool_catalog/version_non_utf8
|
||
/tool_catalog/version_empty
|
||
/tool_catalog/version_nonzero_exit
|
||
/tool_catalog/version_signaled
|
||
/tool_catalog/version_cancelled
|
||
/tool_catalog/version_registry_independence
|
||
/tool_catalog/version_successive_runs
|
||
```
|
||
|
||
## Makefile
|
||
|
||
```make
|
||
TEST_TOOL_CATALOG := tests/test_tool_catalog
|
||
```
|
||
|
||
```make
|
||
$(TEST_TOOL_CATALOG): \
|
||
tests/test_tool_catalog.c \
|
||
src/core/tool_catalog.c \
|
||
src/core/tool_registry.c \
|
||
src/core/tool_process.c
|
||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||
```
|
||
|
||
Ajouter le binaire à `make test` et `make clean`.
|
||
|
||
## Vérifications
|
||
|
||
```bash
|
||
make clean
|
||
make
|
||
make tests/test_tool_catalog
|
||
./tests/test_tool_catalog
|
||
make test
|
||
git diff --check
|
||
```
|
||
|
||
## Vérification mémoire
|
||
|
||
```bash
|
||
G_DEBUG=gc-friendly \
|
||
G_SLICE=always-malloc \
|
||
valgrind \
|
||
--leak-check=full \
|
||
--show-leak-kinds=all \
|
||
./tests/test_tool_catalog
|
||
```
|
||
|
||
## Critères d’acceptation
|
||
|
||
Le ticket est validé lorsque :
|
||
|
||
- le catalogue contient exactement les cinq outils prévus ;
|
||
- toutes les entrées sont immuables ;
|
||
- tous les identifiants sont uniques ;
|
||
- le catalogue s’enregistre dans un registre vide ;
|
||
- un doublon empêche toute insertion partielle ;
|
||
- la détection utilise `ToolProcess` ;
|
||
- aucun shell n’est utilisé ;
|
||
- stdout et stderr sont pris en charge ;
|
||
- la première ligne non vide est normalisée ;
|
||
- une sortie invalide devient un UTF-8 valide ;
|
||
- l’inspection est limitée à 4096 octets par flux ;
|
||
- un code non nul ou un signal est refusé ;
|
||
- l’annulation produit `G_IO_ERROR_CANCELLED` ;
|
||
- la fonction ne modifie pas directement le registre ;
|
||
- les versions successives sont indépendantes ;
|
||
- les tests ne dépendent d’aucun outil installé ;
|
||
- tous les tests passent ;
|
||
- aucune fuite mémoire n’est détectée ;
|
||
- le test est intégré au `Makefile`.
|
||
|
||
## Démonstration finale
|
||
|
||
Après validation :
|
||
|
||
1. créer un `ToolRegistry` ;
|
||
2. appeler `tool_catalog_register_defaults()` ;
|
||
3. appeler `tool_registry_refresh()` ;
|
||
4. afficher les outils présents et absents ;
|
||
5. détecter la version d’un outil disponible ;
|
||
6. stocker explicitement la version avec `tool_registry_set_version()`.
|
||
|
||
Cette démonstration restera hors de l’interface GTK principale.
|
||
|
||
## Suite prévue
|
||
|
||
```text
|
||
#040 — Initialisation des dépendances et analyse asynchrone au démarrage
|
||
```
|
||
|
||
Ce ticket devra créer le registre global de l’application, enregistrer le catalogue, rafraîchir les disponibilités hors du thread GTK, détecter les versions et publier une tâche dans `TaskManager`.
|