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]$Source
    [string]$Prefix
    [string]$Value

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

    [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)
                $prefix = $part.Substring(0, 3)
                $record.Entries += [SpfEntry]::new($source, $prefix, $ip)
                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($source, 'include', $domainName)
                if($retval.source -notcontains $domainName)
                {
                    $additionalRecords = [Dns]::GetSpfRecord($domainName)
                    foreach($additionalRecord in $additionalRecords)
                    {
                        $retVal += [SpfRecord]::Parse($domainName, $additionalRecord)
                    }
                }
                else
                {
                    Write-Warning "Cyclic reference: $domainName"
                }
            }
            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($source, $splits[0], $splits[1])
                }
                else
                {
                    $record.Entries += [SpfEntry]::new($source, $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($source, '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($source, 'mx', $part.Substring($start).Replace(':',''))

                $mx = [Dns]::GetRecord($domainName, [DnsClient.QueryType]::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($source, 'redirect', $domainName)
                $additionalRecords = [Dns]::GetSpfRecord($domainName)
                foreach($additionalRecord in $additionalRecords)
                {
                    $retVal += [SpfRecord]::Parse($domainName, $additionalRecord)
                }
            }
            elseif($part.StartsWith('exp='))
            {
                $domainName = $part.Substring(4)
                $record.Entries += [SpfEntry]::new($source, 'exp', $domainName)
            }
        }
        
        return $retVal
    }

    static [void] ParseAMechanism([string]$domain, [string]$rawEntry, [ref]$record) {
        $records = [Dns]::GetRecord($domain, [DnsClient.QueryType]::A)
        $records += [Dns]::GetRecord($domain, [DnsClient.QueryType]::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, [DnsClient.QueryType]::A)
        $records += [Dns]::GetRecord($domain, [DnsClient.QueryType]::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
{
<#
.SYNOPSIS
    Retrieves and parses SPF record for domain
 
.DESCRIPTION
    This command takes TXT records from provided domain, selects record representing SPF and parses it.
    Multi-string TXT records are concatenated into single string before parsing.
    In case record contains include method, additional records are retrieved and parsed as well, so output of this command is array of parsed SPF records.
.OUTPUTS
    SpfRecord[]
 
.EXAMPLE
Get-SpfRecord -Domain 'microsoft.com'
 
Description
-----------
Retrieves and parses SPF record for microsoft.com domain
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$Domain
    )

    process
    {
        $spfRecords = [Dns]::GetSpfRecord($domain)
        foreach($spfRecord in $spfRecords)
        {
            [SpfRecord]::Parse($domain, $spfRecord)
        }
    }    
}
function Get-SpfRecordEntries
{
<#
.SYNOPSIS
    Retrieves SPF record for domain, or takes parsed SPF record and parses it
 
.DESCRIPTION
    This command retrieves SPF record for domain, or takes raw SPF record and parses it. Returns only entries of type SpfEntry from parsed record.
    SpfEntry represents parsed token from SPF record, like ip4, ip6, mx, a, include, redirect, exp, etc. It also contains information about SPF record it was parsed from.
.OUTPUTS
    SpfEntry[]
 
.EXAMPLE
Get-SpfRecordEntries -Domain 'microsoft.com'
 
Description
-----------
Retrieves and parses SPF record for microsoft.com domain
 
.EXAMPLE
Test-SpfRecord -RawRecord 'v=spf1 include:spf.protection.outlook.com -all' -Domain 'mydomain.com' | Get-SpfRecordEntries
 
Description
-----------
Retrieves and parses raw SPF record for domain mydomain.com and returs parsed entries
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>

    [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 [SpfEntry]}
    }    
}
function Get-SpfRecordIpAddress
{
<#
.SYNOPSIS
    Retrieves SPF record for domain, or takes parsed SPF record and returns only IP addresses from it
 
.DESCRIPTION
    This command retrieves SPF record for domain, or takes raw SPF record and parses it. Returns IPv6 or IPv6 addresses from parsed record.
    SpfIpAddress represents parsed IP address from SPF record from ip4, ip6, mx, a, include and redirect record entries. It also contains information about SPF record it was parsed from.
.OUTPUTS
    SpfIpAddress[]
 
.EXAMPLE
Get-SpfRecord -Domain 'microsoft.com' | Get-SpfRecordIpAddress
 
Description
-----------
Retrieves IP addresses authorized for use with microsoft.com domain
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>

[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
{
<#
.SYNOPSIS
    Retrieves SPF record for domain, or takes parsed SPF record and returns only IP networks from it
 
.DESCRIPTION
    This command retrieves SPF record for domain, or takes raw SPF record and parses it. Returns IPv6 or IPv6 networks from parsed record.
    SpfIpNetwork represents parsed IP network from SPF record from ip4, ip6, include and redirect record entries. It also contains information about SPF record it was parsed from.
.OUTPUTS
    SpfIpNetwork[]
 
.EXAMPLE
Get-SpfRecord -Domain 'microsoft.com' | Get-SpfRecordIpNetwork
 
Description
-----------
Retrieves IP networks authorized for use with microsoft.com domain
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>

    [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
{
<#
.SYNOPSIS
    Tests IP address and sender against policy defined by SPF record
.DESCRIPTION
    This command tests IP address and optional sender to test them with SPF policy defined for domain or defined by SPF record. Command returns entries from SPF record that authorize or deny given IP address and sender.
    Sender information is only used if SPF record contains macros in exists entry that require it.
    Command basically provides the same functionality as SPF test tools like https://www.kitterman.com/spf/validate.html
.OUTPUTS
    SpfIpAddress[]
    SpfIpNetwork[]
    SpfEntry[]
 
.EXAMPLE
Get-SpfRecord -Domain 'microsoft.com' | Test-SpfHost -IpAddress '20.88.157.184'
 
Description
-----------
CHecks if IP address 20.88.157.184 is authorized to send email on behalf of microsoft.com
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>


[CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [SpfRecord]$SpfRecord,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'DomainName')]
        [string]$Domain,
        [Parameter(Mandatory)]
        [string]$IpAddress,
        [Parameter()]
        [string]$SenderAddress
    )

    process
    {
        $ip = [System.Net.IPAddress]::Parse($IpAddress)
        if ($PSCmdlet.ParameterSetName -eq 'DomainName')
        {
            Write-Verbose "Processing $Domain"
            [SpfRecord[]]$spfRecords = Get-SpfRecord -Domain $Domain `
        }
        else
        {
            $spfRecords = @($SpfRecord)
        }
        Write-Verbose "Processing $record"
        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 $spfRecords[0].Source -IpAddress $ip -SenderAddress $SenderAddress
                if($macro -match '%{.' ) {
                    throw "Unsupported macro $macro after expansion of $( $_.Value )"
                }
                try {
                    [Dns]::GetRecord($macro, [DnsClient.QueryType]::A)
                }
                catch {
                    #silently ignore not found expanded macro
                }
            }
            $record.Entries `
            | Where-Object { $_.Prefix -eq 'include' } `
            | Where-Object { $_.Value -match '%{.' } `
            | ForEach-Object {
                $macro =  Expand-SpfMacro -Macro $_.Value -Domain $spfRecords[0].Source -IpAddress $ip -SenderAddress $SenderAddress
                if($macro -match '%{.' ) {
                    throw "Unsupported macro $macro after expansion of $( $_.Value )"
                }
                try {
                    $rawRecord = [Dns]::GetRecord($macro, [DnsClient.QueryType]::TXT)
                    if($null -ne $rawRecord)
                    {
                        $additionalRecord = [SpfRecord]::Parse($_.Source, $rawRecord)
                        $additionalRecord `
                        | Test-SpfHost -IpAddress $IpAddress -SenderAddress $SenderAddress
                    }
                }
                catch {
                    #silently ignore not found expanded macro
                }
            }
        }
    }
}
function Test-SpfRecord
{
<#
.SYNOPSIS
    Parses raw SPF record
.DESCRIPTION
    This command parses raw SPF record and returns parsed SPF record object. This is useful when constructing SPF record from scratch to test it.
.OUTPUTS
    SpfRecord[]
 
.EXAMPLE
Get-SpfRecord -Domain 'mydomain.com' -RawRecord 'v=spf1 include:spf.protection.outlook.com -all'
Description
-----------
CHecks if SPF record can be parsed correctly.
 
.LINK
More about SPF, see http://www.openspf.org/ and https://tools.ietf.org/html/rfc7208
#>


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
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDoB7ip82Tme3SL
# yEWubIYm4JxbZt+WdN0GBLUHl0NGYqCCE2AwggWQMIIDeKADAgECAhAFmxtXno4h
# 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
# hvcNAQkEMSIEIBIZQFE9AkKIIQQHqLCd7+lWRhSigzwcIm5I+zhtdLvmMA0GCSqG
# SIb3DQEBAQUABIIBgI5ko0Kc7dfeDzDd+N9C74Sx2Jq1T7ruCokPLAwIwUgi46x8
# bALkoCCWoe1p0laLbIgwhSOSyW6ipFOpRrWVugKy3ESvImf+i/PwzpDWiPZJ4zY5
# L0BNAUmaEn4EjJqIKSG55C7kIUA9MDZqwxXxdyhiVV3ytJEV0JMJitRLapj9i1p2
# x4lNa/ixtoyomZ35EQF6iIT9cK6BdXTIwsiLch3VGbbu/mq5D+M6aNMZY1LCsL6M
# cd2dc+xNDLhZsjuv7oRlziESM/d9UuoQ/JvEOEaQVXjkkJM31juB/IxXttHr9W1A
# SWNxUDlF+73QxL9Uy0bAqwhuquqwfusYX88+qxM6c7D3DM2AIQQC6e44CTYijJ46
# m0v1Ez8tS12xIPfFgIQBZJa5jOWpPqy4Z0cSk0P4FsMCicCOrtVXOIT1tQIjRUvY
# 2wy3GWUuIfZ++U+4buF6mKl/zK9zqullQHPu2kXV7rJPoB6fLNxGaE8jzbFb0Meu
# 4esna+H6Oova6EiPTaGCFzkwghc1BgorBgEEAYI3AwMBMYIXJTCCFyEGCSqGSIb3
# DQEHAqCCFxIwghcOAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZIhvcNAQkQAQSg
# aARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCArf5+h4DUo0XQN
# JlWr5gVBfPV7CLFNb6NBbiYoY1TPeQIQLi5H3BvGvr+92jQaFvinHRgPMjAyNTAx
# MTAwNzI3NTVaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+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
# AQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAxMTAwNzI3NTVa
# MCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTVhltFMC8G
# CSqGSIb3DQEJBDEiBCCTm5yEBQDUtXx1e9Mrk6pB4P1QnE++W4RvfBDYet+bRzA3
# BgsqhkiG9w0BCRACLzEoMCYwJDAiBCB2dp+o8mMvH0MLOiMwrtZWdf7Xc9sF1mW5
# BZOYQ4+a2zANBgkqhkiG9w0BAQEFAASCAgCy1x+6Q27VOyGBKqwquqLCBRLbSm50
# /7Oe2rO7kLyic5EYJpAyyG4rclIC5TYvDCFTAqsBxO9Ub0fRDuvgqseRAzkLb/M1
# tDBVvHVBo1XOP7pW4MWWxyQBfJNX8YDR84Xu9HpS+AHoYfgfoPLc8xR2PQqzv99A
# WU2tNVCeMB61Una0QqyS1H87fqyxe5iSmTp+jAg1qcvphmguZEY01Kq5dsTPkJms
# +MIlYv9acyIp1iB5LfMCdQH4X7JjL1xoAyVAqrWhqMiEpdWOfy1SwQkcwCq6k0d8
# 5hHIEz+S48HR07tsYNsedFsgbvMHVjdLnHrilvcn/VUq9kRRli4XGIVtfIjKzq2E
# f9TDXwRNOeovP6u5ze7PtG+aHL21K648cWThh74DGCwPWUViGWqqi4uBduH5ytUp
# FLxH7IfZ5+cg8Tem5eFZmncLM0hYF38Vau4vRV11mluCiPmUJv8k4vyWfGXwfepc
# 1S3i4681/veROlxezbNHDgm/TEfaD1g3VKRnlPiTkn4zwaCGrfMEJLp6ZvmsXrIT
# rLUcGBlMu30mMndFCj8WUfhU7o4XynfSSbC3QkHbMPOOKK6zxNYQhL0TBScW145+
# gXs7DZz3y4Avsip8QwR6/imEHBiiEVW+lL/YfxJqGMYFtNKH6XPnboZs8RdnrWTm
# ZJdsx33yw5pYHA==
# SIG # End signature block