UMN-Infoblox.psm1

###
# Copyright 2017 University of Minnesota, Office of Information Technology

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Foobar. If not, see <http://www.gnu.org/licenses/>.
## Modules for infoblox
# It is assumed that a Connection has already been established and the cookie is being passed to many of these functions.
# Use 'Connect-Infoblox' to return a $cookie --

#region Connect-Infoblox
Function Connect-Infoblox{
<#
    .SYNOPSIS
        Core function - retrieve cookie for API access to infoblox
    .DESCRIPTION

    .PARAMETER InfobloxCred
        PS credential of user that has access

    .PARAMETER uriBase
        FQDN of infoblox host including version, example 'https://myhost.mycompany.com/wapi/v1.7.1'

    .PARAMETER SkipCertificateCheck
        Ignore bad SSL Certificates

    .EXAMPLE
        Connect-Infoblox -InfobloxCreds $infobloxCreds -uriBase $uriBase
    .NOTES
        For legacy automation systems dealing with cookies -
        -UseBasicParsing is included on the InvokeWebRequest - needed parsing for Orchestrator
#>



    [CmdletBinding()]
    param(

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]$InfobloxCreds,

        [parameter(Mandatory)]
        [string]$uriBase,

        [switch]$SkipCertificateCheck
    )

     if ($SkipCertificateCheck -and $PSVersionTable.PSVersion.Major -lt 6){
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@

            [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
    }
            $uri  = "$uriBase/grid"
            if ($SkipCertificateCheck -and $PSVersionTable.PSVersion.Major -eq 6){$grid = Invoke-WebRequest -uri $uri -Method Get -Credential $InfobloxCreds -SessionVariable cookie -UseBasicParsing -SkipCertificateCheck}
            else{$grid = Invoke-WebRequest -uri $uri -Method Get -Credential $InfobloxCreds -SessionVariable cookie -UseBasicParsing}
            return ($cookie)
}
#endregion

#region Get-InfobloxAlias
Function Get-InfobloxAlias{

<#
.SYNOPSIS
    Get Alias by FQDN
.DESCRIPTION

.EXAMPLE
    Get-InfobloxAlias -cookie $cookie -uriBase $uriBase -alias $alias
.NOTES
    General notes
#>


    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$alias
    )

    $uri  = "$uriBase/record:cname?name:=$alias"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie
}
#endregion

#region Get-InfobloxHost
Function Get-InfobloxHost{
<#
    .SYNOPSIS
        Get host by FQDN
    .DESCRIPTION
        Get host by FQDN
    .EXAMPLE
        Get-InfobloxHost -cookie $cookie -uriBase $uriBase -host_name $host_name
    .NOTES
        General notes
#>

    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$host_name
    )

    $host_name = $host_name.ToLower()
    $uri  = "$uriBase/record:host?name=$host_name&_return_fields=aliases,ipv4addrs,ipv6addrs"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie
}
#endregion

#region Get-InfobloxIPv4Net
Function Get-InfobloxIPv4Net{
<#
.SYNOPSIS
    Get IPv4 Network
.DESCRIPTION
    Get a network stack from infoblox
.EXAMPLE
    Get-InflobloxIPv4Net -cookie $cookie -uriBase $uriBase -ipv4Net $ipv4net
.NOTES
    General notes
#>


    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net
    )
    $uri = "$uriBase/network?network=$ipv4Net"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie
}
#endregion

#region Get-InfobloxIPv4IP
Function Get-InfobloxIPv4IP{
<#
.SYNOPSIS
    Get reference to specific IPv4 address
.DESCRIPTION

.EXAMPLE
    Get-InflobloxIPv4IP -cookie $cookie -uriBase $uriBase -ipv4Address $ipv4Address
.NOTES
    Check 'types' and look for HOST (NOT BULKHOST). If types contains HOST, its in use
#>



    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Address
    )

    $uri = "$uriBase/ipv4address?ip_address=$ipv4Address"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie

}
#endregion

