Created
January 17, 2023 10:21
-
-
Save jonasnordlund/06aff664873cb1868625d25fcd479b2a to your computer and use it in GitHub Desktop.
Resolve SPF records of domain name
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Resolve-SPFRecord { | |
[CmdletBinding()] | |
param ( | |
# Domain Name | |
[Parameter(Mandatory = $true, | |
ValueFromPipeline = $true, | |
ValueFromPipelineByPropertyName = $true, | |
Position = 1)] | |
[string]$Name, | |
# DNS Server to use | |
[Parameter(Mandatory = $false, | |
ValueFromPipelineByPropertyName = $true, | |
Position = 2)] | |
[string]$Server = "1.1.1.1", | |
# If called nested provide a referrer to build valid objects | |
[Parameter(Mandatory = $false)] | |
[string]$Referrer | |
) | |
begin { | |
class SPFRecord { | |
[string] $SPFSourceDomain | |
[string] $IPAddress | |
[string] $Referrer | |
[string] $Qualifier | |
[bool] $Include | |
# Constructor: Creates a new SPFRecord object, with a specified IPAddress | |
SPFRecord ([string] $IPAddress) { | |
$this.IPAddress = $IPAddress | |
} | |
# Constructor: Creates a new SPFRecord object, with a specified IPAddress and DNSName | |
SPFRecord ([string] $IPAddress, [String] $DNSName) { | |
$this.IPAddress = $IPAddress | |
$this.SPFSourceDomain = $DNSName | |
} | |
# Constructor: Creates a new SPFRecord object, with a specified IPAddress and DNSName and | |
SPFRecord ([string] $IPAddress, [String] $DNSName, [String] $Qualifier) { | |
$this.IPAddress = $IPAddress | |
$this.SPFSourceDomain = $DNSName | |
$this.Qualifier = $Qualifier | |
} | |
} | |
} | |
process { | |
# Keep track of number of DNS queries | |
# DNS Lookup Limit = 10 | |
# https://tools.ietf.org/html/rfc7208#section-4.6.4 | |
# Query DNS Record | |
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type TXT | |
# Check SPF record | |
$SPFRecord = $DNSRecords | Where-Object { $_.Strings -match "^v=spf1" } | |
# Validate SPF record | |
$SPFCount = ($SPFRecord | Measure-Object).Count | |
if ( $SPFCount -eq 0) { | |
# If there is no error show an error | |
Write-Error "No SPF record found for `"$Name`"" | |
} | |
elseif ( $SPFCount -ge 2 ) { | |
# Multiple DNS Records are not allowed | |
# https://tools.ietf.org/html/rfc7208#section-3.2 | |
Write-Error "There is more than one SPF for domain `"$Name`"" | |
} | |
else { | |
# Multiple Strings in a Single DNS Record | |
# https://tools.ietf.org/html/rfc7208#section-3.3 | |
$SPFString = $SPFRecord.Strings -join '' | |
# Split the directives at the whitespace | |
$SPFDirectives = $SPFString -split " " | |
# Check for a redirect | |
if ( $SPFDirectives -match "redirect" ) { | |
$RedirectRecord = $SPFDirectives -match "redirect" -replace "redirect=" | |
Write-Verbose "[REDIRECT]`t$RedirectRecord" | |
# Follow the include and resolve the include | |
Resolve-SPFRecord -Name "$RedirectRecord" -Server $Server -Referrer $Name | |
} | |
else { | |
# Extract the qualifier | |
$Qualifier = switch ( $SPFDirectives -match "^[+-?~]all$" -replace "all" ) { | |
"+" { "pass" } | |
"-" { "fail" } | |
"~" { "softfail" } | |
"?" { "neutral" } | |
} | |
$ReturnValues = foreach ($SPFDirective in $SPFDirectives) { | |
switch -Regex ($SPFDirective) { | |
"%[{%-_]" { | |
Write-Warning "[$_]`tMacros are not supported. For more information, see https://tools.ietf.org/html/rfc7208#section-7" | |
Continue | |
} | |
"^exp:.*$" { | |
Write-Warning "[$_]`tExplanation is not supported. For more information, see https://tools.ietf.org/html/rfc7208#section-6.2" | |
Continue | |
} | |
'^include:.*$' { | |
# Follow the include and resolve the include | |
Resolve-SPFRecord -Name ( $SPFDirective -replace "^include:" ) -Server $Server -Referrer $Name | |
} | |
'^ip[46]:.*$' { | |
Write-Verbose "[IP]`tSPF entry: $SPFDirective" | |
$SPFObject = [SPFRecord]::New( ($SPFDirective -replace "^ip[46]:"), $Name, $Qualifier) | |
if ( $PSBoundParameters.ContainsKey('Referrer') ) { | |
$SPFObject.Referrer = $Referrer | |
$SPFObject.Include = $true | |
} | |
$SPFObject | |
} | |
'^a:.*$' { | |
Write-Verbose "[A]`tSPF entry: $SPFDirective" | |
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type A | |
# Check SPF record | |
foreach ($IPAddress in ($DNSRecords.IPAddress) ) { | |
$SPFObject = [SPFRecord]::New( $IPAddress, ($SPFDirective -replace "^a:"), $Qualifier) | |
if ( $PSBoundParameters.ContainsKey('Referrer') ) { | |
$SPFObject.Referrer = $Referrer | |
$SPFObject.Include = $true | |
} | |
$SPFObject | |
} | |
} | |
'^mx:.*$' { | |
Write-Verbose "[MX]`tSPF entry: $SPFDirective" | |
$DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type MX | |
foreach ($MXRecords in ($DNSRecords.NameExchange) ) { | |
# Check SPF record | |
$DNSRecords = Resolve-DnsName -Server $Server -Name $MXRecords -Type A | |
foreach ($IPAddress in ($DNSRecords.IPAddress) ) { | |
$SPFObject = [SPFRecord]::New( $IPAddress, ($SPFDirective -replace "^mx:"), $Qualifier) | |
if ( $PSBoundParameters.ContainsKey('Referrer') ) { | |
$SPFObject.Referrer = $Referrer | |
$SPFObject.Include = $true | |
} | |
$SPFObject | |
} | |
} | |
} | |
Default { | |
Write-Warning "[$_]`t Unknown directive" | |
} | |
} | |
} | |
$DNSQuerySum = $ReturnValues | Select-Object -Unique SPFSourceDomain | Measure-Object | Select-Object -ExpandProperty Count | |
if ( $DNSQuerySum -gt 6) { | |
Write-Warning "Watch your includes!`nThe maximum number of DNS queries is 10 and you have already $DNSQuerySum.`nCheck https://tools.ietf.org/html/rfc7208#section-4.6.4" | |
} | |
if ( $DNSQuerySum -gt 10) { | |
Write-Error "Too many DNS queries made ($DNSQuerySum).`nMust not exceed 10 DNS queries.`nCheck https://tools.ietf.org/html/rfc7208#section-4.6.4" | |
} | |
$ReturnValues | |
} | |
} | |
} | |
end { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment