SpfAnalyzer.psm1

#region Enums
enum SpfAction {
    Pass
    Fail
    SoftFail
    Neutral
}
#endregion Enums
#region Classes
class Dns {
    hidden static [DnsClient.LookupClient]$client = [DnsClient.LookupClient]::new()

    static [object[]] GetRecord([string]$Name, [DnsClient.QueryType]$recordType) {
        $retVal = @()
        switch($recordType)
        {
            {$_ -in ([DnsClient.QueryType]::A, [DnsClient.QueryType]::AAAA)} { 
                $data = [Dns]::Client.Query($Name, $_) `
                | select-object -ExpandProperty Answers `
                | where-object{$_.RecordType -eq $recordType} `
                | select-object -ExpandProperty Address
                $data | foreach-object {
                    $retVal += [SpfIpAddress]::new($recordName, $_)
                }
                break;
            }
            {$_ -eq [DnsClient.QueryType]::MX} {
                $data = [Dns]::Client.Query($Name, $_) `
                | select-object -ExpandProperty Answers `
                | where-object{$_.RecordType -eq $recordType} `
                | select-object -expand Exchange `
                | select-object -expand Value
                $data | foreach-object {
                    $retVal += $_
                }
                break;
            }
            {$_ -eq [DnsClient.QueryType]::TXT} {
                [Dns]::Client.Query($Name, $_) `
                | select-object -ExpandProperty Answers `
                | where-object{$_.RecordType -eq $recordType} `
                | foreach-object {
                    #TXT records may be split into multiple strings
                    if($_.Text.Count -gt 1) {
                        $retVal += ($_.Text -join '')
                    }
                    else {
                        $retVal += $_.Text
                    }
                }
                break;
            }
            default {
                throw "Unsupported record type $recordType"
            }
        }
        if($retVal.Count -eq 0) {return $null} else {return $retVal}
    }

    static [object[]] GetSpfRecord([string]$Name) {
        $retVal = @()
        [Dns]::GetRecord($Name, [DnsClient.QueryType]::TXT) | foreach-object {
            if($_ -match "^v=spf1") {
                $retVal += $_
            }
        }
        if($retVal.Count -eq 0) {return $null} else {return $retVal}
    }
}
class SpfEntry {
    [string]$Prefix
    [string]$Value

    SpfEntry([string]$prefix, [string]$value) {
        $this.Prefix = $prefix
        $this.Value = $value
    }

    [string] ToString() {
        return "$($this.Prefix) $($this.Value)"
    }
}
class SpfIpAddress {
    [string]$Source
    [System.Net.IPAddress]$Address

    SpfIpAddress([string]$source, [System.Net.IPAddress]$address) {
        $this.Source = $source
        $this.Address = $address
    }

    [string] ToString() {
        return $this.Address.ToString()
    }

    [System.Net.IPNetwork] ToNetwork([int]$prefixLength) {
        return  [SpfIpNetwork]::new($this.source, [IpHelper.IPAddressExtensions]::Mask($this.address,$prefixLength,$true))
    }

    static [SpfIpAddress] Parse([string]$source, [string]$address) {
        try {
            $ip = [System.Net.IPAddress]::Parse($address)
            return [SpfIpAddress]::new($source, $ip)
        }
        catch {
            Write-Warning "Invalid IP address $address"
            return $null
        }            
    }
}
class SpfIpNetwork {
    hidden [System.Net.IPNetwork] $network