#region Get-InfobloxIPv4IPs
Function Get-InfobloxIPv4IPs{
<#
.SYNOPSIS
    Get reference to specific IPv4 network
.DESCRIPTION
    Get all Hosts in a specific IPv4 Network
.EXAMPLE
    Get-InfobloxIPv4ips -cookie $cookie -uriBase $uriBase -ipv4net $ipv4net
.NOTES

#>


    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net
    )

    $uri = "$uriBase/ipv4address?network=$ipv4Net"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie

}
#endregion

#region Get-InfobloxIPv4Available
Function Get-InfobloxIPv4Available{

<#
.SYNOPSIS
    Get an available IPv4 address
.DESCRIPTION
    Check for, and retrieve an available IPv4 address
.EXAMPLE
    Get-InfobloxIP4Available -cookie $cookie -uriBase $uriBase -ipv4net $ipv4net -domainSuffix -$domainSuffix
.NOTES

#>



    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net,

        [ValidateNotNullOrEmpty()]
        [string]$domainSuffix
    )

    $ipv4IPs = Get-InfobloxIPv4IPs -cookie $cookie -uriBase $uriBase -ipv4Net $ipv4Net
    $ipFound = $false
    foreach ($ip in $ipv4IPs){
        if ($ip.types -notcontains "HOST" -and $ip.lease_state -eq "FREE") # IF it returns a single entry this should be the bulk host. < 1 = router, >1 its tied to host record so don't touch it
        {
            $ipFound = $true
            return $ip.ip_address
        }
    }
    if(-not $ipFound){return "unable to find IPv4 Address, exiting"}

}
#endregion

#region Get-InfobloxIPv6IP
Function Get-InfobloxIPv6IP{
<#
.SYNOPSIS
    Get reference to specific IPv6 address
.DESCRIPTION

.EXAMPLE
    Get-InflobloxIPv6IP -cookie $cookie -uriBase $uriBase -ipv6Address $ipv6Address
.NOTES

#>



    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv6address
    )

    $uri  = "$uriBase/record:host_ipv6addr?ipv6addr=$ipv6address"
    Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie

}
#endregion

#region Get-InfobloxIPbyMac
function Get-InfobloxIPbyMac {
    <#
    .SYNOPSIS
        Get IP information by MAC address
    .DESCRIPTION

    .EXAMPLE
        Get-InfobloxIPbyMac -cookie $cookie -uriBase $uriBase -ipv4net $ipv4net -mac $mac
    .NOTES
    #>


    [CmdletBinding()]
    param(

        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net,

        [ValidatePattern("([a-zA-Z0-9]{2}:){5}[a-zA-Z0-9]{2}")]
        [string]$mac

    )

    $uri = "$uriBase/ipv4address?network=$ipv4Net&mac_address=$mac"
    $ipv4IPs =  Invoke-RestMethod -uri $uri -Method Get -WebSession $cookie
    $activeIP = $ipv4IPs | Where-Object {$_.lease_state -eq 'ACTIVE'}
    if ($activeIP -ne $null){return ($activeIP.ip_address)}
    else{throw "Failed to get single IP info $ipv4IPs"}
}
#endregion

#region New-InfobloxHost
Function New-InfobloxHost{

<#
.SYNOPSIS
    Create a new Host object with IPv4 and IPv6 Address
.DESCRIPTION

.EXAMPLE
    New-InfobloxHost -cookie $cookie -uribase $uriBase -host_name $host_name -new_IPv4 $new_ipV4 -new_IPv6 $new_ipv6 -ipv4net $ipv4net
.NOTES
    Use other module commands to generate a list of IP's to associate to a new host entry.
#>




    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$host_name,

        [ValidateNotNullOrEmpty()]
        [string]$new_IPv4,

        [string]$new_IPv6,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net
    )
    $host_name = $host_name.ToLower()
    $uri = "$uriBase/record:host"
    if ($new_IPv6)
    {
        $JSON = @{name=$host_name;ipv4addrs=@(@{ipv4addr=$new_IPv4;configure_for_dhcp=$true;match_client="RESERVED"});
            ipv6addrs=@(@{ipv6addr=$new_IPv6});
            extattrs=@{Custom1=@{value=$ipv4Net};Custom2=@{value="default 2 hour"}}} | ConvertTo-Json
    }
    else
    {
        $JSON = @{name=$host_name;ipv4addrs=@(@{ipv4addr=$new_IPv4;configure_for_dhcp=$true;match_client="RESERVED"});
            extattrs=@{Custom1=@{value=$ipv4Net};Custom2=@{value="default 2 hour"}}} | ConvertTo-Json
    }
    Invoke-RestMethod -Uri $uri -Body $JSON -ContentType "application/json" -Method Post -WebSession $cookie

}
#endregion

