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
}