@echo off
setlocal EnableExtensions
chcp 65001 >nul
title FDA kontrola DNS (SmartEmailing)

rem Windows varianta fda_check.sh. Domeny lze zadat jako parametry
rem (fda_check.bat smartemailing.cz david@smartemailing.cz), jinak se skript zepta.
rem Statistiku vyuziti ("Dejvova analytika") vypnete: fda_check.bat --no-analytics

if "%~1"=="/?" goto :help
if /i "%~1"=="-h" goto :help
if /i "%~1"=="--help" goto :help

set "FDA_DOMAINS=%*"
if not defined FDA_DOMAINS (
  echo.
  echo === FDA kontrola DNS ===
  set /p "FDA_DOMAINS=Zadejte domenu nebo e-mail (vice oddelte mezerou): "
)
if not defined FDA_DOMAINS (
  echo.
  echo Nezadali jste zadnou domenu. Konec.
  echo.
  pause
  exit /b 1
)

powershell -NoProfile -ExecutionPolicy Bypass -Command "$p='%~f0'; $c=[IO.File]::ReadAllText($p); $m=[char]35+'FDAPS'+[char]35; $i=$c.IndexOf($m); iex $c.Substring($i)"
set "EC=%ERRORLEVEL%"
echo.
pause
exit /b %EC%

:help
echo Pouziti: fda_check.bat [volby] ^<domena^|e-mail^> [dalsi ...]
echo.
echo   --no-analytics   neposilat statistiku vyuziti ("Dejvova analytika")
echo   --update         stahne a nainstaluje novejsi verzi
echo   --version        zobrazi verzi programu
echo   /?, -h, --help   tato napoveda
echo.
echo Priklady:
echo   fda_check.bat smartemailing.cz
echo   fda_check.bat david@smartemailing.cz
echo.
echo Bez parametru se skript na domenu zepta.
echo.
pause
exit /b 0

#FDAPS#
# --- PowerShell (spousti se pres iex z tohoto .bat) ---
$ErrorActionPreference = 'Stop'
$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new()
$DocUrl = 'https://docs.google.com/document/d/16Nx-OPpr_s6hovHocMynrgFmHo3vaxBf5_msupQSQpc/edit'
$Version = '1.7'
$VersionDate = '2026-09-24'
$script:SpfBudget = 40
$script:SpfQ = 0
$script:SpfTrunc = $false
$UpdateBase = if ($env:FDA_UPDATE_URL) { $env:FDA_UPDATE_URL } else { 'https://fda.smartemailing.net/update' }

# -- self-update (z vlastniho serveru, HTTPS) --
try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {}
function Get-RemoteMeta($timeoutSec) {
  try { return Invoke-RestMethod -Uri "$UpdateBase/version.json" -TimeoutSec $timeoutSec -ErrorAction Stop } catch { return $null }
}
function VerGt($a, $b) {
  if ("$a" -eq "$b") { return $false }
  try {
    $pa = ("$a" -split '\.'); $pb = ("$b" -split '\.')
    $n = [Math]::Max($pa.Count, $pb.Count)
    for ($i = 0; $i -lt $n; $i++) {
      $na = if ($i -lt $pa.Count) { [int]$pa[$i] } else { 0 }
      $nb = if ($i -lt $pb.Count) { [int]$pb[$i] } else { 0 }
      if ($na -gt $nb) { return $true }
      if ($na -lt $nb) { return $false }
    }
  } catch { }
  return $false
}
function Check-Update {
  $meta = Get-RemoteMeta 2
  if (-not $meta) { return }
  if ($meta.version -and (VerGt $meta.version $Version)) {
    Write-Host ''
    Write-Host "  i K dispozici je novejsi verze $($meta.version) (mate $Version) - aktualizace: fda_check.bat --update" -ForegroundColor DarkGray
  }
}
function Report-Update {
  $meta = Get-RemoteMeta 5
  if (-not $meta) { Write-Host "Server s aktualizacemi je nedostupny (mate verzi $Version)."; return }
  if ($meta.version -and (VerGt $meta.version $Version)) { Write-Host "K dispozici je verze $($meta.version) (mate $Version). Aktualizace: fda_check.bat --update" }
  else { Write-Host "Mate aktualni verzi ($Version)." }
}
function Invoke-SelfUpdate($selfPath) {
  if (-not $selfPath) { Write-Host "Nelze zjistit cestu k .bat souboru."; return }
  $meta = Get-RemoteMeta 5
  if (-not $meta) { Write-Host "Server s aktualizacemi je nedostupny."; return }
  if (-not (VerGt $meta.version $Version)) { Write-Host "Mate aktualni verzi ($Version)."; return }
  $tmp = [IO.Path]::GetTempFileName()
  try { Invoke-WebRequest -Uri "$UpdateBase/fda_check.bat" -OutFile $tmp -TimeoutSec 20 -UseBasicParsing -ErrorAction Stop }
  catch { Write-Host "Stazeni selhalo."; Remove-Item $tmp -ErrorAction SilentlyContinue; return }
  if ($meta.sha256_bat) {
    $got = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower()
    if ($got -ne "$($meta.sha256_bat)".ToLower()) { Write-Host "Kontrolni soucet nesouhlasi - aktualizace zrusena."; Remove-Item $tmp -ErrorAction SilentlyContinue; return }
  }
  $head = "" + (Get-Content -Path $tmp -TotalCount 1 -ErrorAction SilentlyContinue)
  if ($head -notmatch '@echo off') { Write-Host "Stazeny soubor nevypada jako .bat - zruseno."; Remove-Item $tmp -ErrorAction SilentlyContinue; return }
  try { Copy-Item -Path $tmp -Destination $selfPath -Force -ErrorAction Stop; Write-Host "Aktualizovano na verzi $($meta.version). Spuste prosim znovu." }
  catch { Write-Host "Nepodarilo se prepsat soubor (zkuste prava)." }
  Remove-Item $tmp -ErrorAction SilentlyContinue
}