#region New-InfobloxDhcpReservation
Function New-InfobloxDhcpReservation{
<#
.SYNOPSIS
    Create a new host object for doing DHCP reservations
.DESCRIPTION
    For use with DHCP reservations by mac address
.EXAMPLE
    New-InfobloxDhcpReservation -cookie $cookie -uriBase $uriBase -host_name $host_name -new_IPv4 $new_ipV4 -ipv4Net $ipv4net -mac $mac
.NOTES

#>

    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$host_name,

        [ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")]
        [string]$new_IPv4,

        [ValidateNotNullOrEmpty()]
        [string]$ipv4Net,

        [ValidatePattern("([a-zA-Z0-9]{2}:){5}[a-zA-Z0-9]{2}")]
        [string]$mac
    )
    $host_name = $host_name.ToLower()
    $uri = "$uriBase/record:host"
    $JSON = @{name=$host_name;ipv4addrs=@(@{ipv4addr=$new_IPv4;configure_for_dhcp=$true;match_client="MAC_ADDRESS";mac=$mac});
        extattrs=@{Custom1=@{value=$ipv4Net};Custom2=@{value="default 2 hour"}}} | ConvertTo-Json
    Invoke-RestMethod -Uri $uri -Body $JSON -ContentType "application/json" -Method Post -WebSession $cookie

}
#endregion

#region Remove-InfobloxHost
Function Remove-InfobloxHost{
<#
.SYNOPSIS
    Remove Host Record
.DESCRIPTION

.EXAMPLE
    Remove-InfobloxHost -cookie $cookie -uriBase $uriBase -host_name $host_name
.NOTES
    General notes
#>


  ##

    [CmdletBinding()]
    Param
    (
        [ValidateNotNullOrEmpty()]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [ValidateNotNullOrEmpty()]
        [string]$uriBase,

        [ValidateNotNullOrEmpty()]
        [string]$host_name
    )
    $host_name = $host_name.ToLower()
    ## Validate Host exists
    $ibHost = Get-InfobloxHost -cookie $cookie -uriBase $uriBase -host_name $host_name
    if ($ibHost -eq $null){throw "Infoblox Host Doesn't Exist"}
    # Aliases must be removed before removing the host record
    if ($ibHost.aliases){}## waiting for enough permissions to see all the aliases and remove them
    $host_ref = $ibHost._ref

    $uri  = "$uriBase/$host_ref"
    #validate response
    if ((Invoke-RestMethod -uri $uri -Method Delete -WebSession $cookie) -ne $host_ref){throw "Infoblox Delete Failed"}

}
#endregion

