public/Get-SPFRecord.ps1

<#>
.HelpInfoURI 'https://github.com/T13nn3s/Show-SpfDkimDmarc/blob/main/public/CmdletHelp/Get-SPFRecord.md'
#>

function Get-SPFRecord {
    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $True,
            ValueFromPipeline = $True,
            ValueFromPipelineByPropertyName = $True,
            HelpMessage = "Enter one or more domain names to resolve their SPF records."
        )][string[]]$Name,

        [Parameter(Mandatory = $false,
            HelpMessage = "DNS Server to use.")]
        [string]$Server
    )

    begin {

        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        $PSBoundParameters | Out-String | Write-Verbose

        if ($PSBoundParameters.ContainsKey('Server')) {
            $SplatParameters = @{
                'Server'      = $Server
                'ErrorAction' = 'SilentlyContinue'
            }
        }
        Else {
            $SplatParameters = @{
                'ErrorAction' = 'SilentlyContinue'
            }
        }

        $SpfObject = New-Object System.Collections.Generic.List[System.Object]
    }

    Process {
        foreach ($domain in $Name) {

            # Get SPF record from specified domain
            $SPF = Resolve-DnsName -Name $domain -Type TXT @SplatParameters | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue
            
            # Checks for SPF redirect and follow the redirect
            if ($SPF -match "redirect") {
                $redirect = $SPF.Split(" ")
                $RedirectName = $redirect -match "redirect" -replace "redirect="
                $SPF = Resolve-DnsName -Name "$RedirectName" -Type TXT @SplatParameters | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue
            }

            # Check for multiple SPF records
            $RecipientServer = "v=spf1"
            $SPFCount = ($SPF -match $RecipientServer).count
            
            # If there is no SPF record
            if ($null -eq $SPF) {
                $SpfAdvisory = "Domain does not have an SPF record. To prevent abuse of this domain, please add an SPF record to it."
            }
            elseif ($SPFCount -gt 1) {
                $SpfAdvisory = "Domain has more than one SPF record. Only one SPF record per domain is allowed. This is explicitly defined in RFC4408."     
                $SpfTotalLenght = 0
                foreach ($char in $SPF) {
                    $SPFTotalLenght += $char.Length
                    $SpfTotalLenght
                }
            }
            Else {
                $SPF = $SPF -join ""
                $SpfTotalLenght = $SPF.Length

                foreach ($mechanism in $SPF) {
                    if ($mechanism.Length -ge 450) {
                        # See: https://datatracker.ietf.org/doc/html/rfc7208#section-3.4
                        $SpfAdvisory += "Your SPF-record has more than 450 characters. This is SHOULD be avoided according to RFC7208. "
                    }
                    elseif ($mechanism.Length -ge 255) {
                        # See: https://datatracker.ietf.org/doc/html/rfc4408#section-3.1.3
                        $SpfAdvisory = "Your SPF record has more than 255 characters in one string. This MUST not be done as explicitly defined in RFC4408. " 
                    }
                }
            
                switch -Regex ($SPF) {
                    '~all' {
                        $SpfAdvisory += "An SPF-record is configured but the policy is not sufficiently strict."
                    }
                    '-all' {
                        $SpfAdvisory += "An SPF-record is configured and the policy is sufficiently strict."
                    }
                    "\?all" {
                        $SpfAdvisory += "Your domain has a valid SPF record but your policy is not effective enough."
                    }
                    '\+all' {
                        $SpfAdvisory += "Your domain has a valid SPF record but your policy is not effective enough."
                    }
                    Default {
                        $SpfAdvisory += "No qualifier found. Your domain has a SPF record but your policy is not effective enough."
                    }
                }
            }
        
            # Starting Spf Dns Lookup Counter
            # SPF record MUST not exceed 10 DNS Lookups
            # See: https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4
            Write-Verbose "Starting calculation of SPF DNS Lookup count"
            $SpfDnsLookupCount = 0
            $includedDomain

            # Get the mechanisms that count towards the DNS lookup limit
            $SpfDnsLookupCountMechanisms = $SPF -split " " | Where-Object { $_ -match "^(include:|a:|mx:|a$|mx$|ptr$)" }

            foreach ($SpfDnsLookupCountMechanism in $SpfDnsLookupCountMechanisms) {
                Write-Verbose "Processing mechanism: $SpfDnsLookupCountMechanism"
                switch -Regex ($SpfDnsLookupCountMechanism) {
                    "^include:(\S+)" {
                        $SpfDnsLookupCount += 1
                        $includedDomain = $Matches[1]
                        try {
                            $includedSpfRecord = Resolve-DnsName -Name $includedDomain -Type TXT @SplatParameters | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue
                            $includedSpfRecord -split " " | ForEach-Object {
                                switch -Regex ($_) {
                                    "^a:" {
                                        $SpfDnsLookupCount += 1
                                        Write-Verbose "Counting $($_) nested mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                    }
                                    "^a$" {
                                        $SpfDnsLookupCount += 1
                                        Write-Verbose "Counting $($_) nested mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                    }
                                    "^mx$" {
                                        $SpfDnsLookupCount += 1
                                        Write-Verbose "Counting $($_) nested mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                    }
                                    "^ptr$" {
                                        $SpfDnsLookupCount += 1
                                        Write-Verbose "Counting $($_) nested mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                    }
                                    "^include:(\S+)" {
                                        Write-Verbose "Found nested include: $($Matches[1]) in $($SpfDnsLookupCountMechanism)"
                                        $SpfDnsLookupCount += 1
                                        try {
                                            $nestedIncludedSpfRecord = Resolve-DnsName -Name $Matches[1] -Type TXT @SplatParameters | where-object { $_.strings -match "v=spf1" } | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue
                                            switch -Regex ($nestedIncludedSpfRecord) {
                                                "^include:(\S+)" {
                                                    $SpfDnsLookupCount += 1
                                                    Write-Verbose "Counting nested $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                                }
                                                "^a:" {
                                                    $SpfDnsLookupCount += 1
                                                    Write-Verbose "Counting nested $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                                }
                                                "^a$" {
                                                    $SpfDnsLookupCount += 1
                                                    Write-Verbose "Counting nested $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                                }
                                                "^mx$" {
                                                    $SpfDnsLookupCount += 1
                                                    Write-Verbose "Counting nested $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                                }
                                                "^ptr$" {
                                                    $SpfDnsLookupCount += 1
                                                    Write-Verbose "Counting nested $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                                                }
                                            }                                     
                                        }
                                        Catch {
                                            Write-Error "Failed to resolve SPF record for $($Matches[1])"
                                        }
                                    }
                                }
                            }
                                        
                        }
                        Catch {
                            Write-Error "Failed to resolve SPF record for $includedDomain"
                        }
                    }
                    "^a:" {
                        $SpfDnsLookupCount += 1
                        Write-Verbose "Counting $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                    }
                    "^a$" {
                        $SpfDnsLookupCount += 1
                        Write-Verbose "Counting $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                    }
                    "^mx$" {
                        $SpfDnsLookupCount += 1
                        Write-Verbose "Counting $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                    }
                    "^ptr$" {
                        $SpfDnsLookupCount += 1
                        Write-Verbose "Counting $($_) mechanism for DNS lookup, total so far: $SpfDnsLookupCount"
                    }
                }
            }
        }   
        foreach ($domain in $name) {

            $SpfReturnValues = New-Object psobject
            $SpfReturnValues | Add-Member NoteProperty "Name" $domain
            $SpfReturnValues | Add-Member NoteProperty "SPFRecord" "$($SPF)"
            $SpfReturnValues | Add-Member NoteProperty "SPFRecordLength" "$($SpfTotalLenght)"
            if ($SpfDnsLookupCount -eq 10) {
                $SpfReturnValues | Add-Member NoteProperty "SPFRecordDnsLookupCount" "$($SpfDnsLookupCount)/10 (Ok, but maximum DNS Lookups reached!)"
            }
            elseif ($SpfDnsLookupCount -gt 8) {
                $SpfReturnValues | Add-Member NoteProperty "SPFRecordDnsLookupCount" "$($SpfDnsLookupCount)/10 (Ok, but watch your DNS Lookups!)"
            }
            else {
                $SpfReturnValues | Add-Member NoteProperty "SPFRecordDnsLookupCount" "$($SpfDnsLookupCount)/10 (OK)"
            }
            $SpfReturnValues | Add-Member NoteProperty "SPFAdvisory" $SpfAdvisory
            $SpfObject.Add($SpfReturnValues)
            $SpfReturnValues
        }
    } end {}
} 
Set-Alias gspf -Value Get-SPFRecord