    [string]$Source
    [System.Net.IPAddress]$BaseAddress
    [int]$PrefixLength

    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberType  = 'ScriptProperty'
            MemberName  = 'BaseAddress'
            Value       = { $this.network.BaseAddress }
        }
        @{
            MemberType  = 'ScriptProperty'
            MemberName  = 'PrefixLength'
            Value       = { $this.network.PrefixLength }
        }
    )

    static SpfIpNetwork() {
        $TypeName = [SpfIpNetwork].Name
        foreach ($Definition in [SpfIpNetwork]::MemberDefinitions) {
            Update-TypeData -TypeName $TypeName -Force @Definition
        }
    }

    SpfIpNetwork() {}

    SpfIpNetwork([string]$source, [System.Net.IPNetwork]$network) {
        $this.Source = $source
        $this.network = $network
    }

    SpfIpNetwork([string]$source, [System.Net.IPAddress]$address, [int]$prefixLength) {
        $this.Source = $source
        #need compiled helper here to overcome powershell language limitations
        $this.network = [IpHelper.IPAddressExtensions]::Mask($address,$prefixLength,$true)
    }

    [bool] Contains([System.Net.IPAddress]$address) {
        return $this.network.Contains($address)
    }
    
    static [SpfIpNetwork] Parse([string]$source, [string]$address) {
        $parts = $address.Split('/')
        $ip = [System.Net.IPAddress]::Parse($parts[0])
        $mask = [int]$parts[1]
        return [SpfIpNetwork]::new($source, $ip, $mask)
    }

    [string] ToString() {
        return "$($this.BaseAddress)/$($this.PrefixLength)"
    }

}
class SpfRecord
{
    hidden [string] $rawRecord

    [string] $Version
    [SpfAction] $FinalAction
    [string] $Source
    [object[]] $Entries

    SpfRecord([string]$source, [string]$rawRecord) {
        $this.rawRecord = $rawRecord
        $this.Version = 'spf1'
        $this.FinalAction = [SpfAction]::Neutral
        $this.Source = $source
        $this.Entries = @()
    }

    [string] ToString() {
        return "Source: $($this.Source) Record: $($this.rawRecord)"
    }