#region Set-InfobloxHostIPv6
Function Set-InfobloxHostIPv6 {
    <#
        .SYNOPSIS
            Set IPv6 Address of Host Record

        .DESCRIPTION
            Set IPv6 Address of Host Record

        .PARAMETER cookie
            See connect-infoblox function to get authentication cookie

        .PARAMETER host_name
            The FQDN Host name in infoblox to be updated.

        .PARAMETER ipv6address
            The IPv6 address to be set for an AAAA record.

        .PARAMETER uriBase
            The base URI for your infoblox instance, such as https://fqdn/wapi/version

        .EXAMPLE
            Set-InfobloxHostIPv6 -cookie $cookie -uriBase $uriBase -host_name $host_name -ipv6Address

        .NOTES
            General notes
    #>

        [CmdletBinding()]
        Param
        (
            [ValidateNotNullOrEmpty()]
            [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

            [ValidateNotNullOrEmpty()]
            [string]$ipv6Address,

            [ValidateNotNullOrEmpty()]
            [string]$uriBase,

            [ValidateNotNullOrEmpty()]
            [string]$host_name
        )

    Begin
        {
            $hostinfo = get-infobloxhost -cookie $cookie -uriBase $uriBase -host_name $host_name
            $hostref = $hostinfo._ref
            $uri = "$uriBase/$hostref"
            $JSON = @{ipv6addrs=@(@{ipv6addr=$ipv6Address})} | ConvertTo-Json
        }
    Process
        {
            $response = Invoke-RestMethod -Uri $uri -Body $JSON -ContentType "application/json" -Method Put -WebSession $cookie
        }
    End
        {
            return $response
        }
}
#endregion

Function Get-InfobloxNetwork {
    <#
    .SYNOPSIS
        Get a network
    .DESCRIPTION
        Get a specified object from the Infoblox

    .PARAMETER Cookie
        Infoblox web session cookie

    .PARAMETER Network
        CIDR to get or search

    .PARAMETER UriBase
        The base Uri for the Infoblox including API version

    .EXAMPLE
        Get-InfobloxNetwork -cookie $cookie -uriBase $uriBase -network '10.0.0.0/25'
        _ref : network/asdf98a7kh5897afj98f7i2uh982hfakufh:10.0.0.0/25/default
        network : 10.0.0.0/25
        network_view : default

    .EXAMPLE
        Get-InfobloxNetwork -cookie $cookie -uriBase $uriBase -network '10.50.'

        _ref : network/asdf98a7kh5897afj98f7i2uh982hfakufh:10.50.0.0/24/default
        network : 10.50.0.0/24
        network_view : default

        _ref : network/asdf98a7kh5897afj98f7i2uh982hfakufh:10.50.1.0/24/default
        network : 10.50.1.0/24
        network_view : default

        _ref : network/asdf98a7kh5897afj98f7i2uh982hfakufh:10.50.2.0/24/default
        network : 10.50.2.0/24
        network_view : default

        ...
    #>

    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [parameter(Mandatory)]
        [string]$Network,

        [parameter(Mandatory)]
        [string]$uriBase
    )

    Begin{
        $uri="$uriBase/network?network~=$network"}

    Process{
        $Return = Invoke-RestMethod -Method Get -Uri $uri -WebSession $cookie
    }
    End{
        return $return
    }
}

Function Get-InfobloxNetworkExtAttributes {
    <#
    .SYNOPSIS
        Get network extensible attributes

    .DESCRIPTION
        Get extension properties for a network

    .PARAMETER Cookie
        Infoblox web session cookie

    .PARAMETER Network
        CIDR to get properties for.
        Calls Get-InfobloxNetwork - will return multiple networks if a CIDR is not specific.

    .PARAMETER UriBase
        The base Uri for the Infoblox including API version

    .EXAMPLE
        $test = Get-InfobloxNetworkExtAttributes -cookie $cookie -uriBase $uriBase -network '10.50.0.0/24'

        $test.extattrs |format-list
        _ref : network/adsfa98sdf7a9osfj98ag7qouijht984jh9:10.50.0.0/24/default
        extattrs : @{Location=; Owner=}
        network : 10.50.0.0/24
        network_container : 10.50.0.0/16
        network_view : default

        $test.extattrs |format-list
        Location : @{value=AWS}
        Owner : @{value=HSS}
    #>

    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [parameter(Mandatory)]
        [string]$Network,

        [string]$properties = "extattrs,network,network_view,network_container,comment",

        [parameter(Mandatory)]
        [string]$uriBase
    )

    Begin{
        $networkRefs = Get-InfobloxNetwork -cookie $cookie -Network $network -uriBase $uriBase
        $allData = @()
    }
    Process{
        Foreach($item in $networkRefs){
            $ref, $uri = $Null, $Null
            $ref = $item._ref
            $uri = "$uriBase/$ref`?_return_fields=$properties"
            $test = Invoke-RestMethod -Method Get -Uri $uri -WebSession $cookie
            $allData+=$test
        }
    }
    End{
        return $allData
    }
}