$Dns = if ($env:FDA_DNS) { $env:FDA_DNS } else { '8.8.8.8' }

# anonymní statistika do Matomo (vypnout: FDA_ANALYTICS=0)
$MatomoUrl = 'https://analytics.omnisys.cz/matomo.php'
$MatomoSite = '3'
function Track($domain, $result, $errCount) {
  if ([string]::IsNullOrEmpty($MatomoUrl)) { return }
  if ("$env:FDA_ANALYTICS" -match '^(0|off|no|false)$') { return }
  try {
    $uid = "$env:USERNAME@$env:COMPUTERNAME"
    $body = @{
      idsite = $MatomoSite; rec = '1'; send_image = '0'
      e_c = 'fda_check'; e_a = $result; e_n = $domain; e_v = "$errCount"
      uid = $uid; url = "https://fda-check.local/check/$domain"
    }
    Invoke-RestMethod -Uri $MatomoUrl -Method Post -Body $body -TimeoutSec 2 -ErrorAction SilentlyContinue | Out-Null
  } catch { }
}

$raw = $env:FDA_DOMAINS
if ([string]::IsNullOrWhiteSpace($raw)) {
  $raw = Read-Host 'Zadejte doménu nebo e-mail (více oddělte mezerou)'
}
$Domains = @($raw -split '[\s,;]+' | Where-Object { $_ -ne '' })
# verze
if ($Domains | Where-Object { $_ -match '^(--version|-V)$' }) {
  Write-Host "FDA check $Version ($VersionDate)"; exit 0
}
if ($Domains | Where-Object { $_ -match '^(--update|-u)$' }) { Invoke-SelfUpdate $p; exit 0 }
if ($Domains | Where-Object { $_ -match '^--check$' }) { Report-Update; exit 0 }
# vypnutí statistiky využití (Dejvova analytika): --no-analytics / --dejv-off / -A
if ($Domains | Where-Object { $_ -match '^(--no-analytics|--dejv-off|-A)$' }) {
  $env:FDA_ANALYTICS = '0'
  $Domains = @($Domains | Where-Object { $_ -notmatch '^(--no-analytics|--dejv-off|-A)$' })
}
if ($Domains.Count -eq 0) { Write-Host 'Nezadali jste žádnou doménu.' -ForegroundColor Red; exit 1 }

# záznamy dle vzoru FDA
$Records = @(
  @{ label='From doména (bounce / MX)';  name='mkt-smartemailing';                type='MX';    expect='bounce.smartemailing-mta.com';   why='Return-Path pro bounce – na izolované subdoméně sbírá hlášení o nedoručení a zarovnává Return-Path s doménou odesílatele (FDA). Nezasahuje do firemní pošty.' }
  @{ label='DKIM klíč 1';                name='k1-mkt-smartemailing._domainkey';  type='CNAME'; expect='mkt-k1.dkim.smartemailing-mta.com'; why='DKIM podpis – příjemce ověří, že e-mail pochází z autorizované domény a nebyl cestou změněn (d= zarovnané s doménou).' }
  @{ label='DKIM klíč 2';                name='k2-mkt-smartemailing._domainkey';  type='CNAME'; expect='mkt-k2.dkim.smartemailing-mta.com'; why='Druhý DKIM klíč pro rotaci/zálohu – spolehlivé podepisování e-mailů.' }
  @{ label='DKIM klíč 3';                name='k3-mkt-smartemailing._domainkey';  type='CNAME'; expect='mkt-k3.dkim.smartemailing-mta.com'; why='Třetí DKIM klíč pro rotaci/zálohu – spolehlivé podepisování e-mailů.' }
  @{ label='Click doména (odkazy)';      name='lnk-smartemailing';                type='CNAME'; expect='customers.smartemailing.cz';        why='Odkazy v e-mailu vedou přes vaši doménu a https – vyšší důvěra příjemců, lepší měřitelnost, nižší riziko phishingu.' }
  @{ label='SPF odesílací subdomény';    name='mkt-smartemailing';                type='TXT';   expect='include:spf.smartemailing.cz';    why='SPF autorizuje servery SmartEmailingu odesílat za subdoménu mkt-smartemailing.' }
  @{ label='SPF hlavní domény';          name='@';                                type='TXT';   expect='include:spf.smartemailing.cz';    why='SPF na hlavní doméně autorizuje odesílání a podporuje zarovnání (alignment). Pozn.: pokud doménu pro SE nepoužíváte, může být SPF vlastní.' }
)