    static [SpfRecord[]] Parse([string]$source, [string]$rawRecord) {
        $retVal = @()
        $record = [SpfRecord]::new($source, $rawRecord)
        $retVal += $record

        $parts = $rawRecord.Split(' ')
        $continueParsing = $true

        foreach($part in $parts)
        {
            if($part.StartsWith('v='))
            {
                $record.Version = $part.Substring(2)
            }
            #methods
            elseif ($continueParsing -and ($part.StartsWith('ip4:') -or $part.StartsWith('ip6:')))
            {
                $ip = $part.Substring(4)
                if($ip -match '/')
                {
                    $record.Entries += [SpfIpNetwork]::Parse($source, $ip)
                }
                else
                {
                    $record.Entries += [SpfIpAddress]::Parse($source, $ip)
                }
            }
            elseif($continueParsing -and $part.StartsWith('include:'))
            {
                $domainName = $part.Substring(8)
                $record.Entries += [SpfEntry]::new('include', $domainName)
                $additionalRecords = [Dns]::GetSpfRecord($domainName)
                foreach($additionalRecord in $additionalRecords)
                {
                    $retVal += [SpfRecord]::Parse($domainName, $additionalRecord)
                }
            }
            elseif($continueParsing -and $part.StartsWith('exists:') -or $part.StartsWith('ptr:') -or $part -eq 'ptr')
            {
                $splits = $part.Split(':')
                if($splits.Length -gt 1)
                {
                    $record.Entries += [SpfEntry]::new($splits[0], $splits[1])
                }
                else
                {
                    $record.Entries += [SpfEntry]::new($part, $null)
                }
            }
            elseif($continueParsing -and ($part.StartsWith('a:') -or $part.StartsWith('a/') -or $part -eq 'a' -or $part.StartsWith('+a:') -or $part.StartsWith('+a/') -or $part -eq '+a'))
            {
                $mask = -1
                $splits = $part.Split('/')
                if($splits.Length -gt 1)
                {
                    if(-not [int]::TryParse($splits[1], [ref]$mask))
                    {
                        Write-Warning "Invalid mask value in $part"
                    }
                }
                $splits = $splits[0].Split(':')
                $domainName = $source
                if($splits.Length -gt 1)
                {
                    $domainName = $splits[1]
                }
                $start = 1
                if($part[0] -eq '+')
                {
                    $start++
                }

                $record.Entries += [SpfEntry]::new('a', $part.Substring($start).Replace(':',''))
                if($mask -eq -1)
                {
                    [SpfRecord]::ParseAMechanism($domainName, $part, [ref]$record)
                }
                else {
                    [SpfRecord]::ParseAWithMaskMechanism($domainName, $mask, $part, [ref]$record)
                }
            }
            elseif($continueParsing -and ($part.StartsWith('mx') -or $part.startsWith('+mx')))
            {
                $mask = -1
                $splits = $part.Split('/')
                if($splits.Length -gt 1)
                {
                    if(-not [int]::TryParse($splits[1], [ref]$mask))
                    {
                        Write-Warning "Invalid mask value in $part"
                    }
                }
                $splits = $splits[0].Split(':')
                $domainName = $source
                if($splits.Length -gt 1)
                {
                    $domainName = $splits[1]
                }
                $start = 2
                if($part[0] -eq '+')
                {
                    $start++
                }
                $record.Entries += [SpfEntry]::new('mx', $part.Substring($start).Replace(':',''))

                $mx = [Dns]::GetRecord($domainName, [Microsoft.DnsClient.Commands.RecordType]::MX)
                foreach($rec in $mx)
                {
                    if($null -eq $rec) {continue}
                    $domainName = $rec -as [string]
                    if($null -eq $domainName) {continue}
                    if($mask -eq -1)
                    {
                        [SpfRecord]::ParseAMechanism($domainName, $part, [ref]$record)
                    }
                    else {
                        [SpfRecord]::ParseAWithMaskMechanism($domainName, $mask, $part, [ref]$record)
                    }
                }
            }
            elseif($part -eq 'all' -or $part -eq '+all')
            {
                $record.FinalAction = [SpfAction]::Pass
                $continueParsing = $false
            }
            elseif($part -eq '-all')
            {
                $record.FinalAction = [SpfAction]::Fail
                $continueParsing = $false
            }
            elseif($part -eq '~all')
            {
                $record.FinalAction = [SpfAction]::SoftFail
                $continueParsing = $false
            }
            elseif($part -eq '?all')
            {
                $record.FinalAction = [SpfAction]::Neutral
                $continueParsing = $false
            }
            #Modifiers
            elseif($part.StartsWith('redirect='))
            {
                $domainName = $part.Substring(9)
                $record.Entries += [SpfEntry]::new('redirect', $domainName)
                $additionalRecords = [Dns]::GetSpfRecord($domainName)
                $retVal+=$additionalRecords
            }
            elseif($part.StartsWith('exp='))
            {
                $domainName = $part.Substring(4)
                $record.Entries += [SpfEntry]::new('exp', $domainName)
            }
        }
        
        return $retVal
    }

    static [void] ParseAMechanism([string]$domain, [string]$rawEntry, [ref]$record) {
        $records = [Dns]::GetRecord($domain, [Microsoft.DnsClient.Commands.RecordType]::A_AAAA)
        foreach($rec in $records)
        {
            if($null -eq $rec) {continue}
            $ip = $rec -as [System.Net.IPAddress]
            if($null -eq $ip) {continue}
            $record.Entries += [SpfIpAddress]::new("$domain $rawEntry", $ip)
        }
    }