Function Set-InfobloxNetworkExtAttributes{
    <#
    .SYNOPSIS
        Set extensible attributes

    .DESCRIPTION
        Get extension properties for a network

    .PARAMETER Cookie
        Infoblox web session cookie

    .PARAMETER ExtAttribute
        The extensible attribute to set. Provide a hashtable in format

        $extAttributes = @{
            "extattrs" = @{
                "$ExtAttributeKey" = @{
                    "value"="$ExtAttributeValue"};
                "$extAttributeKey2" = @{
                    "value"="$extAttributevalue2"
                }
            }
        }

        Add additional key value pairs as needed.

    .PARAMETER NetworkRef
        See get-infobloxNetwork to get object reference.

    .PARAMETER UriBase
        The base Uri for the Infoblox including API version.
    #>

    [cmdletbinding()]
    param(
        [parameter(Mandatory)]
        [Microsoft.PowerShell.Commands.WebRequestSession]$cookie,

        [parameter(Mandatory)]
        [string]$NetworkRef,

        [parameter(Mandatory)]
        [hashtable]$ExtAttribute,

        [parameter(Mandatory)]
        [string]$uriBase
    )

    Begin{
        $body = $ExtAttribute |convertto-json
        $uri = "$uriBase/$networkRef`?_return_fields=extattrs&_return_as_object=1"
    }
    Process{
        $return = Invoke-RestMethod -Method Put -Uri $uri -body $body -contentType 'application/json' -WebSession $cookie
        $results = $return.result
    }
    End{
        return $results
    }
}

Export-ModuleMember -Function *

