Private/AzStackHci.DNS.Helpers.ps1

# ////////////////////////////////////////////////////////////////////////////
# Check DNS to for domain name, and return IP address if found
Function Get-DnsRecord {
    param (
        [Parameter(Mandatory=$true)]    
        [ValidateLength(1, 255)]
        [string]$url,

        [Parameter(Mandatory=$false)]
        [switch]$SkipRfc1918Check
    )

    begin {
        # Write-Debug "Get-DnsRecord: Beginning DNS lookup process"
        # Initialize variables
        [bool]$dnsExists = $False
    }

    process {
        Write-Debug "Checking to see if $($url) returns an IP address from DNS"
        # Remove variables
        Remove-Variable ipAddress -ErrorAction SilentlyContinue

        # Call Resolve-DnsName with exponential backoff: retry up to $script:DNS_MAX_RETRIES times
        # with increasing delays (1s, 2s, 4s + jitter) to avoid flooding DNS on flaky networks.
        For ($i=1; $i -le $script:DNS_MAX_RETRIES; $i++) {
        
            # Remove variables
            Remove-Variable DNSCheckError -ErrorAction SilentlyContinue
            # Initialize variables
            $DNSCheck = @()
            # Check if the domain name exists in DNS
            if(-not($dnsExists)){
                Write-Debug "DNS attempt $i of 3: Checking DNS server for endpoint '$url'"

                try {
            
                    # Check if the domain name exists in DNS using Resolve-DnsName.
                    # //// Use "5>$null" to suppress internal tracing messages such as "DEBUG: 72136".
                    $DNSCheck = Resolve-DnsName -Name $url -Type A -DnsOnly -ErrorAction Stop -ErrorVariable DNSCheckError 5>$null

                # /////////////////////
                # Error handling logic
                # /////////////////////
                } catch [System.Management.Automation.CommandNotFoundException] { # Catch if Resolve-DnsName is not found, not expected
                    throw "Resolve-DnsName cmdlet is not available. This module requires Windows 8/Server 2012 or later."

                } catch { 
                    # Catch DNS Errors
                    # Check if the error message contains 'DNS name does not exist'
                    if($_.Exception.Message.ToString().Contains('DNS name does not exist')) {
                        Write-Debug "DNS Error for '$url' Exception: $($_.Exception.Message)"
                    } else {
                        # All other DNS errors
                        if($i -eq $script:DNS_MAX_RETRIES){
                            Write-HostAzS "Error: DNS lookup failed for '$url' - Exception Message: $($_.Exception.Message)" -ForegroundColor Red
                        }
                    }
                    # Exponential backoff before next retry: delay = base * 2^(attempt-1) + random jitter
                    if ($i -lt $script:DNS_MAX_RETRIES) {
                        $backoffDelay = $script:DNS_RETRY_BASE_DELAY_SEC * [math]::Pow(2, ($i - 1))
                        $jitter = Get-Random -Minimum 0.0 -Maximum 0.5
                        $totalDelay = [math]::Round($backoffDelay + $jitter, 1)
                        Write-HostAzS "DNS lookup failed for '$url' (attempt $i of $($script:DNS_MAX_RETRIES)), retrying in $($totalDelay)s..." -ForegroundColor Yellow
                        Start-Sleep -Milliseconds (($backoffDelay + $jitter) * 1000)
                    } else {
                        Write-HostAzS "DNS lookup failed for '$url' (attempt $i of $($script:DNS_MAX_RETRIES)), no more retries." -ForegroundColor Red
                    }

                } Finally {
                    # If no DNS errors, set the ipAddress variable to IP address returned from DNS
                    if(-not($DNSCheckError)) {
                        
                        # Check if the DNS name exists, and that $DnsExists is false (IP address not yet found)
                        if($DNSCheck -and (-not($dnsExists))){
                            if(($DNSCheck.IPAddress).count -gt 1){
                                # Use first IP address returned from DNS
                                $ipAddress = ($DNSCheck.IPAddress)[0]
                                Write-Debug "Multiple IP addresses returned from DNS for $url, using first IP from list of addresses: $($DNSCheck.IPAddress)"
                                $dnsExists = $True
                            } else {
                                # Only one IP address returned from DNS
                                $ipAddress = $DNSCheck.IPAddress
                                Write-Debug "Single IP address returned from DNS for $url, $ipAddress"
                                $dnsExists = $True
                            }

                        } elseif((-not($DNSCheck))){
                            # No IP address returned from DNS, but record exists
                            $ipAddress = "No Type A record found in DNS"
                            $dnsExists = $False

                        } else {
                            # Do nothing, DNS already exists
                        }
                    
                    } else {
                        # DNS Error variable exists, set IP address to "DNS Lookup Failed"
                        $ipAddress = "DNS name does not exist"
                        $dnsExists = $False
                    }

                } # End of Finally block

            } else {
                # DNS already exists, skip further checks, but will be on second loop
                Write-Debug "IP address found from DNS on attempt $($i -1), skipping further name resolution attempts"
                Break
            }

        } # End of For loop three attempts

        if($dnsExists){
            Write-Verbose "DNS lookup successful for $url, returned IP Address: $ipAddress"
        } else {
            Write-HostAzS "DNS lookup failed for $url" -ForegroundColor Red
            Write-Verbose  "DNS lookup failed three times for $url - $ipAddress"
        }

        # Test if the IP address is RFC1918 private address
        if(-not($SkipRfc1918Check.IsPresent)){
            # Only test if the SkipRfc1918Check switch is not present
            if($ipAddress -and (-not($ipAddress -in @("No Type A record found in DNS","DNS name does not exist","")))){
                # Check if the IP address is in valid IPv4 format
                if(($IpAddress -match '^(\d{1,3}\.){3}\d{1,3}$')) {
                    # Only test if the IP address is valid
                    Write-Verbose "Testing if returned IP Address '$ipAddress' is an RFC1918 private address"
                    # Check if the IP address is an RFC1918 private address
                    if(Test-IPv4IsRfc1918 -IpAddress $ipAddress){
                        # IP Address is an RFC1918 private address
                        Remove-Variable testUrl -ErrorAction SilentlyContinue
                        # Ensure URL is lowercase for comparison
                        $testUrl = $url.ToLower()

                        # Tier 1: Check if URL matches critical Arc service Private Link endpoints (NOT SUPPORTED)
                        Remove-Variable isUrlArcServicePrivateLink -ErrorAction SilentlyContinue
                        $isUrlArcServicePrivateLink = $script:PrivateLinkCriticalEndpoints | Where-Object { $testUrl -like $_ }

                        # Tier 2: Check if URL matches PaaS services that support Private Link (proxy bypass needed)
                        Remove-Variable isUrlPaaSPrivateLink -ErrorAction SilentlyContinue
                        $isUrlPaaSPrivateLink = $script:PrivateLinkProxyBypassEndpoints | Where-Object { $testUrl -like $_ }

                        if($isUrlArcServicePrivateLink){
                            # Tier 1 — RED: Arc Private Link Scopes NOT supported for Azure Local
                            Write-Debug "URL '$url' matches critical Arc service Private Link endpoint pattern for: '$isUrlArcServicePrivateLink'"
                            Write-HostAzS "CRITICAL: RFC1918 address detected for Arc endpoint '$url'!" -ForegroundColor Red
                            Write-HostAzS "`tArc Private Link Scopes are NOT supported for Azure Local." -ForegroundColor Red
                            Write-HostAzS "`tThis endpoint must resolve to a public IP address. IP returned from DNS: '$ipAddress'" -ForegroundColor Red
                            Write-HostAzS "`tCheck for CNAME Alias in your DNS zones configuration." -ForegroundColor Red
                            Write-HostAzS "`tReference: https://learn.microsoft.com/en-us/azure/azure-local/concepts/firewall-requirements" -ForegroundColor Red
                            Write-HostAzS "Sleeping for 10 seconds..." -ForegroundColor Red
                            Start-Sleep -Seconds 10
                            $script:PrivateLinkCriticalArray += $url

                        } elseif($isUrlPaaSPrivateLink){
                            # Tier 2 — YELLOW: PaaS Private Link supported, but proxy bypass required
                            Write-Debug "URL '$url' matches PaaS Private Link endpoint pattern for: '$isUrlPaaSPrivateLink'"
                            Write-HostAzS "WARNING: RFC1918 address detected for '$url' - Private endpoint in use." -ForegroundColor Yellow
                            Write-HostAzS "`tIP Address returned from DNS: '$ipAddress'" -ForegroundColor Yellow
                            if($script:Proxy.Enabled){
                                Write-HostAzS "`tProxy detected - ensure this FQDN is on the proxy bypass/exception list." -ForegroundColor Yellow
                                Write-HostAzS "`tTraffic to Private Link endpoints must route via ExpressRoute or VPN, not through the proxy." -ForegroundColor Yellow
                            } else {
                                Write-HostAzS "`tEnsure routing is configured to send traffic via ExpressRoute or Site-to-Site VPN." -ForegroundColor Yellow
                            }
                            $script:PrivateLinkProxyBypassArray += $url

                        } else {
                            # Tier 3 — YELLOW: Other RFC1918 address, informational
                            Write-HostAzS "INFO: RFC1918 private address detected for '$url'. IP returned from DNS: '$ipAddress'" -ForegroundColor Yellow
                            Write-HostAzS "`tCheck for CNAME Alias of endpoint in your DNS zones configuration." -ForegroundColor Yellow
                        }

                        $script:PrivateLinkDetected = $true
                        $script:PrivateLinkDetectedArray += $url
                    } else {
                        # Do nothing
                        Write-Verbose "Returned IP Address is NOT an RFC1918 private address."
                    }
                } else {
                    Write-Verbose "Returned IP Address '$ipAddress' is not in valid IPv4 format, skipping RFC1918 private address test"
                }
            } else {
                Write-Debug "Not testing if returned IP Address '$ipAddress' is an RFC1918 private address, as it is not a valid IP address"
            }

        } else {
            Write-Debug "SkipRfc1918Check switch present, skipping RFC1918 private address test"
        }

    } # End of process block

    end {
        # Write-Debug "Get-DnsRecord: DNS lookup process completed"

        # Return True/False and IP Address output as a PSObject.
        $DNSReturnVariable = New-Object PsObject -Property @{
            # True/False
            DNSExists = $dnsExists
            # IP Address, or "DNS Lookup Failed"
            IPAddress = $ipAddress
        }
        return $DNSReturnVariable

    } # End of end block

} # End of Get-DnsRecord function


