Public/Email/Test-SPFRecord.ps1
<#
Copyright © 2024 Integris. For internal company use only. All rights reserved. #> FUNCTION Test-SPFRecord { <# .SYNOPSIS Checks if an IP address is authorized to send email for a domain based on its SPF record. .PARAMETER Domain The domain to check the SPF record for. .PARAMETER IPAddress The IP address to verify against the SPF record. .EXAMPLE Test-SPFRecord -Domain "example.com" -IPAddress "230.128.11.31" #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string[]]$Domain, [Parameter(Mandatory=$true)] [System.Net.IPAddress[]]$IPAddress ) Function Test-IndividualSPFRecord { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$Domain, [Parameter(Mandatory=$true)] [System.Net.IPAddress]$IPAddress ) function Test-IpAddressInCidr { param( [string]$IPAddress, [string]$CIDR ) try { # Split CIDR into IP and mask length $parts = $CIDR.Split('/') $networkAddress = $parts[0] $maskLength = [int]$parts[1] # Convert IP addresses to integers $ipInt = [System.BitConverter]::ToInt32(([System.Net.IPAddress]::Parse($IPAddress).GetAddressBytes()), 0) $netInt = [System.BitConverter]::ToInt32(([System.Net.IPAddress]::Parse($networkAddress).GetAddressBytes()), 0) # Create mask $mask = -1 -shl (32 - $maskLength) # Check if IP is in range return ($ipInt -band $mask) -eq ($netInt -band $mask) } catch { Write-Error "Invalid IP Address or CIDR format." return $false } } try { # Resolve TXT records for the domain $txtRecords = Resolve-DnsName -Name $Domain -Type TXT -ErrorAction Stop | Where-Object { $_.Strings -like 'v=spf1*' } if (-not $txtRecords) { #Write-Warning "No SPF record found for $Domain" return $false } # Get the SPF record $spfRecord = $txtRecords.Strings | Where-Object { $_ -like 'v=spf1*' } if ($spfRecord.Count -eq 0) { #Write-Warning "No valid SPF record found for $Domain" return $false } # Parse SPF record components $spfParts = $spfRecord -split '\s+' $ipValid = $false foreach ($part in $spfParts) { # Check for IP4 mechanism if ($part -like 'ip4:*') { $spfIP = $part -replace 'ip4:' # Handle CIDR notation if ($spfIP -like '*/[0-9]*') { $ipNetwork = $spfIP.Split('/')[0] $cidr = $spfIP.Split('/')[1] try { IF (Test-IpAddressInCidr -IpAddress $IPAddress -Cidr $spfIP) { RETURN $True } } catch { #Write-Warning "Error processing IP range $spfIP" } } else { # Direct IP comparison if ($spfIP -eq $IPAddress) { $ipValid = $true break } } } # Check for include mechanism elseif ($part -like 'include:*') { $includeDomain = $part -replace 'include:' try { # Recursively check included domain if (Test-IndividualSPFRecord -Domain $includeDomain -IPAddress $IPAddress) { $ipValid = $true break } } catch { #Write-Warning "Error checking include domain $includeDomain" } } } # Check for -all (hard fail) if ($spfRecord -like '*-all*') { return $ipValid } # Check for ~all (soft fail) elseif ($spfRecord -like '*~all*') { return $ipValid } # Check for ?all (neutral) elseif ($spfRecord -like '*?all*') { return $true } return $ipValid } catch { #Write-Error "Error checking SPF record: $_" return $false } } $TotalCount = $Domain.Count * $IPAddress.Count $CurrentCount = 0 $Results = @() FOREACH ($Entry in $Domain) { FOREACH ($Address in $IPAddress) { Write-IntegrisProgressBar -TotalCount $TotalCount -CurrentCount $CurrentCount -Activity "Performing SPF Checks" -Status "Checking [$Entry] SPF Record for [$Address]" -ID 20938402 $CurrentCount++ $Result = Test-IndividualSPFRecord -Domain $Entry -IPAddress $Address $PassResult = $False IF ($Result -eq $True) { $PassResult = $True } $Results += [PSCustomObject]@{ Domain = $Entry IPAddress = $Address SPFResult = $PassResult } } } Write-Progress -ID 20938402 -Completed -Activity "Performing SPF Checks" RETURN $Results | Select Domain, IPAddress, SPFResult } |