Private/Audit/Resolve-DomainMailSecurity.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Resolve-DomainMailSecurity { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Domain, [string]$DkimSelector = 'google' ) $result = @{ Domain = $Domain SPF = @{ Record = ''; Valid = $false; Details = '' } DKIM = @{ Record = ''; Valid = $false; Selector = $DkimSelector; Details = '' } DMARC = @{ Record = ''; Valid = $false; Policy = ''; Details = '' } MTASTS = @{ Record = ''; Valid = $false; Details = '' } MX = @{ Records = @(); Details = '' } } # --- SPF --- try { $spfRecords = Resolve-DnsNameSafe -Name $Domain -Type 'TXT' | Where-Object { $_ -match '^v=spf1\s' } if ($spfRecords.Count -gt 0) { $result.SPF.Record = $spfRecords[0] $result.SPF.Valid = $true if ($spfRecords.Count -gt 1) { $result.SPF.Details = 'Multiple SPF records found (RFC 7208 violation)' $result.SPF.Valid = $false } elseif ($spfRecords[0] -match '\+all') { $result.SPF.Details = 'SPF uses +all (permits any sender)' $result.SPF.Valid = $false } elseif ($spfRecords[0] -match '\?all') { $result.SPF.Details = 'SPF uses ?all (neutral, no enforcement)' } } else { $result.SPF.Details = 'No SPF record found' } } catch { $result.SPF.Details = "SPF lookup failed: $_" } # --- DKIM --- try { $dkimName = "$DkimSelector._domainkey.$Domain" $dkimRecords = Resolve-DnsNameSafe -Name $dkimName -Type 'TXT' if ($dkimRecords.Count -gt 0) { $result.DKIM.Record = $dkimRecords[0] if ($dkimRecords[0] -match 'v=DKIM1' -or $dkimRecords[0] -match 'p=') { $result.DKIM.Valid = $true } else { $result.DKIM.Details = 'DKIM record found but does not contain valid key' } } else { $result.DKIM.Details = "No DKIM record found for selector '$DkimSelector'" } } catch { $result.DKIM.Details = "DKIM lookup failed: $_" } # --- DMARC --- try { $dmarcName = "_dmarc.$Domain" $dmarcRecords = Resolve-DnsNameSafe -Name $dmarcName -Type 'TXT' | Where-Object { $_ -match '^v=DMARC1' } if ($dmarcRecords.Count -gt 0) { $result.DMARC.Record = $dmarcRecords[0] $result.DMARC.Valid = $true if ($dmarcRecords[0] -match 'p=reject') { $result.DMARC.Policy = 'reject' } elseif ($dmarcRecords[0] -match 'p=quarantine') { $result.DMARC.Policy = 'quarantine' } elseif ($dmarcRecords[0] -match 'p=none') { $result.DMARC.Policy = 'none' $result.DMARC.Details = 'DMARC policy is none (monitoring only, no enforcement)' } } else { $result.DMARC.Details = 'No DMARC record found' } } catch { $result.DMARC.Details = "DMARC lookup failed: $_" } # --- MTA-STS --- try { $mtaStsName = "_mta-sts.$Domain" $mtaStsRecords = Resolve-DnsNameSafe -Name $mtaStsName -Type 'TXT' | Where-Object { $_ -match '^v=STSv1' } if ($mtaStsRecords.Count -gt 0) { $result.MTASTS.Record = $mtaStsRecords[0] $result.MTASTS.Valid = $true } else { $result.MTASTS.Details = 'No MTA-STS TXT record found' } } catch { $result.MTASTS.Details = "MTA-STS lookup failed: $_" } # --- MX --- try { $mxRecords = Resolve-DnsNameSafe -Name $Domain -Type 'MX' $result.MX.Records = @($mxRecords) } catch { $result.MX.Details = "MX lookup failed: $_" } return $result } function Resolve-DnsNameSafe { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [ValidateSet('TXT', 'MX', 'A', 'AAAA', 'CNAME')] [string]$Type ) # Try Resolve-DnsName (Windows PowerShell / PS7 on Windows) if (Get-Command 'Resolve-DnsName' -ErrorAction SilentlyContinue) { $records = Resolve-DnsName -Name $Name -Type $Type -ErrorAction Stop if ($Type -eq 'TXT') { return @($records | Where-Object { $_.Strings } | ForEach-Object { $_.Strings -join '' }) } elseif ($Type -eq 'MX') { return @($records | Where-Object { $_.NameExchange } | ForEach-Object { "$($_.Preference) $($_.NameExchange)" }) } return @($records) } # Fallback: dig (Linux/macOS) if (Get-Command 'dig' -ErrorAction SilentlyContinue) { $output = dig +short $Name $Type 2>/dev/null if ($LASTEXITCODE -eq 0 -and $output) { $lines = @($output -split "`n" | Where-Object { $_.Trim() }) if ($Type -eq 'TXT') { return @($lines | ForEach-Object { $_.Trim('"') }) } return @($lines) } return @() } # Fallback: nslookup if (Get-Command 'nslookup' -ErrorAction SilentlyContinue) { $output = nslookup -type=$Type $Name 2>/dev/null if ($output) { $lines = @($output -split "`n" | Where-Object { $_.Trim() }) if ($Type -eq 'TXT') { return @($lines | Where-Object { $_ -match 'text\s*=' } | ForEach-Object { ($_ -replace '.*text\s*=\s*', '').Trim('"') }) } return @($lines) } return @() } throw "No DNS resolution tool available (Resolve-DnsName, dig, or nslookup)" } |