# ////////////////////////////////////////////////////////////////////////////
# Function to test if an IP address is in the RFC 1918 private IP range.
# Returns $true if the IP address is in the private range, otherwise returns $false.
Function Test-IPv4IsRfc1918 {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [ipaddress]$IpAddress
    )

    begin {
        # Write-Debug "Test-IPv4IsRfc1918: Beginning RFC1918 private IP address check for '$IpAddress'"
    }

    process {

        $IpAddressString = $IpAddress.ToString()
        # Validated IP is correct IPv4 format
        if (-not ($IpAddressString -match '^(\d{1,3}\.){3}\d{1,3}$')) {
            Write-Error "Invalid IPv4 address format."
            Return $false
        }

        $octets = $IpAddressString.Split('.')
        if ($octets.Count -ne 4) { Return $false }

        # Convert octets to integers
        $o1 = [int]$octets[0]
        $o2 = [int]$octets[1]

        # 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
        if ($o1 -eq 10) { Return $true }
        # 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
        if ($o1 -eq 172 -and $o2 -ge 16 -and $o2 -le 31) { Return $true }
        # 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
        if ($o1 -eq 192 -and $o2 -eq 168) { Return $true }

        # Not in RFC 1918 private IP range
        Return $false
    } # End of process block

    end {
        # Write-Debug "Test-IPv4IsRfc1918: RFC1918 private IP address check completed"
    }
} # End Function Test-IPv4IsRfc1918

