scripts/helpers.ps1
# HPE Nimble PowerShell Toolkit. # File: helpers.ps1 # Description: This file contains common helper routines. These functions are called by generated SDK Cmdlet functions. # # © Copyright 2017 Hewlett Packard Enterprise Development LP. function Connect-NSGroup { <# .SYNOPSIS Connects to a HPE Alletra 6000 and Nimble Storage group. .DESCRIPTION Connect-NSGroup is an advanced function that provides the initial connection to a HPE Alletra 6000 and Nimble Storage array so that other subsequent commands can be run without having to authenticate individually. It is recommended to ignore the server certificate validation (-IgnoreServerCertificate param) since it uses an untrusted SSL certificate. .PARAMETER Group The DNS name or IP address of the array group. .PARAMETER Credential Specifies a user account that has permission to perform this action. Type a user name, such as User01 or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, this function prompts you for a password. .PARAMETER IgnoreServerCertificate Ignore the server SSL certificate. .EXAMPLE Connect-NSGroup -Group nimblegroup.yourdns.local -Credential admin -IgnoreServerCertificate *Note: IgnoreServerCertificate parameter is not available with PowerShell Core .EXAMPLE Connect-NSGroup -Group 192.168.1.50 -Credential admin -IgnoreServerCertificate *Note: IgnoreServerCertificate parameter is not available with PowerShell Core .EXAMPLE Connect-NSGroup -Group nimblegroup.yourdns.local -Credential admin -ImportServerCertificate .EXAMPLE Connect-NSGroup -Group 192.168.1.50 -Credential admin -ImportServerCertificate .INPUTS None .OUTPUTS None .NOTES #> [cmdletbinding(DefaultParameterSetName='IgnoreServerCertificate')] param ( [Parameter(Mandatory,position=0)] [string]$Group, [Parameter(Mandatory,position=1)] $Credential=$null, [Parameter(ParameterSetName='ImportServerCertificate')] [switch]$ImportServerCertificate ) DynamicParam { if ($PSEdition -ne 'Core'){ $IgnoreServerCertificateAttribute = New-Object System.Management.Automation.ParameterAttribute $IgnoreServerCertificateAttribute.Mandatory = $false #$IgnoreServerCertificateAttribute.Position = 3 $IgnoreServerCertificateAttribute.ParameterSetName = 'IgnoreServerCertificate' $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($IgnoreServerCertificateAttribute) $IgnoreServerCertificateParam = New-Object System.Management.Automation.RuntimeDefinedParameter('IgnoreServerCertificate', [Switch],$attributeCollection) $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('IgnoreServerCertificate', $IgnoreServerCertificateParam) return $paramDictionary } } Process{ if ($Credential -is [String]) { $Credential = Get-Credential $Credential } $global:Group=$Group [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($PSBoundParameters.IgnoreServerCertificate) { $Global:NimbleStorageIgnoreServerCertificate = $true; IgnoreServerCertificate} else { $Global:NimbleStorageIgnoreServerCertificate = $false $Global:GlobalImportServerCertificate = $ImportServerCertificate ValidateServerCertificate $group } Import-LocalizedData -BaseDirectory (Split-Path $PSScriptRoot -parent) -FileName "HPEAlletra6000andNimbleStoragePowerShellToolkit.psd1" -BindingVariable "ModuleData" $PSTKVersion = $ModuleData.moduleversion $Global:NimbleAppName = "HPEAlletra6000andNimbleStoragePowerShellToolkitV" + $PSTKVersion $Global:NimbleStoragePort = 5392 $Global:BaseUri = "https://$($global:Group):$($NimbleStoragePort)" try{ $Global:NimbleStorageTokenData = Invoke-RestMethod -Uri "$BaseUri/v1/tokens" -Method Post -Body ((@{data = @{username = $Credential.UserName;password = $Credential.GetNetworkCredential().password;app_name = $NimbleAppName}}) | ConvertTo-Json) Write-Host "Successfully connected to array $group `n`n" } catch{ Write-error "Failed to connect with array $group `n`n $_.Exception.Message" -ErrorAction Stop } $Global:RestVersion = (Invoke-RestMethod -Uri "$BaseUri/versions").data.name $Global:NimbleStorageSession_token = $NimbleStorageTokenData.data.session_token $Global:NimbleStorageArray = $group $Global:NimbleStorageCommonPSParams=@('Debug','Verbose','ErrorAction','ErrorVariable','InformationAction','InformationVariable','OutBuffer','OutVariable','PipelineVariable','Verbose','WarningAction','WarningVariable','WhatIf','Confim','ItemType') } } function Disconnect-NSGroup { <# .SYNOPSIS Disconnects from a Nimble SAN. .DESCRIPTION Disconnect-NSGroup is an advanced function that disconnects the established connection with Nimble Storage array. .EXAMPLE Disconnect-NSGroup .INPUTS None .OUTPUTS None .NOTES #> [CmdletBinding()] param ( ) if (Get-Variable NimbleStorageTokenData -Scope Global -ErrorAction SilentlyContinue) { Remove-NSToken -id $NimbleStorageTokenData.data.id remove-Variable -Scope "Global" NimbleStoragePort Remove-Variable -Scope "Global" BaseUri Remove-Variable -Scope "Global" NimbleStorageTokenData Remove-Variable -Scope "Global" RestVersion Remove-Variable -Scope "Global" NimbleStorageSession_token Remove-Variable -Scope "Global" NimbleStorageArray Remove-Variable -Scope "Global" NimbleStorageCommonPSParams } } function IgnoreServerCertificate { [CmdletBinding()] param() <# [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class IDontCarePolicy : ICertificatePolicy { public IDontCarePolicy() {} public bool CheckValidationResult( ServicePoint sPoint, X509Certificate cert, WebRequest wRequest, int certProb) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy write-verbose 'Server certificate ignored' #> if (-not ([System.Management.Automation.PSTypeName]'CustomCertificateValidationCallback').Type) { add-type @" using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public static class CustomCertificateValidationCallback { public static void Install() { ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidationCallback.CheckValidationResult; } public static bool CheckValidationResult( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { // please don't do this. do some real validation with explicit exceptions. return true; } } "@ } [CustomCertificateValidationCallback]::Install() } function Invoke-NimbleStorageRestAPI () { param( [Parameter(Mandatory=$true)][string] $ResourcePath, [string] $APIVersion = 'v1', [string] $Method = 'GET', [hashtable] $RequestParams = @{} ) if (!(Get-Variable NimbleStorageTokenData -Scope Global -ErrorAction SilentlyContinue)) { Write-Error -Message "Authentication Info missing. Please use Connect-NSGroup to login." -ErrorAction Stop } if ( $NimbleStorageIgnoreServerCertificate ) { IgnoreServerCertificate } # Form the parameters to Invoke-RestMethod call. $WebRequestParams = @{ Uri = "$BaseUri/$APIVersion/$ResourcePath" Header = @{'X-Auth-Token' = $NimbleStorageSession_token} Method = $Method } # Copy request params to different variable. We may need to specifically process few of them. $RequestData = @{} foreach ($key in $RequestParams.keys) { # PowerShell serializes Booleans in JSON as True/False. We need all lowercase for Nimble Array's REST Server. if ($RequestParams.$key.getType() -eq [bool]) { $RequestData.Add($key, $RequestParams.$key.ToString().ToLower()) } elseif ($key -eq 'fields' -and $Method -eq 'GET') { # Array of fields. Convert to comma separated list. $RequestData.Add('fields', ($RequestParams['fields'] | Select-Object -unique) -join ',') } else { $RequestData.Add($key, $RequestParams.$key); } } switch($Method) { 'GET' { # Hashmap supplied in Body for GET request gets converted to query params automatically. $WebRequestParams.Add('Body',$RequestData) } 'POST' { # Encapsulate request payload in 'data'.. $RequestDataForNimbleAPI = @{ data = $RequestData } $WebRequestParams.Add('Body',($RequestDataForNimbleAPI | ConvertTo-Json -Depth 10)) } 'PUT'{ $RequestDataForNimbleAPI = @{ data = $RequestData } $WebRequestParams.Add('Body',($RequestDataForNimbleAPI | ConvertTo-Json -Depth 10)) } 'DELETE' { # Do nothing. No Body expected/required for delete request. } } Write-Verbose ($WebRequestParams | ConvertTo-Json -Depth 50) $max_retry_count = 5 $retry_count = 0 do { try { if ($retry_count -ne 0) { Start-Sleep -Milliseconds 30 } $JsonResponse = (Invoke-RestMethod @WebRequestParams | ConvertTo-Json -Depth 50) Write-Verbose "Server Response: $JsonResponse" # # The Invoke-Restmethod was successful we should exit the retry loop. # To do that we will force the retry count max surpass the max. # $retry_count = $max_retry_count + 1 } catch { if ($_.Exception.Response -ne $null) { APIExceptionHandler } else { # if the Error response buffer is null then we will go for retries. # if we exhaust the retries we will thrown and error. $retry_count = $retry_count + 1 if ($retry_count -gt $max_retry_count) { Write-Verbose $_.exception Write-Error "Error occoured while invoking restapi method, Please retry" -ErrorAction Stop } } } }until ($retry_count -gt $max_retry_count) return ($JsonResponse | ConvertFrom-Json) } function Get-NimbleStorageAPIObject() { param( [Parameter(Mandatory=$true)][string] $ObjectName, [Parameter(Mandatory=$true)][string] $APIPath, [Parameter(Mandatory=$true)][string] $Id, [System.Collections.ArrayList] $Fields ) $Params = @{ ResourcePath = $APIPath + "/$Id" Method = 'GET' } if ($Fields) { $Params.Add('RequestParams', @{ fields = ($Fields | Select-Object) -join ','}) } $APIObject = (Invoke-NimbleStorageRestAPI @Params).data $DataSetType = "NimbleStorage.$ObjectName" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) return $APIObject } function Get-NimbleStorageAPIObjectList() { param( [Parameter(Mandatory=$true)][string] $ObjectName, [Parameter(Mandatory=$true)][string] $APIPath, [hashtable] $Filters, [System.Collections.ArrayList] $Fields ) # First fetch all the objects (only id and name) matching the given filter. # Then for each of the objects, retrieve either all the details or given fields. $Params = @{ ResourcePath = $APIPath Method = 'GET' RequestParams = $Filters } # Get the list of objects matching given criteria $JSONResponseObject = (Invoke-NimbleStorageRestAPI @Params) [System.Collections.ArrayList] $APIObjects = $JSONResponseObject.data # We are expecting a list. If total items/objects on the array for this query are more than 1024, # array will send back only first 1024 objects along with total count of objects in 'totalRows'. if ($JSONResponseObject.endRow -and $JSONResponseObject.totalRows -and ($JSONResponseObject.endRow -lt $JSONResponseObject.totalRows)) { # There are more objects. Keep getting those until we reach the end. while ($JSONResponseObject.endRow -lt $JSONResponseObject.totalRows) { $Params.RequestParams.startRow = $JSONResponseObject.endRow $JSONResponseObject = Invoke-NimbleStorageRestAPI @Params $APIObjects.AddRange($JSONResponseObject.data) | out-null } } [System.Collections.ArrayList] $APIObjectsDetailed = @() # Fetch needed detailes of all the objects. foreach ($APIObject in $APIObjects) { $Params = @{ ObjectName = $ObjectName APIPath = $APIPath Id = $APIObject.id } if ($Fields) { $Params.Add('Fields', $Fields) } $APIObject = (Get-NimbleStorageAPIObject @Params) $DataSetType = "NimbleStorage.$ObjectName" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) $APIObjectsDetailed.Add($APIObject) | out-null } Write-Verbose ("Found " + $APIObjectsDetailed.Count + " objects.") return ,$APIObjectsDetailed } function New-NimbleStorageAPIObject() { param( [Parameter(Mandatory=$true)][string] $ObjectName, [Parameter(Mandatory=$true)][string] $APIPath, [Parameter(Mandatory=$true)][hashtable] $Properties ) $Params = @{ ResourcePath = $APIPath Method = 'POST' RequestParams = $Properties } $ResponseObject = (Invoke-NimbleStorageRestAPI @Params) $APIObject = $ResponseObject.data if ($APIObject) {$DataSetType = "NimbleStorage.$ObjectName" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) } else { $APIObject = $ResponseObject $DataSetType = "NimbleStorage.Messages" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) } return $APIObject } function Set-NimbleStorageAPIObject() { param( [Parameter(Mandatory=$true)][string] $ObjectName, [Parameter(Mandatory=$true)][string] $APIPath, [Parameter(Mandatory=$true)][string] $Id, [Parameter(Mandatory=$true)][hashtable] $Properties ) $Params = @{ ResourcePath = $APIPath + "/$Id" Method = 'PUT' RequestParams = $Properties } $ResponseObject = (Invoke-NimbleStorageRestAPI @Params) $APIObject = $ResponseObject.data if ($APIObject) {$DataSetType = "NimbleStorage.$ObjectName" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) } else { $APIObject = $ResponseObject $DataSetType = "NimbleStorage.Messages" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) } return $APIObject } function Remove-NimbleStorageAPIObject() { param( [Parameter(Mandatory=$true)][string] $ObjectName, [Parameter(Mandatory=$true)][string] $APIPath, [Parameter(Mandatory=$true)][string] $Id ) $Params = @{ ResourcePath = $APIPath + "/$Id" Method = 'DELETE' } $APIObject = (Invoke-NimbleStorageRestAPI @Params).data } function Invoke-NimbleStorageAPIAction() { param( [Parameter(Mandatory=$true)][string] $APIPath, [Parameter(Mandatory=$true)][string] $Action, [Parameter(Mandatory=$true)][hashtable] $Arguments, [Parameter(Mandatory=$true)][string] $ReturnType ) $Params = @{ ResourcePath = $APIPath + "/actions/$Action" Method = 'POST' RequestParams = $Arguments } if ($Arguments.id) { $id = $($Arguments.id) $Params.ResourcePath = $APIPath + "/$id/actions/$Action" $Arguments.Remove('id') } $ResponseObject = (Invoke-NimbleStorageRestAPI @Params) if ($ReturnType -eq "void") { # Return empty object return $ResponseObject } $APIObject = $ResponseObject.data $DataSetType = "NimbleStorage.$ReturnType" $APIObject.PSTypeNames.Insert(0,$DataSetType) $DataSetType = $DataSetType + ".TypeName" $APIObject.PSObject.TypeNames.Insert(0,$DataSetType) return $APIObject } function ValidateServerCertificate() { param( [Parameter(Mandatory,Position=0)] [string]$Group ) $Code = @' using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace CertificateCapture { public class Utility { public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback = (message, cert, chain, errors) => { var newCert = new X509Certificate2(cert); var newChain = new X509Chain(); newChain.Build(newCert); CapturedCertificates.Add(new CapturedCertificate(){ Certificate = newCert, CertificateChain = newChain, PolicyErrors = errors, URI = message.RequestUri }); return true; }; public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>(); } public class CapturedCertificate { public X509Certificate2 Certificate { get; set; } public X509Chain CertificateChain { get; set; } public SslPolicyErrors PolicyErrors { get; set; } public Uri URI { get; set; } } } '@ if ($PSEdition -ne 'Core'){ $webrequest=[net.webrequest]::Create("https://$Group") try { $response=$webrequest.getresponse() } catch {} $cert=$webrequest.servicepoint.certificate if($cert -ne $null){ $Thumbprint = $webrequest.ServicePoint.Certificate.GetCertHashString() $bytes=$cert.export([security.cryptography.x509certificates.x509contenttype]::cert) $tfile=[system.io.path]::getTempFileName() set-content -value $bytes -encoding byte -path $tfile $certdetails = $cert | select * | ft -AutoSize | Out-String if ($($GlobalImportServerCertificate)) { try{ $output =import-certificate -filepath $tfile -certStoreLocation 'Cert:\localmachine\Root' $certdetails = $output | select -Property Thumbprint,subject | ft -AutoSize | Out-String } catch{ Write-Error "Failed to import the server certificate `n`n $_.Exception.Message" -ErrorAction Stop } Write-Host "Successfully imported the server certificate `n $certdetails" } else{ if((Get-ChildItem -Path Cert:\LocalMachine\root | Where-Object {$_.Thumbprint -eq $Thumbprint})){ } else{ write-Error "The security certificate presented by host $Group was not issued by a trusted certificate authority. Please verify the certificate details shown below and use ImportServerCertificate command line parameter to proceed. `n $certdetails `n`n" -ErrorAction Stop } } ResolveIPtoHost $cert.subject $Group } else{ Write-Error "Failed to import the server certificate `n`n" -ErrorAction Stop } }else { Add-Type $Code $Certs = [CertificateCapture.Utility]::CapturedCertificates $Handler = [System.Net.Http.HttpClientHandler]::new() $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback $Client = [System.Net.Http.HttpClient]::new($Handler) $Url = "https://$Group" $Result = $Client.GetAsync($Url).Result $cert= $Certs[-1].Certificate if($certs -ne $null){ $certdetails = $cert | select -Property Thumbprint,subject | ft -AutoSize | Out-String if ($($GlobalImportServerCertificate)) { $bytes=$cert.export([security.cryptography.x509certificates.x509contenttype]::cert) $OpenFlags = [System.Security.Cryptography.X509Certificates.OpenFlags] $store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "Root","LocalMachine" try{ $Store.Open($OpenFlags::ReadWrite) $Store.Add($Cert) $Store.Close() Write-Host "Successfully imported the server certificate `n $certdetails" } catch{ Write-Error "Failed to import the server certificate `n`n $_.Exception.Message" -ErrorAction Stop } } else { if((Get-ChildItem -Path Cert:\LocalMachine\root | Where-Object {$_.Thumbprint -eq $cert.Thumbprint})){ } else{ write-Error "The security certificate presented by host $Group was not issued by a trusted certificate authority. Please verify the certificate details shown below and use ImportServerCertificate command line parameter to proceed. `n $certdetails `n`n" -ErrorAction Stop } } ResolveIPtoHost $cert.subject $Group } else{ Write-Error "Failed to import the server certificate `n`n" -ErrorAction Stop } } } function ResolveIPtoHost{ param( [Parameter(Mandatory)] [string]$CertSubject, [Parameter(Mandatory)] [string]$Group ) # we will check if the host name given as input matches the host name in the certificate. # if IP is given as input and the certificate has hostname (FQDN) then we will use the hostname name but # before that we will ensurelookup from hostname to IP works. $cert_hostname = ($CertSubject.Substring(($CertSubject.IndexOf("=")+1),($CertSubject.IndexOf(",")-3))).trim() # check if the input and cert hostname matches, if yes we are good to go # Else the input must have ben the IP address, we will if cert hostname can be resolved to match the input hostname (IP). if ($Group -ne $cert_hostname) { # we will look up the DNS to resolve the cert_hostname. # we could do this with either cert hostname or ibput hostname(IP), but cert hostname has better chance of getting resolved. try { $resolved_name = [System.Net.DNS]::GetHostEntry($cert_hostname).AddressList $resolved_name = $resolved_name | select -ExpandProperty IPAddressToString # $Group = $resolved_name # will come here if the host got resolved. Write-Verbose " $cert_hostname is host-name for provided input $Group IP address" if ($resolved_name -ne $Group) { # most probably this will not happen, just to be defensive adding this code. # this is the same host as the certificate hostname got resolved to the input hostname(IP) # we will start using the cert hostname for all the calls from this point in this session. Write-Error "Unable to resolve the certificate hostname to match the provided input hostname/IP. `n`n $_.Exception.Message" -ErrorAction Stop } else { # we are good to go. Write-Verbose " Resolved name and input name matches: $Group IP address" $global:Group = $cert_hostname } } catch { # unable to resolve the cert hostname. Host not reachable. Error out!. Write-Error "Unable to resolve the certificate hostname. Host not reachable. `n`n $_.Exception.Message" -ErrorAction Stop } } else { # we are good to go. Write-Verbose "Host-name given as input matches with certificate name" } } Function APIExceptionHandler { #Exception message handle differently for core and non core environment #GetResponseStream method does not work in core environment if(Get-Member -inputobject $_.Exception.Response -name "GetResponseStream" -Membertype Method) { $JsonResponse = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($JsonResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); if (($responseBody | ConvertFrom-Json).messages -ne $null) { foreach( $errorMsg in ($responseBody | ConvertFrom-Json).messages) { if($errorMsg.text -ne $null) {$exceptionString += $errorMsg.text + " "} } throw [System.Exception] $exceptionString } else { throw $_.Exception } } else { $responseBody = $_.ErrorDetails if (($responseBody | ConvertFrom-Json).messages -ne $null) { foreach( $errorMsg in ($responseBody | ConvertFrom-Json).messages) { if($errorMsg.text -ne $null) {$exceptionString += $errorMsg.text + " "} } throw [System.Exception] $exceptionString } else { throw $_.Exception } } } # SIG # Begin signature block # MIIhEwYJKoZIhvcNAQcCoIIhBDCCIQACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCVQTsTa3mrUpqd # okXJZmMqbB9nn1lA6qbWGLXRvpzg3qCCEKwwggUqMIIEEqADAgECAhEAvNU51iSY # 0pIemSd4RhoKzjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJHQjEbMBkGA1UE # CBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQK # Ew9TZWN0aWdvIExpbWl0ZWQxJDAiBgNVBAMTG1NlY3RpZ28gUlNBIENvZGUgU2ln # bmluZyBDQTAeFw0yMTA1MjgwMDAwMDBaFw0yMjA1MjgyMzU5NTlaMIGQMQswCQYD # VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJUGFsbyBBbHRv # MSswKQYDVQQKDCJIZXdsZXR0IFBhY2thcmQgRW50ZXJwcmlzZSBDb21wYW55MSsw # KQYDVQQDDCJIZXdsZXR0IFBhY2thcmQgRW50ZXJwcmlzZSBDb21wYW55MIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyo5MH8CMlPL4CA+tkPZO/A7zvMst # 2DmdLgU7GJoMsXv8PYnYJzxb/ILnmaCIlCCimzZ7YmtuS1F0kMQLedMu0CyY92SW # 0CCqJRMICtIE/ahCIPAHcN3dHjc/CNAezTGvMoqh3oSOGW4KbDk8buzIyVp6O4E8 # Q4SBKjo3Ly+yzBT63Oak+C7GTu7en0r50BPel7STQEaAPLEQbBJCafvCyZwHzF1l # NzPWcnSITN7x9FIJ5H1quYnMhxWaDXY0GXZLW9UoNG0u87Emz3gBCxNrQf6y89qu # wEF4IGDFL0l/PmHN70HXCOHWJhydRjAm7JER80NaBSqKWuDX+BPE63pQ/QIDAQAB # o4IBkDCCAYwwHwYDVR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0O # BBYEFBLk4qaHNH/WpWNTozuiAfRQ3keeMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB # Af8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBK # BgNVHSAEQzBBMDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczov # L3NlY3RpZ28uY29tL0NQUzAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0 # cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdDQS5jcmww # cwYIKwYBBQUHAQEEZzBlMD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LnNlY3RpZ28u # Y29tL1NlY3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNydDAjBggrBgEFBQcwAYYXaHR0 # cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQELBQADggEBAHrs/rf97Zyr # AqyaXhXV58W3q38egR7o5Dxyd8cIDBunhxY1v3e4syOmVU+APjU+49XThv1EHmt1 # Tbhi/NR+ZLBKwVH6rls7WiIQXGT4idWaFFItOlC5SaW0HLbEBLpCK/gva9aZzXfs # EbgIgzBTqxmfpdIseptvdN5F6WIoPLRMaLJH4oCm0V2E5joqYawXunj0TNWzPoah # Otq9x+Q8cinHNOXeqFVAfsQg8DdxX/xsVGyNl/TDU59+/VFZynHWneXi8ND8I6om # iFuzPzKpr7vMiOveAs2wjrdxnaU+4HBL4E2g2WitRi890cmUaTLQrvNM52afdDEk # 538pYKjmCUgwggWBMIIEaaADAgECAhA5ckQ6+SK3UdfTbBDdMTWVMA0GCSqGSIb3 # DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0 # ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVk # MSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2VydmljZXMwHhcNMTkwMzEyMDAw # MDAwWhcNMjgxMjMxMjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5l # dyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNF # UlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh # dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCA # EmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7 # NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTb # f6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/Fp0YvVGO # NaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2VN3I5xI6 # Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq/nRO # acdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6l # ZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8l # iM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0A # vzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+ # /XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeEHg9j1uli # utZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo4HyMIHvMB8GA1Ud # IwQYMBaAFKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBRTeb9aqitKz1SA # 4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNV # HSAECjAIMAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v # ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE # KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI # hvcNAQEMBQADggEBABiHUdx0IT2ciuAntzPQLszs8ObLXhHeIm+bdY6ecv7k1v6q # H5yWLe8DSn6u9I1vcjxDO8A/67jfXKqpxq7y/Njuo3tD9oY2fBTgzfT3P/7euLSK # 8JGW/v1DZH79zNIBoX19+BkZyUIrE79Yi7qkomYEdoiRTgyJFM6iTckys7roFBq8 # cfFb8EELmAAKIgMQ5Qyx+c2SNxntO/HkOrb5RRMmda+7qu8/e3c70sQCkT0ZANMX # XDnbP3sYDUXNk4WWL13fWRZPP1G91UUYP+1KjugGYXQjFrUNUHMnREd/EF2JKmuF # MRTE6KlqTIC8anjPuH+OdnKZDJ3+15EIFqGjX5UwggX1MIID3aADAgECAhAdokgw # b5smGNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoT # FVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBD # ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xODExMDIwMDAwMDBaFw0zMDEyMzEy # MzU5NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0 # ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEk # MCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG # 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6GJ9J8JYvYwgeLdx8nxTP4ya2JWYp # QIZURnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5QYqVRkRBq4Etirv3w+Bisp//uLjMg # +gwZiahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn5Xxqi5UeW2DVftcWkpwAL2j3l+1q # cr44O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcFtk6hPldrH5i8xGLWGwuNx2YbSp+d # gcRyQLXiX+8LRf+jzhemLVWwt7C8VGqdvI1WU8bwunlQSSz3A7n+L2U18iLqLAev # Rtn5RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9D6ehfDrahjVh0wIDAQABo4IBZDCC # AWAwHwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFA7h # OqhTOjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAG # AQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDCDARBgNVHSAECjAI # MAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3Qu # Y29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsG # AQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29t # L1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8v # b2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBNY1DtRzRKYaTb # 3moqjJvxAAAeHWJ7Otcywvaz4GOz+2EAiJobbRAHBE++uOqJeCLrD0bs80ZeQEaJ # EvQLd1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU94Uzgy1KQEi/msJPSrGPJPSzgTfT # t2SwpiNqWWhSQl//BOvhdGV5CPWpk95rcUCZlrp48bnI4sMIFrGrY1rIFYBtdF5K # dX6luMNstc/fSnmHXMdATWM19jDTz7UKDgsEf6BLrrujpdCEAJM+U100pQA1aWy+ # nyAlEA0Z+1CQYb45j3qOTfafDh7+B1ESZoMmGUiVzkrJwX/zOgWb+W/fiH/AI57S # HkN6RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+OrJVWng+vLtvAxAldxU0ivk2zEOS # 5LpP8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK2nxnfn0u6ShMGH7EezFBcZpLKewL # PVdQ0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi+IO9crrLPTru8F4XkmhtyGH5pvEq # CgulufSe7pgyBYWe6/mDKdPGLH29OncuizdCoGqC7TtKqpQQpOEN+BfFtlp5MxiS # 47V1+KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN27XEp6iAb+VT1ODjosLSWxr6MiYt # aldwHDykWC6j81tLB9wyWfOHpxptWDGCD70wgg+5AgEBMIGRMHwxCzAJBgNVBAYT # AkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZv # cmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBS # U0EgQ29kZSBTaWduaW5nIENBAhEAvNU51iSY0pIemSd4RhoKzjANBglghkgBZQME # AgEFAKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3 # AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi # BCD881SdjopR71UWj3hn2+Ehxr7InbkRiROF/dVJNLfeWDANBgkqhkiG9w0BAQEF # AASCAQCVh05tg6/4EdvT6kVW4y22QOWdOyH32X1e3oNjAbmr/DU3+EAfG6oo059F # iucR9NGWOooLTwiw8W3LkCIs4B0nzCYTIl39S9nwACtFRbUxk5WtyPGHdvVjpDMZ # kr603Nn8DljVH0LYWZrw813tu6OU/MYd9Iv+H08k4ukpB9gULuvZfawfkohkJ6wz # qJSBDgPgLX+G/GM0NXFrJZSbZ4NhUkVXP5HlCWkf1I/uToF87KGV52NRYTaTwDcK # +aBPmeb1rb8IjO7c3B/lDMNC8QxAZqVMRkkvJvqYuMtvwUPyB6ERaumFhP+/xfwu # vozRvQAMhkksB/uxSl2J5HsEXFHjoYINfjCCDXoGCisGAQQBgjcDAwExgg1qMIIN # ZgYJKoZIhvcNAQcCoIINVzCCDVMCAQMxDzANBglghkgBZQMEAgEFADB4BgsqhkiG # 9w0BCRABBKBpBGcwZQIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEID1B # +J82T88uk3PaoZ3/21pAWNNimTsZqd9/nJf0hOhfAhEAjTeAOGVN5CSl5kNzv01y # DhgPMjAyMjAyMTEwMjQ0MDFaoIIKNzCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAh # zhQA8N0wDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp # Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMo # RGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAx # MDEwMDAwMDBaFw0zMTAxMDYwMDAwMDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQK # Ew5EaWdpQ2VydCwgSW5jLjEgMB4GA1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIw # MjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXK # Y2tR1zoRQr0KdXVNlLQMULUmEP4dyG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj # /TZqgfop+N0rcIXeAhjzeG28ffnHbQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2 # j8aj4S8bOrdh1nPsTm0zinxdRS1LsVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDr # Do4s1SPa9E14RuMDgzEpSlwMMYpKjIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1ux # HTDCm2mCtNv8VlS8H6GHq756WwogL0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1J # hL0BAgMBAAGjggG4MIIBtDAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW # BgNVHSUBAf8EDDAKBggrBgEFBQcDCDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATAp # MCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0j # BBgwFoAU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwo # LR1ENt3janq8MHEGA1UdHwRqMGgwMqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdp # Y2VydC5jb20vc2hhMi1hc3N1cmVkLXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKG # Q2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVk # SURUaW1lc3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO8 # 5xrnIA6OZ0b9QnJRdAojR6OrktIlxHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQC # Zk3taaQP9rhwz2Lo9VFKeHk2eie38+dSn5On7UOee+e03UEiifuHokYDTvz0/rdk # d2NfI1Jpg4L6GlPtkMyNoRdzDfTzZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO # +MDhXM/EEXLnG2RJ2CKadRVC9S0yOIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j # 1h9bm/cuG08THfdKDXF+l7f0P4TrweOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5Npjh # yyjaW4emii8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHkBdo2l8IVMA0GCSqGSIb3 # DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX # BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3Vy # ZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0zMTAxMDcxMjAwMDBaMHIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ # RCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5fU1ofue2oPSNs4jkl79j # IZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb6+NGRwYaVX4LJ37AovWg # 4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU46gJcWvgzyIQD3XPcXJO # Cq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mIUF79Zm5WYScpiYRR5oLn # RlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfxFwbvPc3WTe8GQv2iUypP # hR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAdBgNVHQ4EFgQU9LbhIB3+ # Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8w # EgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYI # KwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgw # OqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ # RFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkwRzA4BgpghkgBhv1sAAIE # MCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJ # YIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLpUYdWac3v3dp8qmN6s3jP # BjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQdaq6Z+CeiZr8JqmDfdqQ6 # kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC4HLHmNY8ZOUfSBAYX4k4 # YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+tpJn+1Nhiaj1a5bA9Fhp # DXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6HUSHkWGCbugwtK22ixH67 # xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIvIjayS6JKldj1po5SMYIC # hjCCAoICAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu # YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQg # U0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQDUJK4L46iP9gQCHOFADw # 3TANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQw # HAYJKoZIhvcNAQkFMQ8XDTIyMDIxMTAyNDQwMVowKwYLKoZIhvcNAQkQAgwxHDAa # MBgwFgQU4deCqOGRvu9ryhaRtaq0lKYkm/MwLwYJKoZIhvcNAQkEMSIEIGemY+VN # OFEgrOc8PricSpZp7FocUJMrXJEfTPVk4jjKMDcGCyqGSIb3DQEJEAIvMSgwJjAk # MCIEILMQkAa8CtmDB5FXKeBEA0Fcg+MpK2FPJpZMjTVx7PWpMA0GCSqGSIb3DQEB # AQUABIIBAHMj2pkOdnO8UVwR5+qbFQejBBLBcD9F7BLW1rgf3jLWLmB+9a0T2ppi # nDFi6xzpMgBNEMet5AsHUyuqZ62PZPfkTKHK2lPg90FKcBjxCT1uxLeohxxx97yw # 3DCo21G4H4kAH9xpvae+oOsZDnKtXodNWHF4WjVCcEit98F2JUj1zJGifDwNCMJM # 09WfLpAvYKQXMmw0d/gKMym7buxpxRsx0mHeJYTuLhqGQqeZekiCb8Zd2ae05lFU # J8sKlWgxMJonkXpQnvokaPJETFGkB2K08fTLdZ6FmT861r25z9de/yrRFf5VJ990 # LfLUcb5qE85YdMOlxczoq4oWTtLqEIk= # SIG # End signature block |