$Sep = ('-' * 70)

# DNS dotaz -> @{ code; values; ttl }  (code: 0 = ok, DNS RCODE, -1 = nedostupné)
function DnsGet($server, $name, $type) {
  try {
    $r = Resolve-DnsName -Name $name -Type $type -Server $server -DnsOnly -QuickTimeout -ErrorAction Stop
  } catch {
    $code = -1
    $ex = $_.Exception
    if ($ex) {
      foreach ($pn in 'NativeErrorCode','ErrorCode') {
        if ($ex.PSObject.Properties[$pn] -and $ex.$pn) { $code = [int]$ex.$pn; break }
      }
    }
    return @{ code = $code; values = @(); ttl = $null }
  }
  $recs = @($r | Where-Object { "$($_.Type)" -eq $type })
  $vals = @(); $ttl = $null
  foreach ($x in $recs) {
    switch ($type) {
      'MX'    { $vals += ("{0} {1}" -f $x.Preference, $x.NameExchange) }
      'CNAME' { $vals += "$($x.NameHost)" }
      'NS'    { $vals += "$($x.NameHost)" }
      'TXT'   { $vals += ((@($x.Strings)) -join '') }
      'A'     { $vals += "$($x.IPAddress)" }
      'AAAA'  { $vals += "$($x.IPAddress)" }
      default { $vals += "$x" }
    }
    if ($null -eq $ttl) { $ttl = $x.TTL }
  }
  return @{ code = 0; values = $vals; ttl = $ttl }
}

# server odpověděl? (i NXDOMAIN/NODATA/SERVFAIL/REFUSED = odpověděl) – jinak nedostupný
function ServerResponds($server, $probe) {
  $g = DnsGet $server $probe 'SOA'
  if ($g.code -eq 0) { return $true }
  return (@(9003, 9501, 9002, 9005) -contains $g.code)
}

# všechny NS nejbližší zóny (jako název), s IP pro dotazování
function NsAll($zone) {
  $d = $zone
  while ($true) {
    $g = DnsGet $Dns $d 'NS'
    if ($g.values.Count -gt 0) { return @($g.values | ForEach-Object { $_.TrimEnd('.') } | Select-Object -First 8) }
    if (($d -split '\.').Count -gt 2) { $d = $d.Substring($d.IndexOf('.') + 1) } else { break }
  }
  return @()
}
function ResolveIp($name) {
  $a = DnsGet $Dns $name 'A'
  if ($a.values.Count -gt 0) { return $a.values[0] }
  return $name
}

# duplicita SPF -> vrací problém (hashtable) nebo $null
function DupSpfProblem($name, $lbl, $srv) {
  $cnt = @((DnsGet $srv $name 'TXT').values | Where-Object { $_ -match 'v=spf1' }).Count
  if ($cnt -gt 1) {
    return [ordered]@{ label = "$lbl — DUPLICITA SPF"; fqdn = $name; type = 'TXT'; expect = 'právě jeden v=spf1'; why = 'Na jednom jménu smí být JEN JEDEN SPF. Víc záznamů v=spf1 SPF zneplatní (PermError) a autentizace selže – slučte je do jednoho.'; found = "nalezeno ${cnt}x v=spf1" }
  }
  return $null
}

# -- pocet DNS-lookup termu v SPF (RFC 7208, limit 10) --
function SpfBudgetOk {
  $script:SpfQ++
  if ($script:SpfQ -le $script:SpfBudget) { return $true }
  $script:SpfTrunc = $true; return $false
}
function SpfCount($domain, $depth) {
  if ($depth -gt 10 -or $script:SpfTrunc) { return 0 }
  if (-not (SpfBudgetOk)) { return 0 }
  $g = DnsGet $Dns $domain 'TXT'
  $rec = @($g.values | Where-Object { $_ -match 'v=spf1' } | Select-Object -First 1)
  if (-not $rec) { return 0 }
  $rec = "$rec"
  $hasAll = ($rec -match '(^|\s)[-~+?]?all(\s|$)')
  $total = 0
  foreach ($t in ($rec -split '\s+')) {
    if ($t -match '^(a|mx)(:|/|$)') { $total += 1 }
    elseif ($t -match '^ptr(:|$)') { $total += 1 }
    elseif ($t -match '^exists:') { $total += 1 }
    elseif ($t -match '^include:(\S+)$') { $total += 1 + (SpfCount $Matches[1] ($depth + 1)) }
    elseif ($t -match '^redirect=(\S+)$') { if (-not $hasAll) { $total += 1 + (SpfCount $Matches[1] ($depth + 1)) } }
  }
  return $total
}
function SpfLimitProblem($name, $lbl) {
  $g = DnsGet $Dns $name 'TXT'
  $rec = @($g.values | Where-Object { $_ -match 'v=spf1' } | Select-Object -First 1)
  if (-not $rec) { return $null }
  $script:SpfTrunc = $false
  $cnt = SpfCount $name 0
  if ($script:SpfTrunc) {
    return @{ kind = 'problem'; label = "$lbl — SPF limit"; fqdn = $name; type = 'TXT'; expect = '<= 10 DNS lookupu'; why = 'SPF strom je příliš rozsáhlý – překračuje limit 10 DNS lookupů (PermError) a SPF přestane platit celé. Zredukujte include: nebo použijte SPF flattening.'; found = 'více než 10 lookupů (procházení přerušeno)' }
  }
  if ($cnt -gt 10) {
    return @{ kind = 'problem'; label = "$lbl — SPF limit"; fqdn = $name; type = 'TXT'; expect = '≤ 10 DNS lookupů'; why = "SPF vyžaduje $cnt DNS lookupů (limit je 10). Nad 10 je to PermError a SPF přestane platit celé – i pro SmartEmailing. Zredukujte include: nebo použijte SPF flattening."; found = "$cnt lookupů" }
  } elseif ($cnt -ge 8) {
    return @{ kind = 'warn'; text = "${lbl}: SPF má $cnt/10 DNS lookupů – blízko limitu, další include: může SPF rozbít (PermError)." }
  }
  return $null
}