    static [void] ParseAWithMaskMechanism([string]$domain, [int]$mask, [string]$rawEntry, [ref]$record) {
        $records = [Dns]::GetRecord($domain, [Microsoft.DnsClient.Commands.RecordType]::A_AAAA)
        foreach($rec in $records)
        {
            if($null -eq $rec) {continue}
            $ip = $rec -as [System.Net.IPAddress]
            if($null -eq $ip) {continue}
            $record.Entries += [SpfIpNetwork]::new("$domain $rawEntry", $ip, $mask)
        }
    }
}
#endregion Classes
#region Public commands
function Get-SPFRecord
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$Domain
    )

    process
    {
        $spfRecords = [Dns]::GetSpfRecord($domain)
        foreach($spfRecord in $spfRecords)
        {
            [SpfRecord]::Parse($domain, $spfRecord)
        }
    }    
}
function Get-SpfRecordEntries
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [SpfRecord]$SpfRecord,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'DomainName')]
        [string]$Domain,
        [switch]$IncludeIpAddresses,
        [switch]$IncludeIpNetworks
    )

    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'DomainName')
        {
            Write-Verbose "Processing $Domain"
            [SpfRecord[]]$record = Get-SpfRecord -Domain $Domain `
        }
        else
        {
            $record = $SpfRecord
        }
        Write-Verbose "Processing $record"
        $record.Entries | Where-Object{$_ -is [SpfEntry]}
        if($IncludeIpAddresses)
        {
            $record.Entries | Where-Object{$_ -is [SpfIpAddress]}
        }
        if($IncludeIpNetworks)
        {
            $record.Entries | Where-Object{$_ -is [SpfIpNetwork]}
        }
    }    
}
function Get-SpfRecordIpAddress
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [SpfRecord]$SpfRecord,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'DomainName')]
        [string]$Domain
    )

    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'DomainName')
        {
            Write-Verbose "Processing $Domain"
            [SpfRecord[]]$record = Get-SpfRecord -Domain $Domain 
        }
        else
        {
            $record = $SpfRecord
        }

        Write-Verbose "Processing $record"
        $record.Entries | Where-Object { $_ -is [SpfIpAddress] }
    }
}
function Get-SpfRecordIpNetwork
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [SpfRecord]$SpfRecord,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'DomainName')]
        [string]$Domain
    )

    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'DomainName')
        {
            Write-Verbose "Processing $Domain"
            [SpfRecord[]]$record = Get-SpfRecord -Domain $Domain 
        }
        else
        {
            $record = $SpfRecord
        }

        Write-Verbose "Processing $record"
        $record.Entries | Where-Object { $_ -is [SpfIpNetwork] }
    }
}
function Test-SpfHost
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [string]$Domain,
        [Parameter(Mandatory)]
        [string]$Address,
        [Parameter()]
        [string]$SenderAddress
    )

    process
    {
        $ip = [System.Net.IPAddress]::Parse($Address)
        $spfRecords = Get-SPFRecord -Domain $Domain
        foreach($record in $spfRecords)
        {
            $record `
            | Get-SpfRecordIpAddress `
            | Where-Object { $_.Address -eq $ip }

            $record `
            | Get-SpfRecordIpNetwork `
            | Where-Object { $_.Contains($ip) }

            $record.Entries `
            | Where-Object { $_.Prefix -eq 'exists' } `
            | ForEach-Object {
                $macro =  Expand-SpfMacro -Macro $_.Value -Domain $Domain -IpAddress $ip -SenderAddress $SenderAddress
                if($macro -match '%{.' ) {
                    throw "Unsupported macro $macro after expansion of $( $_.Value )"
                }
                try {
                    [Dns]::GetRecord($macro, [Microsoft.DnsClient.Commands.RecordType]::A)
                }
                catch {
                    #silently ignore not found expanded macro
                }
            }
        }
    }
}
function Test-SpfRecord
{
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$RawRecord,
        [Parameter(Mandatory)]
        [string]$Domain
    )

    process
    {
        [SpfRecord]::Parse($Domain, $RawRecord)
    }
}
#endregion Public commands
#region Internal commands
function Expand-SpfMacro
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$Macro,
        [Parameter(Mandatory)]
        [string]$Domain,
        [Parameter(Mandatory)]
        [System.Net.IPAddress]$IpAddress,
        [Parameter()]
        [string]$SenderAddress
    )

    process
    {
        $senderValid = [string]::IsNullOrEmpty($SenderAddress) -eq $false
        if($senderValid) {
            $senderParts = $SenderAddress.Split('@')
            $senderValid = $senderParts.Count -eq 2
        }
        if($macro -match '%{i}') {
            $dottedIp = [IpHelper.IPAddressExtensions]::ToDotted($IpAddress)
            $macro = $macro -replace '%{i}', $dottedIp
        }
        if($macro -match '%{ir}') {
            $dottedIp = [IpHelper.IPAddressExtensions]::ToReverseDotted($IpAddress)
            $macro = $macro -replace '%{ir}', $dottedIp
        }
        if($macro -match '%{c}') {
            $macro = $macro -replace '%{c}', $IpAddress.ToString()
        }
        if($macro -match '%{d}') {
            $macro = $macro -replace '%{d}', $Domain
        }
        if($macro -match '%{h}') {
            #we assume here that domain is a HELO domain
            $macro = $macro -replace '%{h}', $Domain
        }
        if($macro -match '%{s}' -and $senderValid) {
            $macro = $macro -replace '%{s}', $SenderAddress
        }
        if($macro -match '%{l}' -and $senderValid) {
            $macro = $macro -replace '%{l}', $senderParts[0]
        }
        if($macro -match '%{o}' -and $senderValid) {
            $macro = $macro -replace '%{o}', $senderParts[1]
        }
        if($macro -match '%{v}') {
            if($IpAddress.AddressFamily -eq 'InterNetwork') {
                $macro = $macro -replace '%{v}', 'in-addr'
            }
            else {
                $macro = $macro -replace '%{v}', 'ipv6'
            }
        }
        return $macro
    }
}
#endregion Internal commands

# SIG # Begin signature block
# MIIt9gYJKoZIhvcNAQcCoIIt5zCCLeMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAOlBwWUcgJC1oT
# JeihCECRc/NfAjili8tv6RiCephhVKCCE2AwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggcUMIIE/KADAgECAhAP9xCe9qf4ax3LBs7uih/sMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjMxMTA4MDAwMDAwWhcNMjYxMDAxMjM1OTU5WjCBnDET
# MBEGCysGAQQBgjc8AgEDEwJDWjEdMBsGA1UEDwwUUHJpdmF0ZSBPcmdhbml6YXRp
# b24xETAPBgNVBAUTCDA0OTIzNjkzMQswCQYDVQQGEwJDWjEOMAwGA1UEBxMFUHJh
# aGExGjAYBgNVBAoTEUdyZXlDb3JiZWwgcy5yLm8uMRowGAYDVQQDExFHcmV5Q29y
# YmVsIHMuci5vLjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJ8t/Qga
# dJKtGC7EqH4pmIU73fInH+j1scmVnrJtXL8tGlKzWZ7qlWDWOJBR3owF9CVqL4IX
# BGImH8Miowj6RKKqhEe9UtxiH5ipV6msnzAjTFkwqR9vjfEm9vrU1JuXWvAWAfYx
# qYg92oyCEBDQxpURpZmqAVSBy9U/ScDwE4NykZGzb0oYSPtzStd8RJvtUkc4126w
# YKMbVe/kdY1mDbKO9DLfpbSIj3vghrH6XeHwEb7/jAVYI7Vl+jUyyqfmYHD7FldQ
# X2fZfwvoGSibY1uWvvP0/vm0yd6uDbDjCDOTQW8Lxl5wvlXEf5ewn2oaPSoa6ov3
# 1XmnxL5iT8c1LM06JFCwfHS9e0NSyNr86IiKaxQO9/MANrYciTicObtD3cBcSRDO
# pEUfhc4TvA5DQZaakSduVJWPdMhxQs9iWeYMOzh5NDTB3xAx8eLBn7Uj++hjI3FQ
# WGEPw4Ew6WoDsJShU0HemlDJGTPW9EZSWHGdNFr1BxXEPb4F7DbjJZn33QIDAQAB
# o4ICAjCCAf4wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0O
# BBYEFP2yViJvcgO05qXIH6aJSXB/QcEhMD0GA1UdIAQ2MDQwMgYFZ4EMAQMwKTAn
# BggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB
# /wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBP
# hk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0
# MDk2U0hBMzg0MjAyMUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu
# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcN
# AQELBQADggIBADCe9Fh40HN9RneCehz5MrBy4O9WYsYCMJ7qJ9DsBT+Hed98UOKB
# k/XjgSLfsj5eZRHRmz3HzhGDK1PaRI+yIUVQx96a4qL7adktmrHex3fW39Iq+tPB
# rHtiEIp9rwunATeZpk+876u0AXYD1VDRWCtkL8zwZU0oqL6U/mWEIXzkryCB5N3x
# xtE54jMmW7MKi1+To4yQcrK3zQ394e2dr50L+aF2fgJ5mo1/YJvzyLLhigbqpoYG
# U/gjZonhNJXUaYogpHSTgUaBRlIKZ5xCnrFfJlOsbkhex4QAcdkU6XC+XyYfEQka
# 7ERwgxmEoRT3NlZ8/EbrQxJP4S1H8Z29M4D3L6rXNXXmv0IbfA9FQcqEco3Y3tRW
# dgdcFEwJmYTo0mCZrYTJHgkKW8xDvQ5BJISAp/ydOX5tSa71ojx1/Kp7qizqjBN/
# W77jdqJ89N1y+N/SOiHOCH9NO5pDLsHpTWW/arvjZT0I8dVYkqK0V39rh95XELI+
# NwBZvV4AsKLirjrkZU3pwCz6O99VmPkBqp9TA5wl13NdTpDHuQ6QyVT7hbC8LF5p
# z6x/xO/+tEGxG+1A31UTJPmkxhhUlR+NE3ZXiXhcG72CFHYUUvqwlThPkFYe4Ygf
# j9ADmss08k0JhVU5rkbrC2h+549HPlFu/XOSIrps4SXzInjHPEYuBETzMYIZ7DCC
# GegCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x
# QTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQw
# OTYgU0hBMzg0IDIwMjEgQ0ExAhAP9xCe9qf4ax3LBs7uih/sMA0GCWCGSAFlAwQC
# AQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwG
# CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI
# hvcNAQkEMSIEIOCABMuOSkDdKMtGFgnC2dql8EixMxspPzcT2s4cBqrFMA0GCSqG
# SIb3DQEBAQUABIIBgIgIIFWA3WsWiZ6nCFZ4HVwwBqOMTGQ8sNk+jXd6wocXrrue
# 3Eic3LPr+SSmfSeofqzlH7Jp4m5h8mmhJVDLhiGU/PeW9sE2anolVh4c21SmoV0k
# FmR4iMPz0EcEDzGl3rxckNLPCDGDHHNljJYTA7040FRJYmJZM/psHk857MeSevqp
# Eo9h6ZvFqt+OLh6xt28gTI9gezy1G3eVJVsgYp4ieTt9lwWCBB4YUMSD8tWnuXJg
# Nku349/NUWQmQXyO4TyvJ5c1M4HhSYctkVB/sqohxrwjlAAqmKFCW3jfnaNBbgRq
# dg7CnWLXwDOiGBLPLlb6yrvAMQCApNKLhGwPM6rK3gJmYidKFK3M7hqeJDXD4J/D
# JytK6+fUlm7WLju1ePi4PJGJgZDf1lbvfnMHzRrfNvWUwp3PuVg4aI1E5ZSxrOZc
# 9CcjSZQ+OX5mWV1ZCsR46rFANKcJFWIGVBfyAKVjAVpo1gly8op+C87BPX914jmu
# UbgQF87RtyBNWqtDQqGCFzkwghc1BgorBgEEAYI3AwMBMYIXJTCCFyEGCSqGSIb3
# DQEHAqCCFxIwghcOAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZIhvcNAQkQAQSg
# aARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCCzDnT9api1kGcA
# DPhEApy/hvJkU8ImEL41KBIxTz/1bwIQav58ArxzW+M5YOLY6iDsEhgPMjAyNDEy
# MjgxNzM3MzhaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+Vh8b56QTjMwQwDQYJ
# KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2
# IFRpbWVTdGFtcGluZyBDQTAeFw0yNDA5MjYwMDAwMDBaFw0zNTExMjUyMzU5NTla
# MEIxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEgMB4GA1UEAxMXRGln
# aUNlcnQgVGltZXN0YW1wIDIwMjQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
# AoICAQC+anOf9pUhq5Ywultt5lmjtej9kR8YxIg7apnjpcH9CjAgQxK+CMR0Rne/
# i+utMeV5bUlYYSuuM4vQngvQepVHVzNLO9RDnEXvPghCaft0djvKKO+hDu6ObS7r
# JcXa/UKvNminKQPTv/1+kBPgHGlP28mgmoCw/xi6FG9+Un1h4eN6zh926SxMe6We
# 2r1Z6VFZj75MU/HNmtsgtFjKfITLutLWUdAoWle+jYZ49+wxGE1/UXjWfISDmHuI
# 5e/6+NfQrxGFSKx+rDdNMsePW6FLrphfYtk/FLihp/feun0eV+pIF496OVh4R1Tv
# jQYpAztJpVIfdNsEvxHofBf1BWkadc+Up0Th8EifkEEWdX4rA/FE1Q0rqViTbLVZ
# Iqi6viEk3RIySho1XyHLIAOJfXG5PEppc3XYeBH7xa6VTZ3rOHNeiYnY+V4j1XbJ
# +Z9dI8ZhqcaDHOoj5KGg4YuiYx3eYm33aebsyF6eD9MF5IDbPgjvwmnAalNEeJPv
# IeoGJXaeBQjIK13SlnzODdLtuThALhGtyconcVuPI8AaiCaiJnfdzUcb3dWnqUnj
# XkRFwLtsVAxFvGqsxUA2Jq/WTjbnNjIUzIs3ITVC6VBKAOlb2u29Vwgfta8b2ypi
# 6n2PzP0nVepsFk8nlcuWfyZLzBaZ0MucEdeBiXL+nUOGhCjl+QIDAQABo4IBizCC
# AYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMB8GA1Ud
# IwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSfVywDdw4oFZBm
# pWNe7k+SH3agWzBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5n
# Q0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1w
# aW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQA9rR4fdplb4ziEEkfZQ5H2Edub
# Tggd0ShPz9Pce4FLJl6reNKLkZd5Y/vEIqFWKt4oKcKz7wZmXa5VgW9B76k9NJxU
# l4JlKwyjUkKhk3aYx7D8vi2mpU1tKlY71AYXB8wTLrQeh83pXnWwwsxc1Mt+FWqz
# 57yFq6laICtKjPICYYf/qgxACHTvypGHrC8k1TqCeHk6u4I/VBQC9VK7iSpU5wlW
# jNlHlFFv/M93748YTeoXU/fFa9hWJQkuzG2+B7+bMDvmgF8VlJt1qQcl7YFUMYgZ
# U1WM6nyw23vT6QSgwX5Pq2m0xQ2V6FJHu8z4LXe/371k5QrN9FQBhLLISZi2yemW
# 0P8ZZfx4zvSWzVXpAb9k4Hpvpi6bUe8iK6WonUSV6yPlMwerwJZP/Gtbu3CKldMn
# n+LmmRTkTXpFIEB06nXZrDwhCGED+8RsWQSIXZpuG4WLFQOhtloDRWGoCwwc6ZpP
# ddOFkM2LlTbMcqFSzm4cd0boGhBq7vkqI1uHRz6Fq1IX7TaRQuR+0BGOzISkcqwX
# u7nMpFu3mgrlgbAW+BzikRVQ3K2YHcGkiKjA4gi4OA/kz1YCsdhIBHXqBzR0/Zd2
# QwQ/l4Gxftt/8wY3grcc/nS//TVkej9nmUYu83BDtccHHXKibMs/yXHhDXNkoPId
# ynhVAku7aRZOwqw6pDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJ
# KoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQg
# VHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVow
# YzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQD
# EzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGlu
# ZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklR
# VcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54P
# Mx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupR
# PfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvo
# hGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV
# 5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYV
# VSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6i
# c/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/Ci
# PMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5
# K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oi
# qMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuld
# yF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAG
# AQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAW
# gBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDow
# OKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRS
# b290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkq
# hkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvH
# UF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0M
# CIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCK
# rOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rA
# J4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZ
# xhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScs
# PT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1M
# rfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXse
# GYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWY
# MbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYp
# hwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPww
# ggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9v
# dCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYT
# AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy
# dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskh
# PfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIP
# Uh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvu
# INXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59U
# WI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4
# AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJoz
# QL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw
# 4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sE
# AMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZD
# pBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsx
# xcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+Y
# HS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
# BBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
# pyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJ
# KoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqka
# uyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP
# +fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8Lpuny
# NDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiE
# n2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4
# VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggN2MIIDcgIBATB3MGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0ECEAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAIBBQCggdEwGgYJKoZIhvcN
# AQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNDEyMjgxNzM3Mzha
# MCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTVhltFMC8G
# CSqGSIb3DQEJBDEiBCB6Ei1XW1/koB9QWdfyvxc12s9jp05Sl4A7Au3qJHX7rzA3
# BgsqhkiG9w0BCRACLzEoMCYwJDAiBCB2dp+o8mMvH0MLOiMwrtZWdf7Xc9sF1mW5
# BZOYQ4+a2zANBgkqhkiG9w0BAQEFAASCAgCbkgpG0R0xWKOKJnVXzhdXhQfEAjNG
# 406E7k9UCF1azF9mglBwTo//sdyZSVfTa6CEOuaZRVz+kL6Nr4GAhW/w2Kf0f/AT
# pnQl1u1B2yAyeqz+XUmpDi8E81cy4LSUIIHtb6rd3cLE/cuj/yJwevYe9pIaJsXs
# zv4cZMNVf8v07KgNO1sYnZwZYg8YcqUkjwxhvNCK7zrW99w3HQRbvY3K3+mG7o0d
# d4CM8R9+u4GNJFBu+eR3Wkg3RPCpcbk+Y+l25YnRqZ+8daJHArcvH8N7ZxjG0+Bs
# T6atLeJ3v94exqxvRszJjKoS64C9BDPGM2yPVQFWPJ5OIAVVDUOowgbFyz+YeQyf
# 0VxkGFEESOhQv/x5zcmy5Yo9HAD1PnpGDMwalLPi8fg31clNsSVukrY7CghCG/hM
# f+IA4x4ThlRvooet4oaRz2OhUXf/3rCK9bt9dgon3ZFdY5oY9/sHccu1qwhXpgAw
# U9MygyHNy5HN+TMrbbVsyRJ79p3IC8vH5nCbBOtGBveYHk2p2zzsj59fjGzgClii
# Drg5/2Im5EPvc/GQBZF4iKrMaihqjRa5gPCgDDlTuVCOQsXlC160KPrEgSoPrkrl
# KEJlBHYIVj++ynf0lYsW6QXabo8yNYAGBaIUZ61LtCVoJHn2sSfzhhqDAYSlnt8p
# 6UDsfzkGXt/MGQ==
# SIG # End signature block