Skip to content

Instantly share code, notes, and snippets.

@jonasnordlund
Created January 17, 2023 10:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonasnordlund/06aff664873cb1868625d25fcd479b2a to your computer and use it in GitHub Desktop.
Save jonasnordlund/06aff664873cb1868625d25fcd479b2a to your computer and use it in GitHub Desktop.
Resolve SPF records of domain name
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