# -- je nas include ve stromu SPF? "direct" | "<include-pres-ktery>" | "" --
function SpfTreeHas($domain, $target, $depth) {
  if ($depth -gt 10 -or $script:SpfTrunc) { return $false }
  if (-not (SpfBudgetOk)) { return $false }
  $g = DnsGet $Dns $domain 'TXT'
  $rec = @($g.values | Where-Object { $_ -match 'v=spf1' } | Select-Object -First 1)
  if (-not $rec) { return $false }
  $rec = "$rec"
  $hasAll = ($rec -match '(^|\s)[-~+?]?all(\s|$)')
  foreach ($t in ($rec -split '\s+')) {
    if ($t -match '^redirect=' -and $hasAll) { continue }
    if ($t -match '^(?:include:|redirect=)(\S+)$') {
      $tgt = $Matches[1]
      if ($tgt -eq $target) { return $true }
      if (SpfTreeHas $tgt $target ($depth + 1)) { return $true }
    }
  }
  return $false
}
function SpfFindInclude($domain, $target) {
  $g = DnsGet $Dns $domain 'TXT'
  $rec = @($g.values | Where-Object { $_ -match 'v=spf1' } | Select-Object -First 1)
  if (-not $rec) { return '' }
  $rec = "$rec"
  $hasAll = ($rec -match '(^|\s)[-~+?]?all(\s|$)')
  foreach ($t in ($rec -split '\s+')) {
    if ($t -match '^redirect=' -and $hasAll) { continue }
    if ($t -match '^(?:include:|redirect=)(\S+)$') {
      $tgt = $Matches[1]
      if ($tgt -eq $target) { return 'direct' }
      if (SpfTreeHas $tgt $target 1) { return $tgt }
    }
  }
  return ''
}

# ---------- vyhodnocení jednoho záznamu proti serveru ----------
function CheckRec($server, $fqdn, $type, $expect) {
  if (-not $server) { return @{ status = 'na'; actual = ''; ttl = $null } }
  $g = DnsGet $server $fqdn $type
  $vals = $g.values
  if ($type -eq 'CNAME' -and $vals.Count -eq 0) { $g2 = DnsGet $server $fqdn 'A'; $vals = $g2.values }
  $actual = ''
  if ($type -eq 'TXT') {
    if ($expect -match 'spf') {
      $actual = @($vals | Where-Object { $_ -match 'v=spf1' } | Select-Object -First 1)
      if ($actual) { $actual = "$actual" } else { $actual = '' }
    } else { $actual = ($vals -join ' ') }
  } else { $actual = ($vals -join ' ') }

  $status = 'err'
  if ($g.code -eq 9005) { $status = 'err'; if (-not $actual) { $actual = 'REFUSED' } }
  elseif ($actual -and ($actual -match [regex]::Escape($expect))) { $status = 'ok' }
  elseif (-not $actual) { $status = 'err' }
  else { $status = 'err' }
  return @{ status = $status; actual = $actual; ttl = $g.ttl }
}

# ---------- DMARC (dědí se) ----------
function DmarcWalk($server, $start) {
  if (-not $server) { return @{ status = 'na'; rec = ''; dom = '' } }
  $d = $start
  while ($true) {
    $g = DnsGet $server ("_dmarc." + $d) 'TXT'
    $rec = @($g.values | Where-Object { $_ -match 'v=DMARC1' } | Select-Object -First 1)
    if ($rec) {
      $rec = "$rec"
      return @{ status = 'ok'; rec = $rec; dom = $d }
    }
    if (($d -split '\.').Count -gt 2) { $d = $d.Substring($d.IndexOf('.') + 1) } else { break }
  }
  return @{ status = 'err'; rec = ''; dom = '' }
}

# ---------- barevný výpis ----------
function StatusCell($s, $frac) {
  switch ($s) {
    'ok'      { return @('OK',    'Green') }
    'err'     { return @('CHYBA', 'Red') }
    'partial' { return @($frac,   'Yellow') }
    'na'      { return @('n/a',   'DarkGray') }
    default   { return @('?',     'Gray') }
  }
}
function PrintRow($label, $g, $n, $frac, $ttl, $extra) {
  Write-Host ("  " + $label.PadRight(32)) -NoNewline
  $gc = StatusCell $g ''
  Write-Host ($gc[0].PadRight(10)) -ForegroundColor $gc[1] -NoNewline
  $nc = StatusCell $n $frac
  Write-Host ($nc[0].PadRight(10)) -ForegroundColor $nc[1] -NoNewline
  $note = ''
  if ($g -eq 'err' -and ($n -eq 'ok' -or $n -eq 'partial')) {
    $note = "<- v zóně, čeká na propagaci"; if ($ttl) { $note += " (TTL ${ttl}s)" }
  }
  if ($n -eq 'partial') { $note = "<- nekonzistentní mezi NS ($frac)" }
  if ($g -eq 'ok' -and $n -eq 'err') { $note = "<- global z keše, v zóně chybí" }
  if ($extra) { Write-Host ("  " + $extra) -ForegroundColor DarkGray -NoNewline }
  if ($note) { Write-Host ("  " + $note) -ForegroundColor Yellow } else { Write-Host '' }
}

