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. // [--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(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 [--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_ = " + 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? ExtractAll(string json) { try { using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; var result = new List(); IEnumerable 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, ""); s = System.Text.RegularExpressions.Regex.Replace(s, "(?i)(password|pwd|sMdpStandard|sMdp)\\s*[:=]\\s*\\S+", "$1="); 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; }