# SIG # Begin signature block
# MIIoLAYJKoZIhvcNAQcCoIIoHTCCKBkCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD0tVRAY/f03WRf
# +ETjqdoeQqqaDJTf4J3Z++QWJS/zW6CCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z
# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy
# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi
# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ
# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ
# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe
# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk
# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj
# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS
# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns
# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO
# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71
# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9
# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk
# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm
# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn
# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo
# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgwwghoIAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJs9pq34RWUkjdK0gBxye4R2
# NddEnSi3TLj7IAYqjfBHMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEATBzMu8HPtZPm3chn8eD7SGSGk23GJgHy7CNyS9KEcIFWeMlRaAH/lofE
# CaQ2QXg86VeRzNEamrad97Na6tl5BQLLCGWuqDorR3RDepRCgi640szWeteLT1+W
# oae10MJ3jQpqWGApsrzIRkUVxNPrsS8hGEC+kLAT5oUvY90C4YcKJxyOanRnmY2i
# nr5lfHzhUAofRWquyBEic6gyRw3hhtd4ufQFCmwBhOIHj0/t+o0w+iw7d8dSf5Wj
# xZ9ATqodx7lhI+sl3ybjgvm1T2X8YueBdlABPYpcrrjdKncZODKJMajjYMms+XSS
# 3s8rkQQFxml+xhmjv0WodRli1qAo06GCF5YwgheSBgorBgEEAYI3AwMBMYIXgjCC
# F34GCSqGSIb3DQEHAqCCF28wghdrAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFRBgsq
# hkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBhpPVtfXoZGSf3Fd1kt3OXq6hLMr3sW+GR5ZGFP1YWLQIGadfCiNy9
# GBIyMDI2MDQyMDE3MTU1Ni41NlowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy
# aWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo5MjAwLTA1
# RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC
# Ee0wggcgMIIFCKADAgECAhMzAAACI0/ZYCRTz/4rAAEAAAIjMA0GCSqGSIb3DQEB
# CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI2MDIxOTE5Mzk1
# N1oXDTI3MDUxNzE5Mzk1N1owgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx
# JzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo5MjAwLTA1RTAtRDk0NzElMCMGA1UE
# AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAIrpDaeTlZR0rNIJJp+n5SNQBGxbEcpLresmEUL/NJps
# W6ZMG5onRA2uap6+5vkNvt9KPmq3DAqeMg73b4dcXrvX3Z+6MvsMWi3lYSP8C0Rn
# 9evMUeKYqU3WHqARDA/kjrvCLNo9blnNIE2losGDmge8BI85m3B01Shn4NAoXeEm
# XUpm6giVUr6qLtwuOBqTqzmg5lxEIysqe4LdqhVrrBENti8pS6PuuQXH0o7Q+wcn
# +T4udkyCBGF6HgBV1rDKH6g7Mo+OVAZQ19J5ZSDKbZT0Itry23SZBfgPEPPr6tqb
# nSCPWgB/JDpNDuv3o8AMU4oGBpTv5ykedpkbz11N6BDrJ0FEYjJw7DV1FfZ4oNFH
# POIrdyfRZoib/s54azJAqMjMRC5RMO/QmP/3NDu2u4s46kkP3wElU4ruN7zhLPaF
# vce9RJPuPWPY3yl4PqiWSkUdH/VnwnPgX6aStQXsyY8CKtgdHO6dsiDcesMw3AVg
# 3vIGQMDj9Uyj0JjTL2gZSirbKNsLBOJvP1ViX3ecHdBCJMJP2dbcz5M5YH48ytmk
# TGrUFIeYo/Mip6EqqtQOgzfc8r50QrClgsRPq5erge5BExdZP/+w+5tSdABppQx9
# CEBlLLbce3HC03d4r35PjAJq/bBAW3nt5Q7BRbn8MLMwX225rkd7WE2+BwBdqIbX
# AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU1sCHz2/b2c9j1vBBvVBgLPFWB5cwHwYD
# VR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZO
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIw
# VGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBc
# BggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0
# cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYD
# VR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMC
# B4AwDQYJKoZIhvcNAQELBQADggIBAIdDB7vPm2ng1nAB/VwH7hz0niy/Dc/paoYE
# zG2rOdLoN3NTNK1ccJo9mEzjWDWIoc2eZycuPAu6M4Ro2OFKdQOIBmpCNbllqk4H
# GBzsSCCGH2T6vvypYB7esnhCiEFuFIZ1m0qK9NFp5GqaeHLz5OGsqHMJ4TBpqtcm
# KZnBKl1BBQNuF5Yd7IDEBKq6W13ko7Sb9QW87Te196moZcDi0KD9YYQLAqo6MnOl
# EB88gHrLUfJWuT6+YvmukRtPDAs61ftbEUYbz5xguT0eNoOTGtoD8diUpBHHWx3N
# r7D+C6UvCA6cHJEkoXauvwzsU0iXCiLrLAWlo1zwDsd7BoaODD+19wTbrQjVd6Qa
# W4A0j0ec405haUjsEoFBtYTa16jq+xDVWDwHytNlJ49V2ZcvU8+qqzcpV0UozmRi
# hw8IMz7pUvfYhX3qwRJ/ZPsOPFqekKDYPZRiPhnWLtzLxTUssMaDnkpazhp/ZFEG
# MfYy6UeACZbmhsrGJkINCNFqugnZcSVdSGKAT0HO+EIVtP8cNja+lWmXkedKlwJL
# GYvmLmUhP/FsBAwjsu6Hvleub4iyV8VY4Y4YyUKn7bioQkSCVcQ/vHCyiU10E2d1
# eKGHIh59UaUjUNHvEYQuImuTyJ9VZij1cRsRe/+Vu+noXZHZSyfB5ZyS+rTLUdac
# scOofp0+MIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG
# 9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEy
# MDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
# MTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az
# /1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V2
# 9YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oa
# ezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkN
# yjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7K
# MtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRf
# NN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SU
# HDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoY
# WmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5
# C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8
# FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TAS
# BgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1
# Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUw
# UzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNy
# b3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB
# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO
# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr
# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3
# DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEz
# tTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJW
# AAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G
# 82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/Aye
# ixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI9
# 5ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1j
# dEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZ
# KCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xB
# Zj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuP
# Ntq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvp
# e784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCA1Aw
# ggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVADhF
# YWz6ROJmehmICPUG1iPzMI1qoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDtkL/zMCIYDzIwMjYwNDIwMTUwODM1
# WhgPMjAyNjA0MjExNTA4MzVaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAO2Qv/MC
# AQAwCgIBAAICE8ACAf8wBwIBAAICEv4wCgIFAO2SEXMCAQAwNgYKKwYBBAGEWQoE
# AjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkq
# hkiG9w0BAQsFAAOCAQEAF/43PlkpjFMDM1LslPIovy34UuiSK6bKYcgcnsw1mnQv
# s4AmmiGujzrlctCOiKqW485htyv6rUvgKEdUAx3fcuObzz+oEtECfMc3zsvQqogn
# r0k2iz39XrSsg95BCWZ1INJa+cQPQOO4dvaTbqOHPFD5CJBPMUtAYdc2/0j0iFDo
# FpGji00N6ku5G7z1Gzpbjg38RyVqvkZrf6QBiOmR3Nu2BnPkL+cGKuISNnMs595o
# NF2qVEzIE+bJp9qN9+w4cSRss56me37jS8P9PYNj0aSgT2v5nCsluw4IPPOcKy4M
# Hq5mouqKPnNzlQel/p77SLhCgBLYzZkl5Nb10BCB5jGCBA0wggQJAgEBMIGTMHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACI0/ZYCRTz/4rAAEAAAIj
# MA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQw
# LwYJKoZIhvcNAQkEMSIEIF5fqIbRQ44uAjdIlSuDKF6RrBM7tNAAjt9ir7LswhgY
# MIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQglvAzLBFu9waLKeOfCMCpxoPj
# vJi95splEC+0QBHm7rMwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMAITMwAAAiNP2WAkU8/+KwABAAACIzAiBCD/dUL3uvhVgYdwMz/5fkwRfdO6
# Xg/LwUmOq0oXZ7ltMTANBgkqhkiG9w0BAQsFAASCAgADRSu/Be5EY1xRqcXdDOCQ
# BSaim7Dd9uE1NMsw2SUynFQ4aU+Z2T2ee6HDTFGw5n5fWA14peMSTEmpM4/zdHEq
# iWadT0xPPQPWASDFukotOqNRyygaTWa+IsvWqLOIusNEGkIoZpQ9ANr+d8KmJaOx
# 5q3nWMjyrZdyYx75yAyNziJUBKkA7jOVBJxaUN1M/4LMTpEQcRrS6ABKaXv/GLev
# 0oIlGqzwMmxe93cgFfNXw/Z52CQDLXz6M8og1crOkE1TYmgXhmjDtW3yVuMEOleQ
# 3JT7kAM4fQCkx9tWljku8ztQfhW1vx6R9/EXXCVE6FK5U0GbeUDjnmTE02IKFZYh
# DniJ5PrVz17V9fHy045LPXDYhadP6ITUzxdv4jVUaghbu4W8c/YfkyAk45V4gxD9
# pEILH/H1tU0WrLtWRnZIDEOyIw/FXg8c11SEDeAYWfAOtWGcJ9HtsiFlyMy+sb5e
# G/WpoqC3cgRN/F6Y36QpRp/uSCYhh84EXr5Kki9w1oc1lrlLpexnLHlrUHoaBc/D
# Kwg3l0X8xDgqpa3Z4CoG685PWnkF+JY2x1IuDlcNllOznpwnVuKob/VKsPeVfg13
# lXLvqThbF7CYHoBUbcNAv74RGy4cVqUXT3F/SI3dvig6KHRpiesqVlUBcqK/uuUB
# Q8UW1wuY5xyTUAu77WyNFw==
# SIG # End signature block