# ---------- kontrola jedné domény ----------
function CheckDomain($rawDomain) {
  $script:SpfQ = 0; $script:SpfTrunc = $false
  # normalizace na doménu
  $d = $rawDomain
  if ($d.Contains('@')) { $d = $d.Substring($d.LastIndexOf('@') + 1) }
  $d = $d -replace '^[a-z]+://', ''
  $d = ($d -split '/')[0]
  $d = ($d -split ':')[0]
  $d = $d.TrimEnd('.').Trim().ToLower()

  $res = [ordered]@{ domain = $d; ok = 0; prop = 0; err = 0; warn = 0; exit = 0; msg = '' }
  if ([string]::IsNullOrWhiteSpace($d) -or ($d -notmatch '\.')) {
    $res.msg = "'$rawDomain' není platná doména ani e-mail"; $res.exit = 1
    Write-Host ''; Write-Host "  X $($res.msg)." -ForegroundColor Red
    Track $rawDomain 'chyba-vstup' 0
    return $res
  }

  # preflight
  $soa = DnsGet $Dns $d 'SOA'
  if ($soa.code -eq 9003) {
    $res.msg = 'doména neexistuje (NXDOMAIN)'; $res.exit = 3
    Write-Host ''; Write-Host "== $d ==" -ForegroundColor White
    Write-Host "  X Doména neexistuje (NXDOMAIN). Zkontrolujte překlep – FDA záznamy zde nemůžou existovat." -ForegroundColor Red
    Track $d 'chyba-dns' 0
    return $res
  }
  if ($soa.code -eq -1) {
    $res.msg = 'doména bez funkční delegace (NS neodpovídají)'; $res.exit = 3
    Write-Host ''; Write-Host "== $d ==" -ForegroundColor White
    Write-Host "  ! Doména nevrací odpověď – nemá funkční delegaci (nameservery neodpovídají)." -ForegroundColor Yellow
    Track $d 'chyba-dns' 0
    return $res
  }

  # NS zóny + dostupnost (dotazy přes IP)
  $nsNames = NsAll $d
  $NsList = @(); $NsShown = @()
  foreach ($ns in $nsNames) {
    $ip = ResolveIp $ns
    if (ServerResponds $ip $d) { $NsList += $ip; $NsShown += $ns }
  }
  $PrimaryNs = if ($NsList.Count -gt 0) { $NsList[0] } else { $Dns }

  # sběr výsledků
  $okLabels = @(); $propList = @(); $warnList = @(); $problems = @()

  # A/AAAA info
  $a = (DnsGet $Dns $d 'A').values + (DnsGet $Dns $d 'AAAA').values
  $apexA = ($a -join ' ').Trim()

  # vlastní MX domény (ne bounce MX)
  $apexMx = ((DnsGet $Dns $d 'MX').values -join ' ').Trim()
  if ($apexMx -match '^\s*0\s+\.\s*$') { $apexMx = '' }   # null MX (RFC 7505)
  if (-not $apexMx) { $warnList += 'Doména nemá vlastní MX záznam – nepřijímá e-mail (bounce MX pro FDA je zvlášť na mkt-smartemailing).' }

  # NS hlavička
  if ($nsNames.Count -eq 0) {
    $nsHdr = 'nezjištěno'; $warnList += 'Autoritativní NS domény se nepodařilo zjistit – sloupec NS je n/a, řídí se global.'
  } elseif ($NsList.Count -eq 0) {
    $nsHdr = ($nsNames -join ' ') + ' (žádný neodpovídá – NS n/a)'
    $warnList += ('Žádný autoritativní NS (' + ($nsNames -join ' ') + ') neodpovídá – sloupec NS je n/a, řídí se global.')
  } else { $nsHdr = ($NsShown -join ' ') }

  # hlavička
  Write-Host ''
  Write-Host "== FDA kontrola DNS: $d ==" -ForegroundColor White -NoNewline
  Write-Host "  (v$Version, $VersionDate)" -ForegroundColor DarkGray
  if ($apexA) { Write-Host "  web (A/AAAA): $apexA" }
  else { Write-Host "  web (A/AAAA): žádný záznam (doména nemíří na web – pro e-mailing/FDA to nevadí)" -ForegroundColor DarkGray }
  if ($apexMx) { Write-Host "  pošta (MX):   $apexMx" }
  else { Write-Host "  pošta (MX):   žádný vlastní MX (doména nepřijímá e-mail)" -ForegroundColor DarkGray }
  Write-Host "  global DNS: $Dns    autoritativní NS: $nsHdr"
  Write-Host $Sep -ForegroundColor DarkGray
  Write-Host ("  " + 'Záznam'.PadRight(32) + 'global'.PadRight(10) + 'NS'.PadRight(10))
  Write-Host $Sep -ForegroundColor DarkGray

  foreach ($rec in $Records) {
    $fqdn = if ($rec.name -eq '@') { $d } else { "$($rec.name).$d" }
    $gr = CheckRec $Dns $fqdn $rec.type $rec.expect
    $g = $gr.status; $gact = $gr.actual
    $okc = 0; $tot = 0
    foreach ($ns in $NsList) { $tot++; $nr = CheckRec $ns $fqdn $rec.type $rec.expect; if ($nr.status -eq 'ok') { $okc++ } }
    if ($tot -eq 0) { $n = 'na'; $frac = '-' }
    elseif ($okc -eq $tot) { $n = 'ok'; $frac = "$okc/$tot" }
    elseif ($okc -eq 0) { $n = 'err'; $frac = "$okc/$tot" }
    else { $n = 'partial'; $frac = "$okc/$tot" }
    $xnote = ''
    if ($rec.expect -eq 'include:spf.smartemailing.cz' -and (($g -ne 'ok') -or ($n -ne 'ok'))) {
      $src = SpfFindInclude $fqdn 'spf.smartemailing.cz'
      if ($src -and $src -ne 'direct') { $g = 'ok'; $n = 'ok'; $frac = ''; $xnote = "prostrednictvim vnoreneho include ($src)" }
    }
    $ttl = $null
    if ($g -eq 'ok' -or $n -eq 'ok' -or $n -eq 'partial') { $ttl = (CheckRec $PrimaryNs $fqdn $rec.type $rec.expect).ttl }

    PrintRow $rec.label $g $n $frac $ttl $xnote

    # tally (NS = hlavní kritérium)
    if ($n -eq 'ok') {
      if ($g -eq 'ok') { $okLabels += $rec.label } else { $propList += ("$($rec.label) (TTL $(if($ttl){$ttl}else{'?'})s)") }
    } elseif ($n -eq 'partial') {
      $warnList += "$($rec.label): nekonzistentní mezi NS ($frac) – změna se šíří mezi nameservery."
      if ($g -eq 'ok') { $okLabels += $rec.label } else { $propList += ("$($rec.label) (TTL $(if($ttl){$ttl}else{'?'})s)") }
    } elseif ($n -eq 'na') {
      if ($g -eq 'ok') { $okLabels += $rec.label }
      else { $problems += , ([ordered]@{ label = $rec.label; fqdn = $fqdn; type = $rec.type; expect = $rec.expect; why = $rec.why; found = $gact }) }
    } else {
      if ($g -eq 'ok') { $warnList += "$($rec.label): v zóně (autoritativně) chybí, veřejně ještě žije z keše – ověřte, že nebyl omylem smazán." }
      else { $problems += , ([ordered]@{ label = $rec.label; fqdn = $fqdn; type = $rec.type; expect = $rec.expect; why = $rec.why; found = $gact }) }
    }
  }

  # ---------- DMARC ----------
  $gd = DmarcWalk $Dns $d
  $gDstat = $gd.status; $gDrec = $gd.rec; $gDdom = $gd.dom
  $dmz = if ($gDdom) { $gDdom } else { $d }
  $dmNsNames = NsAll $dmz
  $dmNs = @()
  foreach ($ns in $dmNsNames) { $ip = ResolveIp $ns; if (ServerResponds $ip $dmz) { $dmNs += $ip } }
  $dOk = 0; $dTot = 0
  foreach ($ns in $dmNs) { $dTot++; $w = DmarcWalk $ns $dmz; if ($w.status -eq 'ok') { $dOk++ } }
  if ($dTot -eq 0) { $nDstat = 'na'; $dfrac = '-' }
  elseif ($dOk -eq $dTot) { $nDstat = 'ok'; $dfrac = "$dOk/$dTot" }
  elseif ($dOk -eq 0) { $nDstat = 'err'; $dfrac = "$dOk/$dTot" }
  else { $nDstat = 'partial'; $dfrac = "$dOk/$dTot" }
  $dmPrimary = if ($dmNs.Count -gt 0) { $dmNs[0] } else { $Dns }
  $ttlD = $null
  if ($gDstat -eq 'ok' -or $nDstat -eq 'ok' -or $nDstat -eq 'partial') { $ttlD = (DnsGet $dmPrimary ("_dmarc.$dmz") 'TXT').ttl }
  $dnote = ''
  if ($gDdom -and $gDdom -ne $d) { $dnote = "(zděděno z $gDdom)" }
  PrintRow 'DMARC' $gDstat $nDstat $dfrac $ttlD $dnote

  $polWarn = {
    $pol = ''
    if ($gDrec -match 'p=([a-z]+)') { $pol = $matches[1].ToLower() }
    if ($gDrec -and ($pol -eq 'none' -or $pol -eq '')) { $warnList += "DMARC policy p=$(if($pol){$pol}else{'nezadáno'}) – doporučeno p=reject nebo p=quarantine." }
  }
  $ruaWarn = {
    if ($gDrec) {
      $rua = ''
      if (($gDrec -replace '\s','') -match 'rua=([^;]*)') { $rua = $matches[1] }
      if (-not $rua) { $warnList += 'DMARC je v pořádku, ale nemá rua – reporty se neposílají nikam a statistiky v aplikaci SmartEmailing nebudou dostupné.' }
      elseif ($rua -match 'mailto:dmarc\+\d+@([a-z0-9-]+\.)*smartemailing-mta\.com') { }
      elseif ($rua -match '(dmarc\+\d+@smartemailing\.cz)') {
        $warnList += "DMARC používá starší adresu pro reporty ($($matches[1])) – vše funguje, ale nový tvar je dmarc+ID@dmarc.smartemailing-mta.com."
      }
      else {
        $addr = ($rua -replace 'mailto:', '')
        $warnList += "DMARC je v pořádku, ale reporty jdou jinam ($addr) – statistiky v aplikaci SmartEmailing nebudou pro tuto doménu dostupné."
      }
      if ($rua) {
        $addrs = @(($rua -split ',') | ForEach-Object { ($_ -replace '^mailto:','' -replace '!.*$','').ToLower() } | Where-Object { $_ -match '@' })
        $dup = $addrs | Group-Object | Where-Object { $_.Count -gt 1 } | Select-Object -First 1
        if ($dup) { $warnList += "DMARC: adresa pro reporty je v rua uvedena vícekrát ($($dup.Name)) – na funkci to nemá vliv, stačí ji uvést jednou." }
        if ($addrs.Count -gt 2) {
          $sklon = if ($addrs.Count -le 4) { 'adresy' } else { 'adres' }
          $warnList += "DMARC: rua obsahuje $($addrs.Count) $sklon – příjemci mají povinnost podporovat jen dvě, na další nemusí reporty chodit."
        }
        $srcDom = if ($gDdom) { $gDdom } else { $d }
        $sorg = (($srcDom -split '\.') | Select-Object -Last 2) -join '.'
        $seen = @{}; $checked = 0
        foreach ($a in ($addrs | Sort-Object -Unique)) {
          $dest = $a.Split('@')[-1]
          $dorg = (($dest -split '\.') | Select-Object -Last 2) -join '.'
          if ($dorg -eq $sorg -or $dorg -eq 'smartemailing-mta.com' -or $dorg -eq 'smartemailing.cz') { continue }
          if ($seen.ContainsKey($dorg)) { continue }
          $seen[$dorg] = $true
          $checked++; if ($checked -gt 3) { break }
          $auth = DnsGet $Dns "$srcDom._report._dmarc.$dest" 'TXT'
          if (-not (@($auth.values) -match 'v=DMARC1')) {
            $warnList += "DMARC: reporty na $a se nejspíš nedoručují – na doméně $dest chybí autorizační TXT záznam $srcDom._report._dmarc.$dest (v=DMARC1)."
          }
        }
      }
    }
  }
  $dmProblem = {
    $problems += , ([ordered]@{ label = 'DMARC'; fqdn = "_dmarc.$d"; type = 'TXT'; expect = 'v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc+ID@dmarc.smartemailing-mta.com'; why = 'DMARC řídí kontrolu shody (alignment) a kam jdou reporty. Záznam se dědí – hledáno i na nadřazených doménách.'; found = '' }) }
  if ($nDstat -eq 'ok') {
    if ($gDstat -eq 'ok') { $okLabels += 'DMARC'; . $polWarn; . $ruaWarn } else { $propList += ("DMARC (TTL $(if($ttlD){$ttlD}else{'?'})s)") }
  } elseif ($nDstat -eq 'partial') {
    $warnList += "DMARC: nekonzistentní mezi NS ($dfrac)."
    if ($gDstat -eq 'ok') { $okLabels += 'DMARC'; . $polWarn; . $ruaWarn } else { $propList += ("DMARC (TTL $(if($ttlD){$ttlD}else{'?'})s)") }
  } elseif ($nDstat -eq 'na') {
    if ($gDstat -eq 'ok') { $okLabels += 'DMARC'; . $polWarn; . $ruaWarn } else { . $dmProblem }
  } else {
    if ($gDstat -eq 'ok') { $warnList += 'DMARC: v zóně chybí, veřejně ještě z keše – ověřte, že nebyl smazán.' } else { . $dmProblem }
  }

  # ---------- duplicity SPF / DMARC (ground truth = primární NS) ----------
  $sp1 = DupSpfProblem ("mkt-smartemailing.$d") 'SPF odesílací subdomény' $PrimaryNs; if ($sp1) { $problems += , $sp1 }
  $sp2 = DupSpfProblem $d 'SPF hlavní domény' $PrimaryNs; if ($sp2) { $problems += , $sp2 }
  foreach ($sl in @((SpfLimitProblem $d 'SPF hlavní domény'), (SpfLimitProblem ("mkt-smartemailing.$d") 'SPF odesílací subdomény'))) {
    if ($sl) {
      if ($sl.kind -eq 'problem') { $problems += , ([ordered]@{ label = $sl.label; fqdn = $sl.fqdn; type = $sl.type; expect = $sl.expect; why = $sl.why; found = $sl.found }) }
      else { $warnList += $sl.text }
    }
  }
  $cntD = @((DnsGet $dmPrimary ("_dmarc.$dmz") 'TXT').values | Where-Object { $_ -match 'v=DMARC1' }).Count
  if ($cntD -gt 1) { $problems += , ([ordered]@{ label = 'DMARC — DUPLICITA'; fqdn = "_dmarc.$dmz"; type = 'TXT'; expect = 'právě jeden v=DMARC1'; why = 'Víc DMARC záznamů na jednom _dmarc je neplatné – přijímací servery je ignorují. Nechte jen jeden.'; found = "nalezeno ${cntD}x v=DMARC1" }) }

  # ---------- souhrn ----------
  $res.ok = $okLabels.Count; $res.prop = $propList.Count; $res.err = $problems.Count; $res.warn = $warnList.Count
  $res.exit = if ($problems.Count -gt 0) { 2 } else { 0 }

  Write-Host $Sep -ForegroundColor DarkGray
  Write-Host "  SOUHRN   " -NoNewline
  Write-Host "$($okLabels.Count)x OK   " -ForegroundColor Green -NoNewline
  Write-Host "$($propList.Count)x čeká na propagaci   " -ForegroundColor Yellow -NoNewline
  Write-Host "$($problems.Count)x CHYBA   " -ForegroundColor Red -NoNewline
  Write-Host "$($warnList.Count)x upozornění" -ForegroundColor Yellow
  Write-Host $Sep -ForegroundColor DarkGray

  if ($okLabels.Count -gt 0) {
    Write-Host "  [OK] Správně nastaveno: " -ForegroundColor Green -NoNewline
    Write-Host ($okLabels -join ', '); Write-Host ''
  }
  if ($propList.Count -gt 0) {
    Write-Host "  [OK] Nastaveno správně, čeká jen na propagaci " -ForegroundColor Yellow -NoNewline
    Write-Host ("(v zóně OK, svět ještě nevidí – počkejte dle TTL): " + ($propList -join ', '))
    Write-Host ''
  }
  if ($problems.Count -gt 0) {
    Write-Host "  [X] K opravě:" -ForegroundColor Red
    foreach ($p in $problems) {
      Write-Host "     * $($p.label)  ($($p.fqdn), $($p.type))" -ForegroundColor White
      if (-not $p.found) { Write-Host "         chybí:     $($p.expect)" }
      else { Write-Host "         nalezeno:  $($p.found)"; Write-Host "         očekáváno: $($p.expect)" }
      Write-Host "         proč: $($p.why)" -ForegroundColor DarkGray
    }
    Write-Host ''
  }
  if ($warnList.Count -gt 0) {
    Write-Host "  [!] Upozornění:" -ForegroundColor Yellow
    foreach ($w in $warnList) { Write-Host "     - $w" }
    Write-Host ''
  }
  Write-Host "  Vysvětlení záznamů: $DocUrl" -ForegroundColor DarkGray
  if ($problems.Count -gt 0) { Write-Host "  [X] Doména vyžaduje zásah (viz K opravě)." -ForegroundColor Red }
  elseif ($propList.Count -gt 0) { Write-Host "  [OK] V zóně vše správně, čeká se jen na propagaci." -ForegroundColor Green }
  else { Write-Host "  [OK] Vše správně nastavené a propagované." -ForegroundColor Green }

  $result = if ($problems.Count -gt 0) { 'problem' } elseif ($propList.Count -gt 0) { 'propagace' } else { 'ok' }
  Track $d $result $problems.Count
  return $res
}

