Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
using System.Data.OleDb;
|
||||
using System.Text.Json;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using MimeKit;
|
||||
using Omika.Core.Security;
|
||||
|
||||
// ============================================================
|
||||
// OMIKA SMTP PROBE — READ-ONLY DB + (optionnel) SMTP connect/auth/send
|
||||
// Modes :
|
||||
// --audit [--db OMIKA_TEST|BATIK_TEST]
|
||||
// Scan de TOUTES les configs EMAIL_ENVOI% : classification + structure
|
||||
// + déchiffrement (composant DE PRODUCTION SmtpPasswordStandardDecryptor).
|
||||
// Ne modifie aucune donnée.
|
||||
// <PU_Code> <PU_IDUtilisateur> [--db ...] [--send]
|
||||
// Audit d'une configuration unique + SMTP connect/auth (+envoi si --send).
|
||||
// Ne JAMAIS afficher sMdp / sMdpStandard / plaintext / login / clé.
|
||||
// ============================================================
|
||||
|
||||
const string server = "127.0.0.1";
|
||||
const int port = 4900;
|
||||
const string user = "susana";
|
||||
const string pass = "123";
|
||||
|
||||
// ProtectionStd (ParametreUtilisateur) par base de test.
|
||||
var dbMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["OMIKA_TEST"] = "A1BAF17C65BC565F2927BF1A85DAFEED",
|
||||
["BATIK_TEST"] = "E2FC3072E6145E1B20DA7321755CD607",
|
||||
};
|
||||
|
||||
bool audit = args.Contains("--audit");
|
||||
int dbIdx = Array.IndexOf(args, "--db");
|
||||
string db = dbIdx >= 0 && dbIdx + 1 < args.Length ? args[dbIdx + 1] : "OMIKA_TEST";
|
||||
if (!dbMap.TryGetValue(db, out string? stdPwd))
|
||||
{
|
||||
Console.Error.WriteLine($"ERROR: unknown database '{db}' (known: OMIKA_TEST, BATIK_TEST).");
|
||||
return 2;
|
||||
}
|
||||
|
||||
string connStr =
|
||||
$"Provider=PCSOFT.HFSQL;Data Source={server}:{port};Initial Catalog={db};" +
|
||||
$"User ID={user};Password={pass};" +
|
||||
$"Extended Properties=\"Password=ParametreUtilisateur:{stdPwd}\"";
|
||||
|
||||
var decryptor = new SmtpPasswordStandardDecryptor();
|
||||
|
||||
if (audit)
|
||||
{
|
||||
return RunAudit(connStr, db, decryptor);
|
||||
}
|
||||
|
||||
// --- Mode config unique ---
|
||||
string code = args.Where(a => !a.StartsWith("--")).ElementAtOrDefault(0) ?? "EMAIL_ENVOI_5";
|
||||
string idUserText = args.Where(a => !a.StartsWith("--")).ElementAtOrDefault(1) ?? "7";
|
||||
bool send = args.Contains("--send");
|
||||
|
||||
if (code.Any(c => !(char.IsLetterOrDigit(c) || c == '_')) || !long.TryParse(idUserText, out long idUser))
|
||||
{
|
||||
Console.Error.WriteLine("USAGE: Omika.SmtpRealProbe <PU_Code> <PU_IDUtilisateur> [--db DB] [--send]");
|
||||
return 2;
|
||||
}
|
||||
|
||||
string? json = null;
|
||||
try
|
||||
{
|
||||
using var conn = new OleDbConnection(connStr);
|
||||
conn.Open();
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"SELECT PU_Valeur FROM ParametreUtilisateur WHERE PU_Code = '{code}' AND PU_IDUtilisateur = {idUser}";
|
||||
json = cmd.ExecuteScalar() as string;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"SMTP CONFIG FOUND = NO (DB read failed: {ex.GetType().Name})");
|
||||
return 3;
|
||||
}
|
||||
|
||||
Console.WriteLine("SMTP CONFIG FOUND = " + (string.IsNullOrWhiteSpace(json) ? "NO" : "YES"));
|
||||
Console.WriteLine("SOURCE TABLE = ParametreUtilisateur");
|
||||
Console.WriteLine($"PU_CODE = {code}");
|
||||
Console.WriteLine($"PU_IDUTILISATEUR = {idUser}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json)) return 4;
|
||||
|
||||
var cfg = ExtractFirst(json);
|
||||
if (cfg is null) { Console.WriteLine("JSON PARSE = FAILED"); return 5; }
|
||||
|
||||
Console.WriteLine("sMdp PRESENT = " + (string.IsNullOrWhiteSpace(cfg.SMdp) ? "NO" : "YES"));
|
||||
Console.WriteLine("sMdpStandard PRESENT = " + (string.IsNullOrWhiteSpace(cfg.SMdpStandard) ? "NO" : "YES"));
|
||||
Console.WriteLine("sMdpStandard EMPTY = " + (string.IsNullOrWhiteSpace(cfg.SMdpStandard) ? "YES" : "NO"));
|
||||
Console.WriteLine("SMTP HOST = " + (string.IsNullOrWhiteSpace(cfg.Host) ? "(absent)" : cfg.Host));
|
||||
Console.WriteLine("SMTP PORT = " + (string.IsNullOrWhiteSpace(cfg.Port) ? "(absent)" : cfg.Port));
|
||||
Console.WriteLine("SMTP SECURITY MODE = " + cfg.Cryptage switch { 1 => "AUCUN", 2 => "TLS/STARTTLS", 3 => "SSL", _ => $"INCONNU({cfg.Cryptage})" });
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cfg.SMdpStandard))
|
||||
{
|
||||
Console.WriteLine("REAL STANDARD PASSWORD DECRYPT = NOT TESTED (sMdpStandard absent)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
return TestSingle(decryptor, cfg, send);
|
||||
|
||||
// ============================================================
|
||||
|
||||
static int RunAudit(string connStr, string db, SmtpPasswordStandardDecryptor decryptor)
|
||||
{
|
||||
Console.WriteLine("DATABASE = " + db);
|
||||
Console.WriteLine("IS OMIKA DATABASE = " + (db.Equals("OMIKA_TEST", StringComparison.OrdinalIgnoreCase) ? "YES" : "NO"));
|
||||
|
||||
var rows = new List<(string Code, string IdUser, string? Valeur)>();
|
||||
try
|
||||
{
|
||||
using var conn = new OleDbConnection(connStr);
|
||||
conn.Open();
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT PU_Code, PU_IDUtilisateur, PU_Valeur FROM ParametreUtilisateur WHERE PU_Code LIKE 'EMAIL_ENVOI%'";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
string c = (reader.GetValue(0)?.ToString() ?? "").Trim();
|
||||
string i = (reader.GetValue(1)?.ToString() ?? "").Trim();
|
||||
string? v = reader.IsDBNull(2) ? null : reader.GetValue(2)?.ToString();
|
||||
rows.Add((c, i, v));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("QUERY FAILED = " + ex.GetType().Name);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int emailEnvoi = 0, emailEnvoiSoc = 0, emailEnvoiPerso = 0;
|
||||
int invalidJson = 0, legacyBinary = 0, emptyValue = 0;
|
||||
int withMdp = 0, withStd = 0, withoutStd = 0, emptyStd = 0;
|
||||
int base64Valid = 0, base64Invalid = 0;
|
||||
int decryptPass = 0, decryptFail = 0, plaintextEmpty = 0;
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
if (row.Code == "EMAIL_ENVOI") emailEnvoi++;
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(row.Code, @"^EMAIL_ENVOI_\d+$")) emailEnvoiSoc++;
|
||||
else if (row.Code.Contains("Perso")) emailEnvoiPerso++;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(row.Valeur)) { emptyValue++; continue; }
|
||||
|
||||
var configs = ExtractAll(row.Valeur);
|
||||
if (configs is null)
|
||||
{
|
||||
// Non-JSON : legacy/binaire ou JSON invalide.
|
||||
bool looksBinary = row.Valeur.Any(c => c < 32 && c != '\t' && c != '\n' && c != '\r');
|
||||
bool startsJson = row.Valeur.TrimStart().StartsWith('{') || row.Valeur.TrimStart().StartsWith('[');
|
||||
if (startsJson && !looksBinary) invalidJson++;
|
||||
else legacyBinary++;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var cfg in configs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cfg.SMdp)) withMdp++;
|
||||
if (!string.IsNullOrWhiteSpace(cfg.SMdpStandard))
|
||||
{
|
||||
withStd++;
|
||||
byte[]? payload = TryBase64(cfg.SMdpStandard);
|
||||
if (payload is null) base64Invalid++;
|
||||
else
|
||||
{
|
||||
base64Valid++;
|
||||
try
|
||||
{
|
||||
string pt = decryptor.Decrypt(cfg.SMdpStandard);
|
||||
decryptPass++;
|
||||
if (string.IsNullOrEmpty(pt)) plaintextEmpty++;
|
||||
}
|
||||
catch (SmtpStandardPasswordException) { decryptFail++; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
withoutStd++;
|
||||
if (cfg.SMdpStandard is not null) emptyStd++; // champ présent mais vide
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("TOTAL EMAIL CONFIGS = " + rows.Count);
|
||||
Console.WriteLine("EMAIL_ENVOI = " + emailEnvoi);
|
||||
Console.WriteLine("EMAIL_ENVOI_<idSoc> = " + emailEnvoiSoc);
|
||||
Console.WriteLine("EMAIL_ENVOI_Perso_N = " + emailEnvoiPerso);
|
||||
Console.WriteLine("EMPTY VALUE = " + emptyValue);
|
||||
Console.WriteLine("INVALID JSON = " + invalidJson);
|
||||
Console.WriteLine("LEGACY/BINARY = " + legacyBinary);
|
||||
Console.WriteLine("CONFIGS WITH sMdp = " + withMdp);
|
||||
Console.WriteLine("CONFIGS WITH sMdpStandard = " + withStd);
|
||||
Console.WriteLine("CONFIGS WITHOUT sMdpStandard= " + withoutStd);
|
||||
Console.WriteLine("CONFIGS WITH EMPTY sMdpStandard = " + emptyStd);
|
||||
Console.WriteLine("STANDARD BASE64 VALID = " + (base64Valid > 0 ? (base64Invalid > 0 ? "PARTIAL" : "YES") : "NOT AVAILABLE"));
|
||||
Console.WriteLine("REAL OMIKA CONFIGS TESTED = " + (base64Valid + base64Invalid));
|
||||
Console.WriteLine("DECRYPT PASS = " + decryptPass);
|
||||
Console.WriteLine("DECRYPT FAIL = " + decryptFail);
|
||||
Console.WriteLine("PLAINTEXT EMPTY = " + plaintextEmpty);
|
||||
Console.WriteLine("LEGACY sMdp USED BY OMIKA = NO (audit read-only, sMdp non déchiffré)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int TestSingle(SmtpPasswordStandardDecryptor decryptor, SmtpConfig cfg, bool send)
|
||||
{
|
||||
byte[] payload;
|
||||
try { payload = Convert.FromBase64String(cfg.SMdpStandard!.Trim()); }
|
||||
catch { Console.WriteLine("STANDARD BASE64 VALID = NO"); return 7; }
|
||||
int ctLen = payload.Length - 16;
|
||||
Console.WriteLine("STANDARD BASE64 VALID = YES");
|
||||
Console.WriteLine($"PAYLOAD LENGTH = {payload.Length}");
|
||||
Console.WriteLine("IV LENGTH = 16");
|
||||
Console.WriteLine($"CIPHERTEXT LENGTH = {ctLen}");
|
||||
Console.WriteLine("CIPHERTEXT BLOCK VALID = " + (ctLen > 0 && ctLen % 16 == 0 ? "YES" : "NO"));
|
||||
|
||||
string plaintext;
|
||||
try
|
||||
{
|
||||
plaintext = decryptor.Decrypt(cfg.SMdpStandard);
|
||||
Console.WriteLine("REAL STANDARD PASSWORD DECRYPT = PASS");
|
||||
Console.WriteLine("PLAINTEXT EMPTY = " + (string.IsNullOrEmpty(plaintext) ? "YES" : "NO"));
|
||||
Console.WriteLine($"PLAINTEXT LENGTH = {plaintext.Length}");
|
||||
}
|
||||
catch (SmtpStandardPasswordException ex)
|
||||
{
|
||||
Console.WriteLine($"REAL STANDARD PASSWORD DECRYPT = FAIL ({ex.LogCode})");
|
||||
return 8;
|
||||
}
|
||||
|
||||
Console.WriteLine("LEGACY sMdp USED BY OMIKA = NO");
|
||||
Console.WriteLine("PASSWORD LOGGED = NO");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cfg.Host)) { Console.WriteLine("SMTP CONNECT = NOT TESTED (host absent)"); return 9; }
|
||||
|
||||
int smtpPort = int.TryParse(cfg.Port, out int p) ? p : 25;
|
||||
SecureSocketOptions secure = cfg.Cryptage switch
|
||||
{
|
||||
2 => SecureSocketOptions.StartTls,
|
||||
>= 3 => SecureSocketOptions.SslOnConnect,
|
||||
_ => SecureSocketOptions.None
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
client.Connect(cfg.Host, smtpPort, secure);
|
||||
Console.WriteLine("SMTP CONNECT = PASS");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cfg.Login))
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Authenticate(cfg.Login, plaintext);
|
||||
Console.WriteLine("SMTP AUTH = PASS");
|
||||
Console.WriteLine("PASSWORD SOURCE = sMdpStandard");
|
||||
if (send)
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(MailboxAddress.Parse(cfg.Login));
|
||||
message.To.Add(MailboxAddress.Parse(cfg.Login));
|
||||
message.Subject = "Omika SMTP validation test";
|
||||
message.Body = new TextPart("plain") { Text = "Test automatique Omika de validation SMTP (sMdpStandard)." };
|
||||
client.Send(message);
|
||||
Console.WriteLine("SMTP SEND = PASS");
|
||||
Console.WriteLine("MESSAGE ACCEPTED BY SMTP SERVER = YES");
|
||||
}
|
||||
else Console.WriteLine("SMTP SEND = NOT TESTED (--send non fourni)");
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
Console.WriteLine("SMTP AUTH = FAIL");
|
||||
Console.WriteLine("AUTH ERROR TYPE = " + ex.GetType().Name);
|
||||
Console.WriteLine("AUTH ERROR MESSAGE SANITIZED = " + Sanitize(ex.Message, cfg.Login));
|
||||
}
|
||||
}
|
||||
else Console.WriteLine("SMTP AUTH = NOT TESTED (login absent)");
|
||||
|
||||
try { client.Disconnect(true); } catch { }
|
||||
}
|
||||
catch (SslHandshakeException ex)
|
||||
{
|
||||
Console.WriteLine("SMTP CONNECT = FAIL");
|
||||
Console.WriteLine("ERROR TYPE = " + ex.GetType().Name);
|
||||
Console.WriteLine("ERROR MESSAGE SANITIZED = " + Sanitize(ex.Message, cfg.Login));
|
||||
}
|
||||
catch (System.Net.Sockets.SocketException ex)
|
||||
{
|
||||
Console.WriteLine("SMTP CONNECT = FAIL");
|
||||
Console.WriteLine("ERROR TYPE = " + ex.GetType().Name);
|
||||
Console.WriteLine("ERROR MESSAGE SANITIZED = " + Sanitize(ex.Message, cfg.Login));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("SMTP CONNECT = FAIL");
|
||||
Console.WriteLine("ERROR TYPE = " + ex.GetType().Name);
|
||||
Console.WriteLine("ERROR MESSAGE SANITIZED = " + Sanitize(ex.Message, cfg.Login));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static byte[]? TryBase64(string s)
|
||||
{
|
||||
try { return Convert.FromBase64String(s.Trim()); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
static SmtpConfig? ExtractFirst(string json)
|
||||
{
|
||||
var all = ExtractAll(json);
|
||||
return all is { Count: > 0 } ? all[0] : null;
|
||||
}
|
||||
|
||||
static List<SmtpConfig>? ExtractAll(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
using JsonDocument doc = JsonDocument.Parse(json);
|
||||
JsonElement root = doc.RootElement;
|
||||
var result = new List<SmtpConfig>();
|
||||
|
||||
IEnumerable<JsonElement> elements = root.ValueKind == JsonValueKind.Array
|
||||
? root.EnumerateArray()
|
||||
: [root];
|
||||
|
||||
foreach (JsonElement element in elements)
|
||||
{
|
||||
if (!element.TryGetProperty("SMTP", out JsonElement smtp) || smtp.ValueKind != JsonValueKind.Object)
|
||||
continue;
|
||||
var cfg = new SmtpConfig();
|
||||
if (smtp.TryGetProperty("sLogin", out JsonElement e) && e.ValueKind == JsonValueKind.String) cfg.Login = e.GetString();
|
||||
if (smtp.TryGetProperty("sMdp", out e) && e.ValueKind == JsonValueKind.String) cfg.SMdp = e.GetString();
|
||||
if (smtp.TryGetProperty("sMdpStandard", out e) && e.ValueKind == JsonValueKind.String) cfg.SMdpStandard = e.GetString();
|
||||
if (smtp.TryGetProperty("sAdresseServeur", out e) && e.ValueKind == JsonValueKind.String) cfg.Host = e.GetString();
|
||||
if (smtp.TryGetProperty("sPortServeur", out e) && e.ValueKind == JsonValueKind.String) cfg.Port = e.GetString();
|
||||
if (smtp.TryGetProperty("nCryptage", out e) && e.ValueKind == JsonValueKind.Number) cfg.Cryptage = e.GetInt32();
|
||||
result.Add(cfg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
|
||||
static string Sanitize(string message, string? login)
|
||||
{
|
||||
string s = message ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(login)) s = s.Replace(login, "<login>");
|
||||
s = System.Text.RegularExpressions.Regex.Replace(s, "(?i)(password|pwd|sMdpStandard|sMdp)\\s*[:=]\\s*\\S+", "$1=<redacted>");
|
||||
return s.Length > 200 ? s[..200] : s;
|
||||
}
|
||||
|
||||
sealed class SmtpConfig
|
||||
{
|
||||
public string? Login;
|
||||
public string? SMdp;
|
||||
public string? SMdpStandard;
|
||||
public string? Host;
|
||||
public string? Port;
|
||||
public int Cryptage;
|
||||
}
|
||||
Reference in New Issue
Block a user