Microsoft.PowerShell.RemotingTools.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. ## ## Enable-SSHRemoting Cmdlet ## class PlatformInfo { [bool] $isCoreCLR [bool] $isLinux [bool] $isOSX [bool] $isWindows [bool] $isAdmin [bool] $isUbuntu [bool] $isUbuntu14 [bool] $isUbuntu16 [bool] $isCentOS [bool] $isFedora [bool] $isOpenSUSE [bool] $isOpenSUSE13 [bool] $isOpenSUSE42_1 [bool] $isRedHatFamily } function DetectPlatform { param ( [ValidateNotNull()] [PlatformInfo] $PlatformInfo ) try { $Runtime = [System.Runtime.InteropServices.RuntimeInformation] $OSPlatform = [System.Runtime.InteropServices.OSPlatform] $platformInfo.isCoreCLR = $true $platformInfo.isLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) $platformInfo.isOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) $platformInfo.isWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) } catch { $platformInfo.isCoreCLR = $false $platformInfo.isLinux = $false $platformInfo.isOSX = $false $platformInfo.isWindows = $true } if ($platformInfo.isWindows) { $platformInfo.isAdmin = ([System.Security.Principal.WindowsPrincipal]::new([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole( ` [System.Security.Principal.WindowsBuiltInRole]::Administrator) } if ($platformInfo.isLinux) { $LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData $platformInfo.isUbuntu = $LinuxInfo.ID -match 'ubuntu' $platformInfo.isUbuntu14 = $platformInfo.isUbuntu -and ($LinuxInfo.VERSION_ID -match '14.04') $platformInfo.isUbuntu16 = $platformInfo.isUbuntu -and ($LinuxInfo.VERSION_ID -match '16.04') $platformInfo.isCentOS = ($LinuxInfo.ID -match 'centos') -and ($LinuxInfo.VERSION_ID -match '7') $platformInfo.isFedora = ($LinuxInfo.ID -match 'fedora') -and ($LinuxInfo.VERSION_ID -ge '24') $platformInfo.isOpenSUSE = $LinuxInfo.ID -match 'opensuse' $platformInfo.isOpenSUSE13 = $platformInfo.isOpenSUSE -and ($LinuxInfo.VERSION_ID -match '13') $platformInfo.isOpenSUSE42_1 = $platformInfo.isOpenSUSE -and ($LinuxInfo.VERSION_ID -match '42.1') $platformInfo.isRedHatFamily = $platformInfo.isCentOS -or $platformInfo.isFedora -or $platformInfo.isOpenSUSE } } class SSHSubSystemEntry { [string] $subSystemLine [string] $subSystemName [string] $subSystemCommand [string[]] $subSystemCommandArgs } class SSHRemotingConfig { [PlatformInfo] $platformInfo [SSHSubSystemEntry[]] $psSubSystemEntries = @() [string] $configFilePath $configComponents = @() SSHRemotingConfig( [PlatformInfo] $platInfo, [string] $configFilePath) { $this.platformInfo = $platInfo $this.configFilePath = $configFilePath $this.ParseSSHRemotingConfig() } [string[]] SplitConfigLine([string] $line) { $line = $line.Trim() $lineLength = $line.Length $rtnStrArray = [System.Collections.Generic.List[string]]::new() for ($i=0; $i -lt $lineLength; ) { $startIndex = $i while (($i -lt $lineLength) -and ($line[$i] -ne " ") -and ($line[$i] -ne "`t")) { $i++ } $rtnStrArray.Add($line.Substring($startIndex, ($i - $startIndex))) while (($i -lt $lineLength) -and ($line[$i] -eq " ") -or ($line[$i] -eq "`t")) { $i++ } } return $rtnStrArray.ToArray() } ParseSSHRemotingConfig() { [string[]] $contents = Get-Content -Path $this.configFilePath foreach ($line in $contents) { $components = $this.SplitConfigLine($line) $this.configComponents += @{ Line = $line; Components = $components } if (($components[0] -eq "Subsystem") -and ($components[1] -eq "powershell")) { $entry = [SSHSubSystemEntry]::New() $entry.subSystemLine = $line $entry.subSystemName = $components[1] $entry.subSystemCommand = $components[2] $entry.subSystemCommandArgs = @() for ($i=3; $i -lt $components.Count; $i++) { $entry.subSystemCommandArgs += $components[$i] } $this.psSubSystemEntries += $entry } } } } function UpdateConfiguration { param ( [SSHRemotingConfig] $config, [string] $PowerShellPath ) # # Update and re-write config file with existing settings plus new PowerShell remoting settings # # Subsystem [System.Collections.Generic.List[string]] $newContents = [System.Collections.Generic.List[string]]::new() $psSubSystemEntry = "Subsystem powershell {0} {1} {2} {3}" -f $powerShellPath, "-SSHS", "-NoProfile", "-NoLogo" $subSystemAdded = $false foreach ($lineItem in $config.configComponents) { $line = $lineItem.Line $components = $lineItem.Components if ($components[0] -eq "SubSystem") { if (! $subSystemAdded) { # Add new powershell subsystem entry $newContents.Add($psSubSystemEntry) $subSystemAdded = $true } if ($components[1] -eq "powershell") { # Remove all existing powershell subsystem entries continue } # Include existing subsystem entries. $newContents.Add($line) } else { # Include all other configuration lines $newContents.Add($line) } } if (! $subSystemAdded) { $newContents.Add($psSubSystemEntry) } # Copy existing file to a backup version $uniqueName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName()) $backupFilePath = $config.configFilePath + "_backup_" + $uniqueName Copy-Item -Path $config.configFilePath -Destination $backupFilePath if ($?) { WriteLine "A backup copy of the old sshd_config configuration file has been created at:" WriteLine $backupFilePath } Set-Content -Path $config.configFilePath -Value $newContents.ToArray() -ErrorAction Stop } function CheckPowerShellVersion { param ( [string] $FilePath ) if (! (Test-Path $FilePath)) { throw "CheckPowerShellVersion failed with invalid path: $FilePath" } $commandToExec = "& '$FilePath' -noprofile -noninteractive -c '`$PSVersionTable.PSVersion.Major'" $sb = [scriptblock]::Create($commandToExec) $psVersionMajor = 0 try { $psVersionMajor = [int] (& $sb) 2>$null Write-Verbose "" Write-Verbose "CheckPowerShellVersion: $psVersionMajor for FilePath: $FilePath" } catch { } if ($psVersionMajor -ge 6) { return $true } else { return $false } } function WriteLine { param ( [string] $Message, [int] $PrependLines = 0, [int] $AppendLines = 0 ) for ($i=0; $i -lt $PrependLines; $i++) { Write-Output "" } Write-Output $Message for ($i=0; $i -lt $AppendLines; $i++) { Write-Output "" } } # Windows only GetShortPathName PInvoke $typeDef = @' using System; using System.Runtime.InteropServices; using System.Text; namespace NativeUtils { public class Path { [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern int GetShortPathName( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength); public static string ConvertToShortPath( string longPath) { int shortPathLength = 2048; StringBuilder shortPath = new StringBuilder(shortPathLength); GetShortPathName( path: longPath, shortPath: shortPath, shortPathLength: shortPathLength); return shortPath.ToString(); } } } '@ <# .Synopsis Enables PowerShell SSH remoting endpoint on local system .Description This cmdlet will set up an SSH based remoting endpoint on the local system, based on the PowerShell executable file path passed in. Or if no PowerShell file path is provided then the currently running PowerShell file path is used. The end point is enabled by adding a 'powershell' subsystem entry to the SSHD configuration, using the provided or current PowerShell file path. Both the SSH client and SSHD server components are detected and if not found a terminating error is emitted, asking the user to install the components. Then the sshd_config is parsed, and if a new 'powershell' subsystem entry is added. .Parameter SSHDConfigFilePath File path to the SSHD service configuration file. This file will be updated to include a 'powershell' subsystem entry to define a PowerShell SSH remoting endpoint, so current credentials must have write access to the file. .Parameter PowerShellFilePath Specifies the file path to the PowerShell command used to host the SSH remoting PowerShell endpoint. If no value is specified then the currently running PowerShell executable path is used in the subsytem command. .Parameter Force When true, this cmdlet will update the sshd_config configuration file without prompting. #> function Enable-SSHRemoting { [CmdletBinding()] param ( [string] $SSHDConfigFilePath, [string] $PowerShellFilePath, [switch] $Force ) # Detect platform $platformInfo = [PlatformInfo]::new() DetectPlatform $platformInfo Write-Verbose "Platform information" Write-Verbose "$($platformInfo | Out-String)" # Non-Windows platforms must run this cmdlet as 'root' if (!$platformInfo.isWindows) { $user = whoami if ($user -ne 'root') { if (! $PSCmdlet.ShouldContinue("This cmdlet must be run as 'root'. If you continue, PowerShell will restart under 'root'. Do you wish to continue?", "Enable-SSHRemoting")) { return } # Spawn new PowerShell with sudo and exit this session. $modFilePath = (Get-Module -Name Microsoft.PowerShell.RemotingTools | Select-Object -Property Path).Path $modName = [System.IO.Path]::GetFileNameWithoutExtension($modFilePath) $modFilePath = Join-Path -Path (Split-Path -Path $modFilePath -Parent) -ChildPath "${modName}.psd1" $parameters = "" foreach ($key in $PSBoundParameters.Keys) { $parameters += "-${key} " $value = $PSBoundParameters[$key] if ($value -is [string]) { $parameters += "'$value' " } } & sudo "$PSHOME/pwsh" -NoExit -c "Import-Module -Name $modFilePath; Enable-SSHRemoting $parameters" exit } } # Detect SSH client installation if (! (Get-Command -Name ssh -ErrorAction SilentlyContinue)) { Write-Warning "SSH client is not installed or not discoverable on this machine. SSH client must be installed before PowerShell SSH based remoting can be enabled." } # Detect SSHD server installation $SSHDFound = $false if ($platformInfo.IsWindows) { $SSHDFound = $null -ne (Get-Service -Name sshd -ErrorAction SilentlyContinue) } elseif ($platformInfo.IsLinux) { $sshdStatus = systemctl status sshd $SSHDFound = $null -ne $sshdStatus } else { # macOS $SSHDFound = ((launchctl list | Select-String 'com.openssh.sshd') -ne $null) } if (! $SSHDFound) { Write-Warning "SSHD service is not found on this machine. SSHD service must be installed and running before PowerShell SSH based remoting can be enabled." } # Validate a SSHD configuration file path if ([string]::IsNullOrEmpty($SSHDConfigFilePath)) { Write-Warning "-SSHDConfigFilePath not provided. Using default configuration file location." if ($platformInfo.IsWindows) { $SSHDConfigFilePath = Join-Path -Path $env:ProgramData -ChildPath 'ssh' -AdditionalChildPath 'sshd_config' } elseif ($platformInfo.isLinux) { $SSHDConfigFilePath = '/etc/ssh/sshd_config' } else { # macOS $SSHDConfigFilePath = '/private/etc/ssh/sshd_config' } } # Validate a PowerShell command to use for endpoint $PowerShellToUse = $PowerShellFilePath if (! [string]::IsNullOrEmpty($PowerShellToUse)) { WriteLine "Validating provided -PowerShellFilePath argument." -AppendLines 1 -PrependLines 1 if (! (Test-Path $PowerShellToUse)) { throw "The provided PowerShell file path is invalid: $PowerShellToUse" } if (! (CheckPowerShellVersion $PowerShellToUse)) { throw "The provided PowerShell file path is an unsupported version of PowerShell. PowerShell version 6.0 or greater is required." } } else { WriteLine "Validating current PowerShell to use as endpoint subsystem." -AppendLines 1 # Try currently running PowerShell $PowerShellToUse = Get-Command -Name "$PSHome/pwsh" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source if (! $PowerShellToUse -or ! (CheckPowerShellVersion $PowerShellToUse)) { throw "Current running PowerShell version is not valid for SSH remoting endpoint. SSH remoting is only supported for PowerShell version 6.0 and higher. Specify a valid PowerShell 6.0+ file path with the -PowerShellFilePath parameter." } } # SSHD configuration file uses the space character as a delimiter. # Consequently, the configuration Subsystem entry will not allow argument paths containing space characters. # For Windows platforms, we can a short cut path. # But for non-Windows platforms, we currently throw an error. # One possible solution is to crete a symbolic link # New-Item -ItemType SymbolicLink -Path <NewNoSpacesPath> -Value $<PathwithSpaces> if ($PowerShellToUse.Contains(' ')) { if ($platformInfo.IsWindows) { Add-Type -TypeDefinition $typeDef $PowerShellToUse = [NativeUtils.Path]::ConvertToShortPath($PowerShellToUse) if (! (Test-Path -Path $PowerShellToUse)) { throw "Converting long Windows file path resulted in an invalid path: ${PowerShellToUse}." } } else { throw "The PowerShell executable (pwsh) selected for hosting the remoting endpoint has a file path containing space characters, which cannot be used with SSHD configuration." } } WriteLine "Using PowerShell at this path for SSH remoting endpoint:" WriteLine "$PowerShellToUse" -AppendLines 1 # Validate the SSHD configuration file path if (! (Test-Path -Path $SSHDConfigFilePath)) { throw "The provided SSHDConfigFilePath parameter, $SSHDConfigFilePath, is not a valid path." } WriteLine "Modifying SSHD configuration file at this location:" WriteLine "$SSHDConfigFilePath" -AppendLines 1 # Get the SSHD configurtion $sshdConfig = [SSHRemotingConfig]::new($platformInfo, $SSHDConfigFilePath) if ($sshdConfig.psSubSystemEntries.Count -gt 0) { WriteLine "The following PowerShell subsystems were found in the sshd_config file:" foreach ($entry in $sshdConfig.psSubSystemEntries) { WriteLine $entry.subSystemLine } Writeline "Continuing will overwrite any existing PowerShell subsystem entries with the new subsystem." -PrependLines 1 WriteLine "The new SSH remoting endpoint will use this PowerShell executable path:" WriteLine "$PowerShellToUse" -AppendLines 1 } $shouldContinue = $Force if (! $shouldContinue) { $shouldContinue = $PSCmdlet.ShouldContinue("The SSHD service configuration file (sshd_config) will now be updated to enable PowerShell remoting over SSH. Do you wish to continue?", "Enable-SSHRemoting") } if ($shouldContinue) { WriteLine "Updating configuration file ..." -PrependLines 1 -AppendLines 1 UpdateConfiguration $sshdConfig $PowerShellToUse WriteLine "The configuration file has been updated:" -PrependLines 1 WriteLine $sshdConfig.configFilePath -AppendLines 1 WriteLine "You must restart the SSHD service for the changes to take effect." -AppendLines 1 } } # SIG # Begin signature block # MIIjigYJKoZIhvcNAQcCoIIjezCCI3cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCMnbsG7JmyvYb9 # U4aowPwk9xNX7lWJLxa6PKzgIdVFK6CCDYUwggYDMIID66ADAgECAhMzAAABUptA # n1BWmXWIAAAAAAFSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCxp4nT9qfu9O10iJyewYXHlN+WEh79Noor9nhM6enUNbCbhX9vS+8c/3eIVazS # YnVBTqLzW7xWN1bCcItDbsEzKEE2BswSun7J9xCaLwcGHKFr+qWUlz7hh9RcmjYS # kOGNybOfrgj3sm0DStoK8ljwEyUVeRfMHx9E/7Ca/OEq2cXBT3L0fVnlEkfal310 # EFCLDo2BrE35NGRjG+/nnZiqKqEh5lWNk33JV8/I0fIcUKrLEmUGrv0CgC7w2cjm # bBhBIJ+0KzSnSWingXol/3iUdBBy4QQNH767kYGunJeY08RjHMIgjJCdAoEM+2mX # v1phaV7j+M3dNzZ/cdsz3oDfAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU3f8Aw1sW72WcJ2bo/QSYGzVrRYcw # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1NDEzNjAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AJTwROaHvogXgixWjyjvLfiRgqI2QK8GoG23eqAgNjX7V/WdUWBbs0aIC3k49cd0 # zdq+JJImixcX6UOTpz2LZPFSh23l0/Mo35wG7JXUxgO0U+5drbQht5xoMl1n7/TQ # 4iKcmAYSAPxTq5lFnoV2+fAeljVA7O43szjs7LR09D0wFHwzZco/iE8Hlakl23ZT # 7FnB5AfU2hwfv87y3q3a5qFiugSykILpK0/vqnlEVB0KAdQVzYULQ/U4eFEjnis3 # Js9UrAvtIhIs26445Rj3UP6U4GgOjgQonlRA+mDlsh78wFSGbASIvK+fkONUhvj8 # B8ZHNn4TFfnct+a0ZueY4f6aRPxr8beNSUKn7QW/FQmn422bE7KfnqWncsH7vbNh # G929prVHPsaa7J22i9wyHj7m0oATXJ+YjfyoEAtd5/NyIYaE4Uu0j1EhuYUo5VaJ # JnMaTER0qX8+/YZRWrFN/heps41XNVjiAawpbAa0fUa3R9RNBjPiBnM0gvNPorM4 # dsV2VJ8GluIQOrJlOvuCrOYDGirGnadOmQ21wPBoGFCWpK56PxzliKsy5NNmAXcE # x7Qb9vUjY1WlYtrdwOXTpxN4slzIht69BaZlLIjLVWwqIfuNrhHKNDM9K+v7vgrI # bf7l5/665g0gjQCDCN6Q5sxuttTAEKtJeS/pkpI+DbZ/MIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFVswghVXAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAFSm0CfUFaZdYgAAAAA # AVIwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIApM # Ej1ArxNqlcDqriIpSCrY1eWLqlMmYd0DgUb5J8cBMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAV6xNDCx8t2+tzbvYE1EPnUSG9Z40AILF097A # 7NYqdHFEI+lVregcGIKaA/esCKllNEmDiVQzXcTl0d24rmefz0Ii60lfPgWLQ2qw # PEiGVYhai34Vl2dvdAcdjmhq+oCHHjCtT/9oQAvRUZGLz7CQBI95/LJOMIHOALz2 # ms3w9y0KtgfuwPkXGEgIRv2OcKoVWCw2mFKtYqEA2wNBl6rWYpS5v4CAD26NWec+ # Tu6AXGDeXkzv7kVydGfHHklEXwx3RBIofw9xDbG0jp/UeolGCu+Z3aoCbBOQoCh7 # FVA08E+raH78BgxnCGTEScNnfqz3zEFB5G8MenMFwwoxEgZ93KGCEuUwghLhBgor # BgEEAYI3AwMBMYIS0TCCEs0GCSqGSIb3DQEHAqCCEr4wghK6AgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCDx1fL2Dgk7p+LGsZ0yPHkfPK6a5uy27p7G # lezjyRa/0AIGXiswpQ5eGBMyMDIwMDEzMDE3MTYyNC4xNjRaMASAAgH0oIHQpIHN # MIHKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z # b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjozQkQ0LTRCODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDjwwggTxMIID2aADAgECAhMzAAABC+T5vo9vTB3QAAAA # AAELMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE5MTAyMzIzMTkxNVoXDTIxMDEyMTIzMTkxNVowgcoxCzAJBgNVBAYTAlVT # MQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy # YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjNCRDQtNEI4 # MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlwLVnUYxQbjPg9p4VCi1blr/ # XGXKtf/HpspEAaZQ4ovA6sMAjZw9MyYc+5/eFrVoxbHOSi/3RfIClkzER+TFU2uX # cQibulbWaG3PrM7TPtCTzOVZnG/+w/gJRRERgJEBhsTv2eH8Rx9fxHGf4sFIps2n # 14wTpSEN0UsVAI/fNJYrgMjQq4/CXbpxkd51Ukb8SbVqVGb5SFK2GOCw5iSbBbCP # ILHIdy63IZj3gZKMbL8u0aSoXDkLU2GnA+PL8+3809nInIiagF8Wbe37YfLIKiol # FEQlbkpXFClwV5v9XXGAiqjqFM9mBrtotLeCv19eyVmeY3Tdb8as0kGvT+Dx8QID # AQABo4IBGzCCARcwHQYDVR0OBBYEFK0f2eodih6c4JgNUERl//dtXt7vMB8GA1Ud # IwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKG # Pmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUH # AwgwDQYJKoZIhvcNAQELBQADggEBACboo52p7za0ut3vOwitIMCJiPAuCXYcSyz5 # wOpv6VEl1npfSgmt7feTUTTt+jYHpg8YbJM+61R4lIoG9aSXZvkweUoYNg5T4tVI # XQk2jeZU1mfqxwBXwyOItoHSjsHcroO95uY2tnanw05dg4uWscHAYA7xrGS3wZvm # hrrdr1BgQYNUIzCn6kBqjCQmMFzxnR5sETdVDeTKTkQZE5pNgxFlo0ZtCykNf3le # CmIlOXFeBgtP/P6v1+9cG68Hch9mcr4dpiDhPuE/ZmXOx9As2fEHakx3dsW009Rk # jUXnmGJZ05FpQohC42JCJx1H8LpgtaQrmTH+CEzcOyo3jhj8ig0wggZxMIIEWaAD # AgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBD # ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3 # MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWl # CgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/Fg # iIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeR # X4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/Xcf # PfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogI # Neh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB # 5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvF # M2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAP # BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjE # MFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kv # Y3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEF # BQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8E # gZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcC # AjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUA # bgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Pr # psz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOM # zPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCv # OA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v # /rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99 # lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1kl # D3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQ # Hm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30 # uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp # 25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HS # xVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi6 # 2jbb01+P3nSISRKhggLOMIICNwIBATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMx # CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0JENC00Qjgw # LTY5QzMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB # ATAHBgUrDgMCGgMVAPH9+R0xalPc8IoSPZLZrD4KcDBSoIGDMIGApH4wfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDh3O9UMCIY # DzIwMjAwMTMwMTM1OTQ4WhgPMjAyMDAxMzExMzU5NDhaMHcwPQYKKwYBBAGEWQoE # ATEvMC0wCgIFAOHc71QCAQAwCgIBAAICGRkCAf8wBwIBAAICEcAwCgIFAOHeQNQC # AQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEK # MAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAwebQk35h2oMifAgDKFYnc/pTW # r3wruN9RY2owfD6EkoeW21EX6XQVriBbBei9UtvTOTXGbI2yKefh8/ZfSXcR1tg3 # us8FCjzK7tCJsMIXDFqxgSVNgTGHjk9zVzeDy3AZ2AK+YMmo/lplOAMw4TGbFVM/ # /nslNXsSxwHpH1kpUjGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABC+T5vo9vTB3QAAAAAAELMA0GCWCGSAFlAwQCAQUAoIIB # SjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIBdz # 0mYiDtiFeiyURM+Tuo1Ycvf/WYAtZfCRNyG8swwlMIH6BgsqhkiG9w0BCRACLzGB # 6jCB5zCB5DCBvQQgNI/QziBTPjokl/FwJFwF4r0UdCzxwOnFVPwEwBNcc4gwgZgw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAQvk+b6Pb0wd # 0AAAAAABCzAiBCA/lNIf2guAs/nVzSU/c3meXkg3wX6B6R9EaZMBHH1VNDANBgkq # hkiG9w0BAQsFAASCAQBaQZjoUwgdnWSZ2LzV+2gOrUGov5j6Q6a8JHkkvEbO32X8 # DVWZKlFAWFxsyBb1dqAes2cUmYkgt6xfkIHMuaPtDIvVnicGHDA9dLS+ulDWiPm0 # pawJesdycQm4H4++JZi0Gbg1iVNjtTgGXh37VhSA5ydg9GGETFQBBViKBPqP7lp/ # MfEy5xy24j0Mp8CDC1F6OxxZRu8vycNcuUyFJ2w7miQxr74BmJa9h0XhtyaeN14k # /jB+zTIRNYpkvuSp8nKlZKLLSTUzXe8frjIBWY/vD4MZNp2a618w3u80qNUYV8pX # 6zW/AmKC+mPiZa9wuGab2K+1xzSOjj7t0+jvWSKY # SIG # End signature block |