# ---------- hlavní běh ----------
if (-not (ServerResponds $Dns $Domains[0])) {
  Write-Host ''
  Write-Host "  X DNS server '$Dns' neodpovídá / nelze použít." -ForegroundColor Red
  Write-Host "    Zkontrolujte proměnnou FDA_DNS. Bez ní se použije 8.8.8.8." -ForegroundColor DarkGray
  exit 4
}

$overall = 0
$results = @()
foreach ($dom in $Domains) {
  $r = CheckDomain $dom
  $results += $r
  if ($r.exit -gt $overall) { $overall = $r.exit }
}

if ($Domains.Count -gt 1) {
  Write-Host ''
  Write-Host "== Souhrn dávky ($($results.Count) domén) ==" -ForegroundColor White
  Write-Host $Sep -ForegroundColor DarkGray
  foreach ($r in $results) {
    Write-Host ("  " + $r.domain.PadRight(40)) -NoNewline
    if ($r.msg) { Write-Host "X $($r.msg)" -ForegroundColor Red }
    elseif ($r.err -gt 0) { Write-Host "X $($r.err)x k opravě" -ForegroundColor Red }
    elseif ($r.prop -gt 0) { Write-Host "cca čeká na propagaci" -ForegroundColor Yellow }
    elseif ($r.warn -gt 0) { Write-Host "OK (+$($r.warn) upozornění)" -ForegroundColor Green }
    else { Write-Host "OK" -ForegroundColor Green }
  }
  Write-Host ''
}

Check-Update
exit $overall
