@anonympins/fingerprint 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +60 -53
- package/composer.json +38 -38
- package/index.js +4 -4
- package/package.json +103 -103
- package/phpunit.xml +20 -20
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.js +4429 -4381
- package/src/js/mongodb-store.js +79 -79
- package/src/js/pow.solver.inline.js +31 -0
- package/src/js/pow.solver.js +31 -0
- package/src/js/tests/fingerprint.builder.test.js +79 -0
- package/src/js/tests/fingerprint.client.init.test.js +120 -0
- package/src/js/tests/fingerprint.client.test.js +105 -0
- package/src/js/tests/fingerprint.engine.test.js +371 -0
- package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
- package/src/js/tests/fingerprint.test.js +2319 -0
- package/src/js/tests/ip-reputation.test.js +132 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
- package/src/js/tests/library.test.js +96 -0
- package/src/js/tests/metrics.test.js +104 -0
- package/src/js/tests/pow.solver.test.js +198 -0
- package/src/js/tests/problem-manager.test.js +323 -0
- package/src/js/tests/stores.test.js +118 -0
- package/src/php/Challenge/ChallengeUtils.php +361 -361
- package/src/php/Config/SecurityProfiles.php +271 -266
- package/src/php/FingerprintBuilder.php +185 -185
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +1006 -1006
- package/src/php/Ja3AnomalyDetector.php +227 -227
- package/src/php/Optimization/FunctionRegistry.php +62 -62
- package/src/php/Optimization/Optimization.php +255 -255
- package/src/php/Optimization/OptimizationOperators.php +304 -304
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +104 -104
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/ChallengeUtilsTest.php +81 -81
- package/src/php/Tests/FingerprintBuilderTest.php +57 -57
- package/src/php/Tests/FingerprintClientTest.php +71 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -299
- package/src/php/Tests/IpReputationTest.php +156 -156
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
- package/src/php/Tests/MetricsTest.php +45 -45
- package/src/php/Tests/PowTest.php +39 -39
- package/src/php/Tests/ProblemManagerTest.php +296 -296
- package/src/php/Tests/RequestUtilsTest.php +253 -145
- package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
- package/src/php/Tests/problems.config.json +8 -8
- package/src/php/Utils/BigInt.php +144 -144
- package/src/php/Utils/Logger.php +29 -29
- package/src/php/Utils/MaliciousPatterns.php +58 -58
- package/src/php/Utils/MetricsManager.php +166 -166
- package/src/php/Utils/RequestUtils.php +1169 -1169
- package/src/php/Utils/TLSClientHelloParser.php +117 -0
- package/src/php/bin/auto-tune.php +117 -117
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Décode nativement un paquet TLS Client Hello binaire pour calculer l'empreinte JA3/JA4.
|
|
9
|
+
*/
|
|
10
|
+
class TLSClientHelloParser
|
|
11
|
+
{
|
|
12
|
+
private const GREASE_VALUES = [
|
|
13
|
+
2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
|
|
14
|
+
35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse le Client Hello brut et retourne l'empreinte JA3 et une approximation JA4.
|
|
19
|
+
*
|
|
20
|
+
* @param string $binary Le premier paquet TCP reçu sur la socket.
|
|
21
|
+
* @return array{ja3_string: string, ja3_hash: string, ja4_raw: string}|null
|
|
22
|
+
*/
|
|
23
|
+
public static function parse(string $binary): ?array
|
|
24
|
+
{
|
|
25
|
+
$len = strlen($binary);
|
|
26
|
+
if ($len < 43) {
|
|
27
|
+
return null; // Paquet trop court
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1. Vérification du Record Layer Type (0x16 = Handshake)
|
|
31
|
+
if (ord($binary[0]) !== 0x16) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Vérification du Handshake Type (0x01 = Client Hello)
|
|
36
|
+
if (ord($binary[5]) !== 0x01) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$offset = 43; // Sauter l'en-tête, la version et le Random Client (32 octets)
|
|
41
|
+
if ($len < $offset + 1) return null;
|
|
42
|
+
|
|
43
|
+
// 3. Lecture du Session ID
|
|
44
|
+
$sessionLen = ord($binary[$offset]);
|
|
45
|
+
$offset += 1 + $sessionLen;
|
|
46
|
+
if ($len < $offset + 2) return null;
|
|
47
|
+
|
|
48
|
+
// 4. Lecture des Cipher Suites
|
|
49
|
+
$ciphersLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
50
|
+
$offset += 2;
|
|
51
|
+
if ($len < $offset + $ciphersLen + 1) return null;
|
|
52
|
+
|
|
53
|
+
$ciphers = [];
|
|
54
|
+
for ($i = 0; $i < $ciphersLen; $i += 2) {
|
|
55
|
+
$ciphers[] = unpack('n', substr($binary, $offset + $i, 2))[1];
|
|
56
|
+
}
|
|
57
|
+
$offset += $ciphersLen;
|
|
58
|
+
|
|
59
|
+
// 5. Lecture des Compression Methods
|
|
60
|
+
$compressionLen = ord($binary[$offset]);
|
|
61
|
+
$offset += 1 + $compressionLen;
|
|
62
|
+
if ($len < $offset + 2) return null;
|
|
63
|
+
|
|
64
|
+
// 6. Lecture des Extensions
|
|
65
|
+
$extensionsLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
66
|
+
$offset += 2;
|
|
67
|
+
|
|
68
|
+
$extensions = [];
|
|
69
|
+
$curves = [];
|
|
70
|
+
$points = [];
|
|
71
|
+
|
|
72
|
+
$extLimit = $offset + $extensionsLen;
|
|
73
|
+
while ($offset < $extLimit && $offset + 4 <= $len) {
|
|
74
|
+
$extType = unpack('n', substr($binary, $offset, 2))[1];
|
|
75
|
+
$extLen = unpack('n', substr($binary, $offset + 2, 2))[1];
|
|
76
|
+
$offset += 4;
|
|
77
|
+
|
|
78
|
+
if ($offset + $extLen > $len) break;
|
|
79
|
+
|
|
80
|
+
$extensions[] = $extType;
|
|
81
|
+
|
|
82
|
+
if ($extType === 10) { // Extension Supported Groups (Elliptic Curves)
|
|
83
|
+
if ($extLen >= 2) {
|
|
84
|
+
$curvesLen = unpack('n', substr($binary, $offset, 2))[1];
|
|
85
|
+
for ($j = 2; $j < $curvesLen + 2; $j += 2) {
|
|
86
|
+
$curves[] = unpack('n', substr($binary, $offset + $j, 2))[1];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} elseif ($extType === 11) { // Extension EC Point Formats
|
|
90
|
+
if ($extLen >= 1) {
|
|
91
|
+
$pointsLen = ord($binary[$offset]);
|
|
92
|
+
for ($j = 1; $j < $pointsLen + 1; $j++) {
|
|
93
|
+
$points[] = ord($binary[$offset + $j]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
$offset += $extLen;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Nettoyage des valeurs GREASE (RFC 8701) pour la conformité JA3
|
|
101
|
+
$filterGrease = fn(array $arr) => array_values(array_filter($arr, fn($v) => !in_array($v, self::GREASE_VALUES, true)));
|
|
102
|
+
|
|
103
|
+
$sslVersion = unpack('n', substr($binary, 9, 2))[1];
|
|
104
|
+
$ja3String = implode(',', [
|
|
105
|
+
$sslVersion,
|
|
106
|
+
implode('-', $filterGrease($ciphers)),
|
|
107
|
+
implode('-', $filterGrease($extensions)),
|
|
108
|
+
implode('-', $filterGrease($curves)),
|
|
109
|
+
implode('-', $filterGrease($points))
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
'ja3_string' => $ja3String,
|
|
114
|
+
'ja3_hash' => md5($ja3String)
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* CLI Script d'auto-tuning périodique des configurations de sécurité.
|
|
7
|
-
* Ce script lit la configuration courante, récupère les logs, les assainit,
|
|
8
|
-
* exécute l'optimiseur génétique, et écrase le fichier JSON d'origine.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
$autoloaderPaths = [
|
|
12
|
-
__DIR__ . '/../../vendor/autoload.php',
|
|
13
|
-
__DIR__ . '/../vendor/autoload.php',
|
|
14
|
-
__DIR__ . '/../../../autoload.php',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
$autoloaded = false;
|
|
18
|
-
foreach ($autoloaderPaths as $path) {
|
|
19
|
-
if (file_exists($path)) {
|
|
20
|
-
require_once $path;
|
|
21
|
-
$autoloaded = true;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!$autoloaded) {
|
|
27
|
-
fwrite(STDERR, "Erreur : Impossible de charger l'autoloader. Exécutez 'composer install'.\n");
|
|
28
|
-
exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
use Anonympins\Fingerprint\Optimization\Optimization;
|
|
32
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
33
|
-
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
34
|
-
|
|
35
|
-
// 1. Récupération des arguments CLI
|
|
36
|
-
$configPath = $argv[1] ?? null;
|
|
37
|
-
if (!$configPath) {
|
|
38
|
-
echo "Usage: php auto-tune.php [chemin_vers_security-config.json]\n";
|
|
39
|
-
exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!file_exists($configPath)) {
|
|
43
|
-
fwrite(STDERR, "Erreur : Fichier de configuration introuvable : {$configPath}\n");
|
|
44
|
-
exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
$config = json_decode(file_get_contents($configPath), true);
|
|
48
|
-
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
49
|
-
fwrite(STDERR, "Erreur : Fichier de configuration JSON invalide.\n");
|
|
50
|
-
exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 2. Connexion au store pour récupérer les logs accumulés
|
|
54
|
-
$store = StoreManager::getStore();
|
|
55
|
-
if (!$store) {
|
|
56
|
-
fwrite(STDERR, "Erreur : Aucun store de persistance actif.\n");
|
|
57
|
-
exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
$rawTrafficLogs = $store->get('traffic_logs') ?? [];
|
|
61
|
-
if (empty($rawTrafficLogs)) {
|
|
62
|
-
echo "[AutoTuning] Aucun log de trafic disponible pour l'optimisation.\n";
|
|
63
|
-
exit(0);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 3. Nettoyage des données pour prévenir l'empoisonnement (Sybil attacks)
|
|
67
|
-
$sanitizedLogs = RequestUtils::sanitizeTrafficData($rawTrafficLogs);
|
|
68
|
-
|
|
69
|
-
$minDataPoints = $config['autotuning']['minDataPoints'] ?? 200;
|
|
70
|
-
if (count($sanitizedLogs) < $minDataPoints) {
|
|
71
|
-
echo "[AutoTuning] Reporté : Pas assez de données assainies (" . count($sanitizedLogs) . "/{$minDataPoints}).\n";
|
|
72
|
-
exit(0);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
echo "[AutoTuning] Lancement de l'optimisation sur " . count($sanitizedLogs) . " données de trafic...\n";
|
|
76
|
-
|
|
77
|
-
// 4. Résolution du Front de Pareto
|
|
78
|
-
$paretoFront = Optimization::solveFullSecurityTuning(['trafficData' => $sanitizedLogs]);
|
|
79
|
-
if (empty($paretoFront)) {
|
|
80
|
-
fwrite(STDERR, "[AutoTuning] L'optimisation n'a retourné aucun résultat.\n");
|
|
81
|
-
exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 5. Sélection de la solution la plus équilibrée
|
|
85
|
-
$bestSolution = $paretoFront[0];
|
|
86
|
-
$minDistance = sqrt(pow($bestSolution['objectives'][0], 2) + pow($bestSolution['objectives'][1], 2));
|
|
87
|
-
foreach ($paretoFront as $candidate) {
|
|
88
|
-
$distance = sqrt(pow($candidate['objectives'][0], 2) + pow($candidate['objectives'][1], 2));
|
|
89
|
-
if ($distance < $minDistance) {
|
|
90
|
-
$minDistance = $distance;
|
|
91
|
-
$bestSolution = $candidate;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
$newConfig = $bestSolution['solution'];
|
|
96
|
-
$maxChangeVelocity = 0.15; // Inertie de 15% maximum par cycle
|
|
97
|
-
|
|
98
|
-
$applyInertialUpdate = function (array &$current, array $target) use ($maxChangeVelocity) {
|
|
99
|
-
$sumCurrent = array_sum($current);
|
|
100
|
-
if ($sumCurrent === 0) return;
|
|
101
|
-
$sumTarget = 0;
|
|
102
|
-
foreach ($current as $k => $v) {
|
|
103
|
-
if (isset($target[$k])) $sumTarget += $target[$k];
|
|
104
|
-
}
|
|
105
|
-
$ratio = ($sumTarget - $sumCurrent) / $sumCurrent;
|
|
106
|
-
$factor = 1 + max(-$maxChangeVelocity, min($maxChangeVelocity, $ratio));
|
|
107
|
-
foreach ($current as $k => &$v) {
|
|
108
|
-
if (isset($target[$k])) $v *= $factor;
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
$applyInertialUpdate($config['thresholds'], $newConfig['thresholds']);
|
|
113
|
-
$applyInertialUpdate($config['weights'], $newConfig['weights']);
|
|
114
|
-
$applyInertialUpdate($config['patterns'], $newConfig['patterns']);
|
|
115
|
-
|
|
116
|
-
// 6. Écrasement propre du fichier de configuration original
|
|
117
|
-
file_put_contents($configPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CLI Script d'auto-tuning périodique des configurations de sécurité.
|
|
7
|
+
* Ce script lit la configuration courante, récupère les logs, les assainit,
|
|
8
|
+
* exécute l'optimiseur génétique, et écrase le fichier JSON d'origine.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
$autoloaderPaths = [
|
|
12
|
+
__DIR__ . '/../../vendor/autoload.php',
|
|
13
|
+
__DIR__ . '/../vendor/autoload.php',
|
|
14
|
+
__DIR__ . '/../../../autoload.php',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
$autoloaded = false;
|
|
18
|
+
foreach ($autoloaderPaths as $path) {
|
|
19
|
+
if (file_exists($path)) {
|
|
20
|
+
require_once $path;
|
|
21
|
+
$autoloaded = true;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!$autoloaded) {
|
|
27
|
+
fwrite(STDERR, "Erreur : Impossible de charger l'autoloader. Exécutez 'composer install'.\n");
|
|
28
|
+
exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
use Anonympins\Fingerprint\Optimization\Optimization;
|
|
32
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
33
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
34
|
+
|
|
35
|
+
// 1. Récupération des arguments CLI
|
|
36
|
+
$configPath = $argv[1] ?? null;
|
|
37
|
+
if (!$configPath) {
|
|
38
|
+
echo "Usage: php auto-tune.php [chemin_vers_security-config.json]\n";
|
|
39
|
+
exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!file_exists($configPath)) {
|
|
43
|
+
fwrite(STDERR, "Erreur : Fichier de configuration introuvable : {$configPath}\n");
|
|
44
|
+
exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$config = json_decode(file_get_contents($configPath), true);
|
|
48
|
+
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
49
|
+
fwrite(STDERR, "Erreur : Fichier de configuration JSON invalide.\n");
|
|
50
|
+
exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Connexion au store pour récupérer les logs accumulés
|
|
54
|
+
$store = StoreManager::getStore();
|
|
55
|
+
if (!$store) {
|
|
56
|
+
fwrite(STDERR, "Erreur : Aucun store de persistance actif.\n");
|
|
57
|
+
exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$rawTrafficLogs = $store->get('traffic_logs') ?? [];
|
|
61
|
+
if (empty($rawTrafficLogs)) {
|
|
62
|
+
echo "[AutoTuning] Aucun log de trafic disponible pour l'optimisation.\n";
|
|
63
|
+
exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Nettoyage des données pour prévenir l'empoisonnement (Sybil attacks)
|
|
67
|
+
$sanitizedLogs = RequestUtils::sanitizeTrafficData($rawTrafficLogs);
|
|
68
|
+
|
|
69
|
+
$minDataPoints = $config['autotuning']['minDataPoints'] ?? 200;
|
|
70
|
+
if (count($sanitizedLogs) < $minDataPoints) {
|
|
71
|
+
echo "[AutoTuning] Reporté : Pas assez de données assainies (" . count($sanitizedLogs) . "/{$minDataPoints}).\n";
|
|
72
|
+
exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
echo "[AutoTuning] Lancement de l'optimisation sur " . count($sanitizedLogs) . " données de trafic...\n";
|
|
76
|
+
|
|
77
|
+
// 4. Résolution du Front de Pareto
|
|
78
|
+
$paretoFront = Optimization::solveFullSecurityTuning(['trafficData' => $sanitizedLogs]);
|
|
79
|
+
if (empty($paretoFront)) {
|
|
80
|
+
fwrite(STDERR, "[AutoTuning] L'optimisation n'a retourné aucun résultat.\n");
|
|
81
|
+
exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 5. Sélection de la solution la plus équilibrée
|
|
85
|
+
$bestSolution = $paretoFront[0];
|
|
86
|
+
$minDistance = sqrt(pow($bestSolution['objectives'][0], 2) + pow($bestSolution['objectives'][1], 2));
|
|
87
|
+
foreach ($paretoFront as $candidate) {
|
|
88
|
+
$distance = sqrt(pow($candidate['objectives'][0], 2) + pow($candidate['objectives'][1], 2));
|
|
89
|
+
if ($distance < $minDistance) {
|
|
90
|
+
$minDistance = $distance;
|
|
91
|
+
$bestSolution = $candidate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$newConfig = $bestSolution['solution'];
|
|
96
|
+
$maxChangeVelocity = 0.15; // Inertie de 15% maximum par cycle
|
|
97
|
+
|
|
98
|
+
$applyInertialUpdate = function (array &$current, array $target) use ($maxChangeVelocity) {
|
|
99
|
+
$sumCurrent = array_sum($current);
|
|
100
|
+
if ($sumCurrent === 0) return;
|
|
101
|
+
$sumTarget = 0;
|
|
102
|
+
foreach ($current as $k => $v) {
|
|
103
|
+
if (isset($target[$k])) $sumTarget += $target[$k];
|
|
104
|
+
}
|
|
105
|
+
$ratio = ($sumTarget - $sumCurrent) / $sumCurrent;
|
|
106
|
+
$factor = 1 + max(-$maxChangeVelocity, min($maxChangeVelocity, $ratio));
|
|
107
|
+
foreach ($current as $k => &$v) {
|
|
108
|
+
if (isset($target[$k])) $v *= $factor;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
$applyInertialUpdate($config['thresholds'], $newConfig['thresholds']);
|
|
113
|
+
$applyInertialUpdate($config['weights'], $newConfig['weights']);
|
|
114
|
+
$applyInertialUpdate($config['patterns'], $newConfig['patterns']);
|
|
115
|
+
|
|
116
|
+
// 6. Écrasement propre du fichier de configuration original
|
|
117
|
+
file_put_contents($configPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
118
118
|
echo "[AutoTuning] Succès : Fichier {$configPath} mis à jour avec les paramètres optimisés.\n";
|