extensions.ps1
function Get-ICExtension { [cmdletbinding(DefaultParameterSetName="List")] Param( [parameter( Mandatory, ValueFromPipeline, ParameterSetName='Id')] [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] [alias('extensionId')] [String]$Id, [parameter( ParameterSetName='List', HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] [HashTable]$where=@{}, [parameter( ParameterSetName='List')] [Switch]$NoLimit, [parameter( ParameterSetName='List')] [Switch]$CountOnly, [Parameter( ParameterSetName='Id', HelpMessage = "Filepath and name to save extension to. Recommending ending as .lua")] [ValidateScript( { Test-Path -Path $_ -IsValid })] [String]$SavePath, [parameter( ParameterSetName = 'Id')] [parameter( ParameterSetName = 'List')] [Switch]$IncludeBody ) PROCESS { if ($Id) { Write-Verbose "Looking up extension by Id." $Endpoint = "extensions/$Id" $exts = Get-ICAPI -Endpoint $Endpoint -ea 0 if (-NOT $exts) { Write-Warning "Could not find extension with Id: $($Id)" return } } else { Write-Verbose "Getting extensions" $Endpoint = "extensions" $exts = Get-ICAPI -endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly if (-NOT $exts) { Write-Verbose "Could not find any extensions loaded with filter: $($where|convertto-json -Compress)" return } if ($CountOnly) { return $exts } } if (-NOT $IncludeBody) { return $exts } $n = 1 if ($null -eq $exts.count) { $c = 1 } else { $c = $exts.count } $exts | ForEach-Object { $ext = $_ Write-Verbose "Getting Extension $($ext.name) [$($ext.id)]" try { $pc = [math]::Floor(($n / $c) * 100) } catch { $pc = -1 } Write-Progress -Id 1 -Activity "Getting Extention Body from Infocyte API" -Status "Requesting Body from Extension $n of $c" -PercentComplete $pc $extBody = Get-ICAPI -endpoint "extensions/$($ext.id)/LatestVersion" -fields body, sha256 $Script = @{ body = $extBody.body sha256 = $extBody.sha256 } Write-Verbose "Looking up user: $($ext.createdBy) and $($ext.updatedBy)" $ext.createdBy = (Get-ICAPI -endpoint users -where @{ id = $ext.createdBy } -fields email -ea 0).email $ext.updatedBy = (Get-ICAPI -endpoint users -where @{ id = $ext.updatedBy } -fields email -ea 0).email Write-Verbose "Parsing Extension Header for $($ext.name) [$($ext.id)]" try { $header = Parse-ICExtensionHeader -Body $Script.body if ($header) { $h = @{} $header.psobject.properties | % { $h[$_.name] = $_.value } } $ext | Add-Member -MemberType NoteProperty -Name args -Value $h.args $ext | Add-Member -MemberType NoteProperty -Name globals -Value $h.globals $ext | Add-Member -MemberType NoteProperty -Name header -Value $h.info } catch { Write-Warning "Could not parse header on $($ext.name) [$($ext.id)]: $($_)" } $ext | Add-Member -MemberType NoteProperty -Name script -Value $Script $n += 1 } Write-Progress -Id 1 -Activity "Getting Extentions from Infocyte API" -Status "Complete" -Completed if ($SavePath) { $exts.body | Out-File $SavePath | Out-Null } $exts } } function New-ICExtension { [cmdletbinding()] param( [parameter(mandatory=$true)] [String]$Name, [Parameter()] [String]$Author, [Parameter()] [String]$Description, [Parameter()] [ValidateSet( "Collection", "Response" )] [String]$Type = "Collection", [Parameter(HelpMessage="Filepath and name to save new extension to. Recommending ending as .lua")] [ValidateScript({ Test-Path -Path $_ -IsValid })] [String]$SavePath ) $CollectionTemplate = "https://raw.githubusercontent.com/Infocyte/extensions/master/examples/collection_template.lua" $ActionTemplate = "https://raw.githubusercontent.com/Infocyte/extensions/master/examples/response_template.lua" if ($Type -eq "Collection"){ $template = (new-object Net.WebClient).DownloadString($CollectionTemplate) } else { $template = (new-object Net.WebClient).DownloadString($ActionTemplate) } $template = $template -replace '(?si)(?<start>^--\[=\[.+?name\s*=\s*")(?<field>.+?)(?<end>"\n)', "`${start}$Name`${end}" $template = $template -replace '(?si)(?<start>^--\[=\[.+?author\s*=\s*")(.+?)(?<end>"\n)', "`${start}$Author`${end}" $template = $template -replace '(?si)(?<start>^--\[=\[.+?description\s*=\s*)([^|].+?|\|.+?\|)(?<end>"\n)', "`${start}| $Description |`${end}" $template = $template -replace '(?si)(?<start>^--\[=\[.+?guid\s*=\s*)(.+?)(?<end>"\n)', "`${start}$([guid]::NewGuid().guid)`${end}" $dt = Get-Date -UFormat "%F" $template = $template -replace '(?si)(?<start>^--\[=\[.+?created\s*=\s*)(.+?)(?<end>"\n)',"`${start}$dt`${end}" $template = $template -replace '(?si)(?<start>^--\[=\[.+?updated\s*=\s*)(.+?)(?<end>"\n)',"`${start}$dt`${end}" if ($SavePath) { Write-Host "`nCreated $Type extension from template and saved to $SavePath" $template | Out-File -FilePath $SavePath return $true } else { return $template } } function Import-ICExtension { [cmdletbinding()] Param( [parameter( Mandatory = $true, ParameterSetName = 'Path', ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string]$Path, # path to extension file [parameter( mandatory = $true, ParameterSetName = 'String' )] [ValidateNotNullorEmpty()] [String]$Body, [Switch]$Active, [Switch]$Force ) PROCESS { $Endpoint = "extensions" $postbody = @{} if ($Active) { $postbody['active'] = $true } else { $postbody['active'] = $false } if ($PSCmdlet.ParameterSetName -eq 'Path') { Write-Verbose "Testing path: $Path" if (Test-Path $Path) { Write-Verbose "Using filename for Extension Name." $Body = Get-Content $Path -Raw } else { Throw "$Path does not exist!" } } $postbody['body'] = $Body $header = Parse-ICExtensionHeader -Body $Body if (-NOT $header.info.name -OR -NOT $header.info.type) { Throw "Extension Header is incomplete or incorrectly formatted. Recommend using a template header" } $postbody['name'] = $header.info.name if (($header.info.type).toLower() -eq "collection") { $postbody['type'] = "collection" } else { $postbody['type'] = "response" } $postbody['description'] = $header.info.guid if ($header.info.guid) { $ext = Get-ICExtension -where @{ description = $header.info.guid } if ($ext) { if (-NOT $Force) { Write-Warning "There is already an existing extension named $($ext.name) [$($ext.Id)] with guid $($ext.description). Try using Update-ICExtension or use -Force flag." return } Write-Warning "There is already an existing extension named $($ext.name) [$($ext.Id)] with guid $($ext.description). Forcing update." $postbody['id'] = $ext.id } } else { Write-Verbose "Adding new Extension named: $($postbody['name'])" } Invoke-ICAPI -Endpoint $Endpoint -body $postbody -method POST $globals = Get-ICAPI -endpoint ExtensionGlobalVariables -nolimit $header.globals | where-object { $_.name -notin $globals.name -AND $_.default } | ForEach-Object { $varbody = @{ name = $_.name type = $_.type value = $_.default } Invoke-ICAPI -Endpoint ExtensionGlobalVariables -Method POST -body $varbody } } } function Update-ICExtension { <# Updates an existing extension with a new body from a file or string. #> [cmdletbinding(SupportsShouldProcess=$true)] Param( [parameter( mandatory=$false, ParameterSetName = 'Path' )] [parameter( mandatory=$false, ParameterSetName = 'String' )] [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] [alias('extensionId')] [String]$Id, [parameter( Mandatory = $true, ParameterSetName = 'Path', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [alias('FullName')] [string]$Path, # <paths of the survey results (.bz2) files to upload> [parameter( mandatory = $true, ParameterSetName = 'String' )] [ValidateNotNullorEmpty()] [String]$Body ) PROCESS { $Endpoint = "extensions" $postbody = @{} if ($PSCmdlet.ParameterSetName -eq 'Path') { Write-Verbose "Getting Script body from $Path" if (Test-Path $Path) { $Body = Get-Content $Path -Raw } else { Write-Warning "$Path does not exist!" return } } $header = Parse-ICExtensionHeader -Body $Body Write-Verbose "Extension Header:`n$($header | ConvertTo-Json)" $postbody['body'] = $Body $postbody['name'] = $header.info.name if ($header.info.type -match "collection") { $postbody['type'] = "collection" } else { $postbody['type'] = "response" } $postbody['description'] = $header.info.guid if ($Id) { Write-Verbose "Looking up extension by Id" $ext = Get-ICExtension -id $Id if ($ext) { Write-Verbose "Extension found: `n$($ext | ConvertTo-Json)" $postbody['id'] = $Id $postbody['active'] = $ext.active if (-NOT $postbody['name']) { $postbody['name'] = $ext.name } if (-NOT $postbody['type']) { $postbody['type'] = $ext.type } if (-NOT $postbody['description']) { $postbody['description'] = $ext.description } if ($ext.description -AND ($header.info.guid -ne $ext.description)) { Write-Warning "Extension Guids do not match. Cannot be updated, try importing the new extension!`nCurrent: $($ext.description)`nNew: $($header.info.guid)" return } } else { Write-Warning "Extension with id $id not found!" return } } else { Write-Verbose "Looking up extension by Guid" $ext = Get-ICExtension -ea 0 -where @{ description = $header.info.guid } if ($ext) { Write-Verbose "Founding existing extension with matching guid $($header.info.guid). Updating id $($ext.id)" $postbody['id'] = $ext.id if (-NOT $postbody['name']) { $postbody['name'] = $ext.name } if (-NOT $postbody['type']) { $postbody['type'] = $ext.type } if (-NOT $postbody['description']) { $postbody['description'] = $ext.description } } else { Write-Warning "Could not find existing extension with Guid: $($header.info.guid)" return } } Write-Verbose "Updating Extension: $($ext['name']) [$($ext.id)] with `n$($postbody|convertto-json)" if ($PSCmdlet.ShouldProcess($($ext.name), "Will update extension $($postbody['name']) [$postbody['id'])]")) { Invoke-ICAPI -Endpoint $Endpoint -body $postbody -method POST } } } function Remove-ICExtension { [cmdletbinding(DefaultParameterSetName = 'Id', SupportsShouldProcess=$true)] Param( [parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] [alias('extensionId')] [String]$Id ) PROCESS { $Endpoint = "extensions/$Id" $ext = Get-ICExtension -id $Id if (-NOT $ext) { Write-Error "Extension with id $id not found!" return } if ($PSCmdlet.ShouldProcess($($ext.Id), "Will remove $($ext.name) with extensionId '$($ext.Id)'")) { try { Invoke-ICAPI -Endpoint $Endpoint -Method DELETE Write-Verbose "Removed extension $($ext.name) [$($ext.Id)]" $true } catch { Write-Warning "Extension $($ext.name) [$($ext.Id)] could not be removed!" } } } } function Import-ICOfficialExtensions { [cmdletbinding()] Param( [Switch]$IncludeContributed, [Switch]$Update ) $InstanceExtensions = Get-ICExtension -NoLimit Write-Verbose "Pulling Official Extensions from Github: https://api.github.com/repos/Infocyte/extensions/contents/official/" try { $Extensions = Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/official/collection" | Select-Object -ExpandProperty content | ConvertFrom-Json } catch { Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/official/collection" } try { $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/official/response" | Select-Object -ExpandProperty content | ConvertFrom-Json } catch { Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/official/response" } If ($IncludeContributed) { Write-Verbose "Pulling Official Extensions from Github: https://api.github.com/repos/Infocyte/extensions/contents/contrib/" try { $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/contrib/collection" | Select-Object -ExpandProperty content | ConvertFrom-Json } catch { Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/contrib/collection" } try { $Extensions += Invoke-WebRequest -Uri "https://api.github.com/repos/Infocyte/extensions/contents/contrib/response" | Select-Object -ExpandProperty content | ConvertFrom-Json } catch { Write-Warning "Could not download extensions from https://api.github.com/repos/Infocyte/extensions/contents/contrib/response" } } $Results = @() $Extensions | ForEach-Object { $filename = ($_.name -split "\.")[0] try { $ext = (new-object Net.WebClient).DownloadString($_.download_url) } catch { Write-Warning "Could not download extension. [$_]" continue } try { $header = Parse-ICExtensionHeader -Body $ext } catch { Write-Warning "Could not parse header on $($filename)"; continue } $existingExt = $InstanceExtensions | Where-Object { $_.description -eq $header.info.guid } if ($existingExt) { if ($Update) { Write-Verbose "Updating extension $($header.name) [$($existingExt.id)] with guid $($header.guid):`n$existingExt" Update-ICExtension -Id $existingExt.id -Body $ext } else { Write-Warning "Extension $($header.name) [$($existingExt.id)] with guid $($header.guid) already exists. Try using -Update to update it." } } else { Write-Verbose "Importing extension $($header.name) [$($header.Type)] with guid $($header.guid)" Import-ICExtension -Body $ext -Active -Force:$Update } } } # For Extension Developers function Test-ICExtension { [cmdletbinding()] [alias("Invoke-ICExtension")] param( [parameter(mandatory=$true)] [String]$Path, [Object]$Globals, [Object]$Arguments ) $LoggingColor = 'DarkCyan' If ($env:OS -match "windows" -AND (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))) { Throw "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!" } elseif ($IsLinux -AND (id -u) -ne 0) { Throw "You do not have permissions to run this script!`nPlease re-run this with sudo!" } if (-NOT (Test-Path $Path)) { Throw "$Path not found" } # Clear-Host $agentname = "agent.exe" if ($IsWindows -OR $env:OS -match "windows") { $Devpath = "C:/Program Files/Infocyte/dev" $AgentPath = "C:/Program Files/Infocyte/Agent" } else { $Devpath = "opt/infocyte/dev" $AgentPath = "opt/infocyte/agent" } # Check for Agent if (Test-Path "$DevPath/$agentname") { $DevVer = (& "$DevPath/$agentname" "--version").split(" ")[2] } else { New-Item $Devpath -ItemType Directory | Out-Null if (Test-Path "$AgentPath/$agentname") { $AgentVer = (& "$AgentPath/$agentname" "--version").split(" ")[2] Write-Warning "$Devpath/$agentname not found but latest version ($AgentVer) was found within your agent folder ($AgentPath). Copying this over." Copy-Item -Path "$AgentPath/$agentname" -Destination "$Devpath" | Out-Null } else { Write-Warning "Infocyte Agent not found. Install an Agent or download it into $DevPath" return } } # Update Agent if (Test-Path "$AgentPath/$agentname") { $AgentVer = (& "$AgentPath/$agentname" "--version").split(" ")[2] if ($AgentVer -gt $DevVer) { Write-Warning "$agentname ($DevVer) has an update: ($AgentVer). Copy '$AgentPath/$agentname' to '$Devpath/$agentname'.`n `tRun this command to do so: Copy-Item -Path '$AgentPath/$agentname' -Destination '$Devpath/$agentname'" } } $Path = Get-item $Path | Select-Object -ExpandProperty FullName $ext = Get-item $Path | Select-Object -ExpandProperty name if (($env:OS -match "windows" -OR $isWindows) -AND (-NOT (Test-Path "$DevPath/luacheck.exe"))) { $url = "https://github.com/mpeterv/luacheck/releases/download/0.23.0/luacheck.exe" Write-Host -ForegroundColor $LoggingColor "$Devpath/luacheck.exe not found (used for linting). Attempting to download from Github." # Download luacheck from Github #[Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls13 $wc = New-Object Net.WebClient $wc.Encoding = [System.Text.Encoding]::UTF8 $wc.UseDefaultCredentials = $true try { $wc.DownloadFile($URL, "$Devpath/luacheck.exe") | Out-Null } catch { Write-Warning "Could not download luacheck.exe from $URL." } } if (($env:OS -match "windows" -OR $isWindows) -AND (Test-Path "$DevPath/luacheck.exe")) { $Config = "C:\Program Files\Infocyte\dev\.luacheckrc" if (-NOT (Test-Path $Config)) { 'globals = { "hunt" }' > $Config 'allow_defined = true' >> $Config 'ignore = { "611", "612", "613", "614" }' >> $Config } Write-Host -ForegroundColor $LoggingColor "Linting $Path" $luacheck = Get-Content $Path | & "$Devpath\luacheck.exe" - --codes --config "C:\Program Files\Infocyte\dev\.luacheckrc" $luacheck | ForEach-Object { Write-Host $_ } } $a = @() $a += "--debug" $a += "--extension `"$Path`"" if ($Globals) { $Globals | ConvertTo-Json | Out-File -Force "$Devpath/globals.json" | Out-Null $a += "--extension-globals `"$Devpath/globals.json`"" } if ($Arguments) { $Arguments | ConvertTo-Json | Out-File -Force "$Devpath/args.json" | Out-Null $a += "--extension-args `"$Devpath/args.json`"" } $a += "survey --no-compress --only-extensions" $completedsuccessfully = $false $agentOutputRegex = '^\[(?<datestamp>\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}\.\d+\sUTC)\]\[(?<level>.+?)\]\[(?<logtype>.+?)\]\s(?<msg>.+)' $color = 'green' Write-Host -ForegroundColor $LoggingColor "Executing $ext with $agentname (Version: $DevVer)" Write-Host -ForegroundColor $LoggingColor "$Devpath/$agentname $a" $psi = New-object System.Diagnostics.ProcessStartInfo $psi.CreateNoWindow = $true $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $false $psi.FileName = "$Devpath/$agentname" $psi.Arguments = $a $process = New-Object System.Diagnostics.Process $process.StartInfo = $psi $process.Start() | Out-Null Write-Verbose "Args: $($psi.Arguments)" #$process.WaitForExit() if ($process.HasExited) { Write-Warning "Something went wrong on survey running: $Devpath/$agentname $($a.ToString())" } while ($line -OR -NOT $process.HasExited) { $line = $process.StandardOutput.ReadLine() if ($line -Match $agentOutputRegex) { $AgentOutput = $Matches if ($AgentOutput.msg -match "System inspections complete") { # End Write-Verbose "System inspections complete!" break } elseif ($AgentOutput.logtype -eq "agent::survey") { Write-Verbose "$line" } elseif ($AgentOutput.msg -match "Logging initialized") { Write-Host -ForegroundColor $LoggingColor "Running Extension..." } else { #Color code output Switch ($AgentOutput.level) { "ERROR" { $color = 'Red' } "WARN" { $color = 'DarkYellow' } "DEBUG" { $color = 'Yellow' } "VERBOSE" { $color = 'Yellow' } "TRACE" { $color = 'Yellow' } "INFO" { $color = 'Blue' } default { Write-Warning "[Unknown] $($AgentOutput.msg)" } } if ($AgentOutput.logtype -eq "agent::extensions" -AND $AgentOutput.level -eq "ERROR") { Write-Host -ForegroundColor $color "[$($AgentOutput.level)][$($AgentOutput.logtype)] $($AgentOutput.msg)" $exitError = $true } else { Write-Host -ForegroundColor $color "[$($AgentOutput.level)] $($AgentOutput.msg)" } } } else { # print and error output if ($color -eq 'Red') { Write-Host -ForegroundColor Red "[ERROR] $line" } else { Write-Host -ForegroundColor DarkGray "[PRINT] $line" } } } if ($exitError) { Write-Warning "Survey did not complete successfully. Address any bugs in the extension and try again." } #Remove-Item "$Devpath/args.json" | Out-Null #Remove-Item "$Devpath/globals.json" | Out-Null -NOT $exitError } Add-Type -Path "$PSScriptRoot\lib\tommy.dll" function Parse-ICExtensionHeader { [cmdletbinding(DefaultParameterSetName = 'Body')] Param( [parameter( Mandatory, ValueFromPipeline, ParameterSetName = 'Body')] [ValidateNotNullOrEmpty()] [String]$Body, [parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path')] [ValidateNotNullorEmpty()] [String]$Path ) if ($Path) { $Body = Get-Content $Path -Raw #$reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $Path } if ($Body -match '(?si)^--\[=\[(?<preamble>.+?)\]=\]') { $preamble = $matches.preamble.trim() } else { Write-Error "Could not parse header (should be a comment section wrapped by --[=[ <header> ]=] )" return } $reader = [System.IO.StringReader]::new($preamble) try { [Tommy.TomlTable]$table = [Tommy.TOML]::Parse($reader) } catch { Throw "Improper header format. Should be a TOML file: $($_)" } try { $header = $table.ToString().replace(" =", ":").replace("`"`"`"", "`"").replace("'''", "'") | ConvertFrom-Json } catch { Throw "TOML header could not be converted to/from JSON: $($_)" } if ($header.filetype -ne "Infocyte Extension") { Throw "Incorrect filetype. Not an Infocyte Extension" } if ($header.info.guid -notmatch $GUID_REGEX) { Throw "Incorrect guid format: $($header.guid). Should be a guid of form: $GUID_REGEX. Use the following command to generate a new one: [guid]::NewGuid().Guid" } $header.info.created = [datetime]$header.info.created $header.info.updated = [datetime]$header.info.updated $header } # SIG # Begin signature block # MIINOAYJKoZIhvcNAQcCoIINKTCCDSUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUjV19y+vNTAEUjOevFeeDXn6y # Gwqgggp2MIIFHzCCBAegAwIBAgIQA7ShIT20JORyIag/jWOSyzANBgkqhkiG9w0B # AQsFADB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBTSEEyIEhp # Z2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQTAeFw0xODA5MTIwMDAwMDBaFw0y # MDExMTgxMjAwMDBaMF4xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0G # A1UEBxMGQXVzdGluMRYwFAYDVQQKEw1JbmZvY3l0ZSwgSW5jMRYwFAYDVQQDEw1J # bmZvY3l0ZSwgSW5jMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApzcR # GuszlupWLdmtti4glKsr6SS2sp370yioc2XyIwPzU/etsBQa41x6VO7HXjtSXSry # p3SYaoLPQmBiAzKjDP6dzu0l7cQFbwPMky3SGqrC3Wr+Kw/qoMgn3wKBxzPJ53Gj # s1oxNwyz2N7FwN977vErW9C/FgM/XuE7Zde/HGl3oxTJNtY++BG2Ri3rwi5hNbzV # 5+avrJFW1DzHVBXYxbrE9vNy4V6s7dlZT2xZoJ3AtHoBCUMgHRKii3wHgFRaxiuz # 6XzlvHzmnh02KUfoV6cX++bP4bRtsJjmvrfJV+Mhlh/MhUidhhQQx0spLIfxv+vZ # OACP5jLm0g2fj4G4VQIDAQABo4IBvzCCAbswHwYDVR0jBBgwFoAUZ50PIAkMzIo6 # 5YJGcmL88cyQ5UAwHQYDVR0OBBYEFBqi6MjBKip4kQYxVCjC7yOrUHWFMA4GA1Ud # DwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBtBgNVHR8EZjBkMDCgLqAs # hipodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1jcy1nMS5jcmwwMKAu # oCyGKmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWhhLWNzLWcxLmNybDBM # BgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3 # dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBiAYIKwYBBQUHAQEEfDB6MCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUgYIKwYBBQUHMAKG # Rmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJIaWdoQXNz # dXJhbmNlQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B # AQsFAAOCAQEABnQP0mJYXNzfz4pMCc4FbQ9qe8NjloGuIlUgXaxlFAoMKZWMueXq # mciPWULaE+Wd5ChTuNsrWKG7bWYYWmmo7C1RWhdZhQT/C+k3lFcsSr6gdXAXiOmv # 3xv3d3oqfNe19G6jt6akQ6LjEauRw4xKinoK/S61Pw9c1KtEAGT8djgX74h433fy # FPiQd//ePnihKN+GXRCeLvSaDGuVrhHuI6UUhe3MK2/Nb8MzFddwkOOdpky1HBn4 # 8oFEAOzbrTVTTv4BWLNRvAiY8UO3D2Kt322UuAdXIKNxWB94UaFt2jg2QsRkTHGQ # MmbQ8OgMIWWNcE9RcVKuobYbzUAGPoMimTCCBU8wggQ3oAMCAQICEAt+EJA8OEkP # +i9nmoehp7kwDQYJKoZIhvcNAQELBQAwbDELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UE # AxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTAeFw0xMzEwMjIx # MjAwMDBaFw0yODEwMjIxMjAwMDBaMHYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNTAzBgNVBAMT # LERpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgQ29kZSBTaWduaW5nIENBMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtEpefQcPQd7E9XYWNr1x/88/ # T3NLnNEN/krLV1hehRbdAhVUmfCPPC9NAngQaMjYNUs/wfdnzpgcrjO5LR2kClST # xIWi3zWx9fE8p7M0+11IyUbJYkS8SJnrKElTwz2PwA7eNZjpYlHfPWtAYe4EQdrP # p1xWltH5TLdEhIeYaeWCuRPmVb/IknCSCjFvf4syq89rWp9ixD7uvu1ZpFN/C/FS # iIp7Cmcky5DN7NJNNEyw4bWfnMb2byzN5spTdAGfZzXeOEktzu05RIIZeU4asrX7 # u3jwSWanz/pclnWSixpy2f9QklPMPsJDMgkahhNpPPuBMjMyZHVzKCYdCDA7BwID # AQABo4IB4TCCAd0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwfwYIKwYBBQUHAQEEczBxMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYBBQUHMAKGPWh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RD # QS5jcnQwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9v # dENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYc # aHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4E # FgQUZ50PIAkMzIo65YJGcmL88cyQ5UAwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1Jgm # GggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBAGoO/34TfAalS8AujPlTZAniuliR # MFDszJ/h06gvSEY2GCnQeChfmFZADx66vbE7h1zcW9ggDe0aFk3VESQhS/EnaZAT # 6xGhAdr9tU55WXW9OCpqw/aOQSuKoovXLFFR2ZygyONOumyoR9JO0WgfjAJXO7Mp # ao5qICq58gBiZLrI6QD5zKTUupo12K8sZWwWfFgh3kow0PrrJF0GyZ0Wt61KRdMl # 4gzwQKpcTax+zQaCuXZGaQjYMraC/uOpWDRDG45nZ5c/aDEWNjiVPof3x8OvnXp3 # Gdnek7X9biv8lPk9t0wSNSwwvuiNngVwmkgT9IzW5x6sOOeo860Mt3rsZ+0xggIs # MIICKAIBATCBijB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBT # SEEyIEhpZ2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQQIQA7ShIT20JORyIag/ # jWOSyzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq # hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC # NwIBFTAjBgkqhkiG9w0BCQQxFgQUKGeysnsLD0b0Y+ZBtFg4Q77yvHIwDQYJKoZI # hvcNAQEBBQAEggEAMAxsXruSC8WzfIyKmnkMnGTvBoZr7J488P1VgRKJ3VpJptZk # CuQzizWmxcQt5YzNq6c46q3C+333wRCZTxBoHbmIsWSdphaEZjhVn0dwtyPuGH3q # lyOH7ZgnP41QPGyKx3k+Hu0f5qzt5hgTyxf1w7zg/q2Yx9d+jaUSiSFYXm1xFLi4 # uBpXVfLM6I9hObXFuyopmN85QOoB7z221oDCTGCB+nJ37NMp36xVk1I4E/drVGNG # dQhXEik9Bexi5KVMTzTD8PGrWW0ZDqEOSroHIVMO11w5MPcFZxcvU9CI1t1IWyf6 # ZY6J9gSVMYbOjr5o9BkQJzGWQrY2qQYIxNtVjg== # SIG # End signature block |