# SIG # Begin signature block
# MIIfBwYJKoZIhvcNAQcCoIIe+DCCHvQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUzpdY0rPmmIpVIc1ySr9Xrg5q
# 5TGgghoTMIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B
# AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV
# BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU
# cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw
# NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs
# dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G
# A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF
# UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q
# gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x
# 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ
# w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH
# d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh
# 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT
# bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE
# JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P
# AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG
# A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz
# dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG
# GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C
# L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9
# 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf
# ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt
# pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd
# ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY
# Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G
# CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV
# BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv
# cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU
# VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0
# ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy
# MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg
# MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI
# vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca
# WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI
# upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n
# OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J
# AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf
# BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ
# yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
# AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud
# HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp
# cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw
# Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG
# CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB
# BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash
# U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T
# p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI
# 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh
# JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE
# 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb
# MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS
# R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
# T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg
# Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC
# R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y
# ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI
# QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
# MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn
# P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns
# d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8
# rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf
# dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9
# nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw
# FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y
# VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM
# MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB
# BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG
# MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j
# cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp
# Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo
# dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw
# pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4
# EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh
# dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi
# KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2
# V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF
# cvfqG5bilK+GLjCCBawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZI
# hvcNAQELBQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlB
# bm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24x
# JTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0
# MDAwMDAwWhcNMjAxMjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEM
# BTU1NDU1MRIwEAYDVQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlz
# MRgwFgYDVQQJDA8xMDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkg
# b2YgTWlubmVzb3RhMSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBv
# cnQxIDAeBgNVBAMMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucC
# XomNj9NYj8t+JfPna3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVai
# U5s5hG3vBhfRX8W1/2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4
# qmFGV9RJxtiLZH1qUldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y
# /new5+CBUGTAvrw6K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSp
# d75+hvw4Sl3HAaWZtoWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCC
# AdQwHwYDVR0jBBgwFoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4L
# EhElVUvT8n5txOJSNAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA
# MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAE
# XzBdMFsGDCsGAQQBriMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5p
# bmNvbW1vbi5vcmcvY2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRm
# MEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9J
# bkNvbW1vblJTQUNvZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggr
# BgEFBQcwAoY4aHR0cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNB
# Q29kZVNpZ25pbmdDQS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29t
# bW9uLXJzYS5vcmcwGQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcN
# AQELBQADggEBAENRlesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6i
# iRJcFVasGLUVFncdG1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3p
# aDy1sj4CADO2D0gnxcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/
# b/yblwA1fKXw4locUpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswu
# avB00T52Y+ZZmz3tMMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8h
# y8LgoY5UnGjX5jHsIvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5Q
# RYXzJiSABeLUMA0GCSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo
# ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0
# aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5
# NTlaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFy
# Ym9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYD
# VQQDExxJbkNvbW1vbiBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOY
# MlYjXAaGlw2A6O2igTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsrip
# hHMMzKuHRhxqx+85Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj7
# 8gw2qblessWBRyN9EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4Drk
# N3dRyJ59UopPgNwmucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJ
# mm2j/w2nP35MViPWCb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYw
# HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf/
# /wY9nFDgjCRlMx5wEIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/
# AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNV
# HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0
# UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8G
# CCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNB
# QWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVz
# dC5jb20wDQYJKoZIhvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4
# J8HHnQTJgjTbhuET98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh
# 26owAmvquMCF1pKiQWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6
# c9G1VNwLkl/po9ORPljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIq
# UDw3LsrdYWzgg4x06tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQ
# Asp05HUNCXyhznlVLr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5Mj
# vwTzuNorbwBv0DL+7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwf
# z/8bq3MhTiKOWEb04FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6
# Tv4WAVsr/i7LoXU72gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmx
# eO4u94196qIqJQl+8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGx
# CDkbSQf3npXeRFm0IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+
# AEOqnY98j2zRMYIEXjCCBFoCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT
# Ak1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8G
# A1UECxMISW5Db21tb24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25p
# bmcgQ0ECEHJNXiAT1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcC
# AQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYB
# BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFDLRFwYfyb7q
# bJEZ0RR8HGN+u8etMA0GCSqGSIb3DQEBAQUABIIBAFZs7QWSK02xTwoC4mZeZqQx
# yaffKrahgBJ3P59kPnD7dbBO6EaNCdPjUEHxqq8RMKcnJ/lBNdqS+2n5BrDUOUjJ
# /qQmeW+6CTW37kw3XuIsTWT7TwxSm4WUp8du5Zd83Y8bl8xHF6NQuMAiwKtHukq/
# qWUUrMXj93FCCo6uUfbnoWWZrQFxaY9JlcyV5DMkfslygLZLRnsMWyu+zDGV5F6O
# 2CwVVXAJCev6rUVMe/xbBd1TxqYPnQrfPSd8iF+Caza/Sighec09auxGrhE65HNq
# Jve7HtwO46k/mG5BLqZInMlqIIGS8xcgm9SwuU1Jb4pFR3SA/D/QPDdhuSOAuqKh
# ggIoMIICJAYJKoZIhvcNAQkGMYICFTCCAhECAQEwgY4wejELMAkGA1UEBhMCR0Ix
# GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEa
# MBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxIDAeBgNVBAMTF0NPTU9ETyBUaW1l
# IFN0YW1waW5nIENBAhArc9t0YxFMWlsySvIwV3JJMAkGBSsOAwIaBQCgXTAYBgkq
# hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDA0MzAyMTI0
# MTRaMCMGCSqGSIb3DQEJBDEWBBQ3Q7zNH+43jrdW3+KRFpR3SyXJgjANBgkqhkiG
# 9w0BAQEFAASCAQChUy2l90UrE8k3zvSUJkMtYGXkVjWeiT2vXV6iztlX7b8r4LMf
# 64C02dIaKql5/Boz2lnB+nhJ1ufrAhbDPm3Kv16Xrk5RsJkrbYdAY/5n2kGTBesX
# lT7WbLK3ztrcbdqN7JqCGsr+t1mUF9MyF5RVQm1uW/PTtMsbpHv4e0TN0Sq5XEu8
# Ip3tvwy8mBJUtoVuYUjBStoHDnHRwQW8kxbhJzvqQxvl+1eisxb1jI0SwaNZBani
# m0TtBSxM66FqiG2W03Hs5bxCgal4lf4TDMpJKZIhXUCFbmd0LLQUXfGASapG+d1g
# NPtYfa2o984dRPEDWUOdb/RaUpA55kpeCNiU
# SIG # End signature block