src/APRSMessenger.psm1
# This file is part of APRSMessenger. # # APRSMessenger is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # APRSMessenger 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 Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License # along with APRSMessenger. If not, see <https://www.gnu.org/licenses/>. Function Send-APRSThing { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] [OutputType([Void], ParameterSetName='APRS-IS')] [OutputType([String], ParameterSetName='ToScreen')] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $From, [Parameter(Mandatory, Position=1)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $To, [Parameter(Mandatory, Position=2)] [String] $Message, [Parameter(ParameterSetName="APRS-IS")] [ValidateSet( 'asia.aprs2.net', 'aunz.aprs2.net', 'euro.aprs2.net', 'noam.aprs2.net', 'rotate.aprs2.net', 'soam.aprs2.net' )] [String] $Server = 'rotate.aprs2.net', [Parameter(ParameterSetName="APRS-IS")] [ValidateNotNullOrEmpty()] [ValidateRange(1,65535)] [UInt16] $Port = 14580, [Parameter(ParameterSetName="APRS-IS")] [Switch] $Force ) #region Handle group bulletins correctly # Group bulletins need to be sent to BLNn, with the # group name included just before the message itself. $MsgTo = $To If ($To -Match "^BLN[0-9]") { $To = "BLN" + $To[3] } #endregion $ToSend = "$From>$To,TCPIP*::$($MsgTo.PadRight(9)):$Message" If ($PSCmdlet.ParameterSetName -ne 'APRS-IS') { Return $ToSend } Else { #region Create user agent and prepare to sign onto the server $ThisModuleName = $MyInvocation.MyCommand.Module.Name $ThisModuleVersion = $MyInvocation.MyCommand.Module.Version $Greeting = "user $From pass $(Get-APRSISPasscode $From) vers $ThisModuleName $ThisModuleVersion" #endregion If ($Force -or $PSCmdlet.ShouldProcess("Send a message to $To via APRS-IS", $To, 'Send a message')) { Try { $socket = [Net.Sockets.TcpClient]::new($Server, $Port) $stream = $socket.GetStream() Write-Debug "Sending a greeting to APRS: $Greeting" $writer = [IO.StreamWriter]::new($stream) $writer.WriteLine("$Greeting`n") $writer.Flush() Write-Debug 'Waiting for a response' $bufsize = 256 $buffer = New-Object -TypeName Byte[] -ArgumentList $bufsize $null = $stream.Read($buffer, 0, $bufsize) Write-Debug "Sending packet: $ToSend" $writer.WriteLine("$ToSend`n") $writer.Flush() } Catch [IO.IOException] { Write-Error "Could not connect to ${Server}:$Port" } Finally { If ($null -ne $writer) { $writer.Close() } If ($null -ne $stream) { $stream.close() } } } } } Function Send-APRSMessage { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ToScreen', ConfirmImpact='Low')] [OutputType([Void], ParameterSetName='APRS-IS')] [OutputType([String], ParameterSetName='ToScreen')] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $From, [Parameter(Mandatory, Position=1)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $To, [Parameter(Mandatory, Position=2, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('InputObject', 'MessageText', 'Text')] [ValidateLength(0,67)] [ValidatePattern("[^~\|\{]*")] [String] $Message, [Alias('Acknowledgment', 'Acknowledgement')] [ValidateLength(1,5)] [String] $Acknowledge, [Parameter(ParameterSetName='APRS-IS')] [ValidateSet( 'asia.aprs2.net', 'aunz.aprs2.net', 'euro.aprs2.net', 'noam.aprs2.net', 'rotate.aprs2.net', 'soam.aprs2.net' )] [String] $Server = 'rotate.aprs2.net', [Parameter(ParameterSetName='APRS-IS')] [ValidateNotNullOrEmpty()] [ValidateRange(1,65535)] [UInt16] $Port = 14580, [Parameter(ParameterSetName='APRS-IS')] [Switch] $Force ) $ToSend = $Message If ($Acknowledge) { $ToSend += "{$Acknowledge" } $Arguments = @{ 'From' = $From 'To' = $To 'Message' = $ToSend 'WhatIf' = $WhatIfPreference 'Verbose' = $VerbosePreference 'Debug' = $DebugPreference } If ($PSCmdlet.ParameterSetName -eq 'APRS-IS') { $Arguments.Server = $Server $Arguments.Port = $Port $Arguments.Force = $Force Write-Debug "From:$From To:$To Msg:$ToSend - sending to ${Server}:$Port (Force:$Force)" } Else { Write-Debug "From:$From To:$To Msg:$ToSend - printing to screen" } Return (Send-APRSThing @Arguments) } Function Send-APRSBulletin { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ToScreen', ConfirmImpact='Low')] [Alias('Send-APRSGeneralBulletin')] [OutputType([Void], ParameterSetName='APRS-IS')] [OutputType([String], ParameterSetName='ToScreen')] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $From, [Parameter(Mandatory, Position=1)] [ValidateRange(0,9)] [UInt] $BulletinID, [Parameter(Mandatory, Position=2, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('InputObject', 'MessageText', 'Text')] [ValidateLength(0,67)] [ValidatePattern("[^~\|]*")] [String] $Message, [Parameter(ParameterSetName='APRS-IS')] [ValidateSet( 'asia.aprs2.net', 'aunz.aprs2.net', 'euro.aprs2.net', 'noam.aprs2.net', 'rotate.aprs2.net', 'soam.aprs2.net' )] [String] $Server = 'rotate.aprs2.net', [Parameter(ParameterSetName='APRS-IS')] [ValidateNotNullOrEmpty()] [ValidateRange(1,65535)] [UInt16] $Port = 14580, [Parameter(ParameterSetName='APRS-IS')] [Switch] $Force ) $To = "BLN$BulletinID" $Arguments = @{ 'From' = $From 'To' = $To 'Message' = $Message 'WhatIf' = $WhatIfPreference 'Verbose' = $VerbosePreference 'Debug' = $DebugPreference } If ($PSCmdlet.ParameterSetName -eq 'APRS-IS') { $Arguments.Server = $Server $Arguments.Port = $Port $Arguments.Force = $Force Write-Debug "From:$From To:$To Msg:$Message - sending to ${Server}:$Port (Force:$Force)" } Else { Write-Debug "From:$From To:$To Msg:$Message - printing to screen" } Return (Send-APRSThing @Arguments) } Function Send-APRSGroupBulletin { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ToScreen', ConfirmImpact='Low')] [Alias('Send-APRSGeneralBulletin')] [OutputType([Void], ParameterSetName='APRS-IS')] [OutputType([String], ParameterSetName='ToScreen')] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $From, [Parameter(Mandatory, Position=1)] [ValidateRange(0,9)] [UInt] $BulletinID, [Parameter(Mandatory, Position=2)] [ValidateLength(0,5)] [String] $GroupName, [Parameter(Mandatory, Position=3, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('InputObject', 'MessageText', 'Text')] [ValidateLength(0,67)] [ValidatePattern("[^~\|]*")] [String] $Message, [Parameter(ParameterSetName='APRS-IS')] [ValidateSet( 'asia.aprs2.net', 'aunz.aprs2.net', 'euro.aprs2.net', 'noam.aprs2.net', 'rotate.aprs2.net', 'soam.aprs2.net' )] [String] $Server = 'rotate.aprs2.net', [Parameter(ParameterSetName='APRS-IS')] [ValidateNotNullOrEmpty()] [ValidateRange(1,65535)] [UInt16] $Port = 14580, [Parameter(ParameterSetName='APRS-IS')] [Switch] $Force ) $To = "BLN$BulletinID$GroupName" $Arguments = @{ 'From' = $From 'To' = $To 'Message' = $Message 'WhatIf' = $WhatIfPreference 'Verbose' = $VerbosePreference 'Debug' = $DebugPreference } If ($PSCmdlet.ParameterSetName -eq 'APRS-IS') { $Arguments.Server = $Server $Arguments.Port = $Port $Arguments.Force = $Force Write-Debug "From:$From To:$To Msg:$Message - sending to ${Server}:$Port (Force:$Force)" } Else { Write-Debug "From:$From To:$To Msg:$Message - printing to screen" } Return (Send-APRSThing @Arguments) } Function Send-APRSAnnouncement { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ToScreen', ConfirmImpact='Low')] [OutputType([Void], ParameterSetName='APRS-IS')] [OutputType([String], ParameterSetName='ToScreen')] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $From, [Parameter(Mandatory, Position=1)] [ValidatePattern('[A-Z]')] [Char] $AnnouncementID, [Parameter(Mandatory, Position=2, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('InputObject', 'MessageText', 'Text')] [ValidateLength(0,67)] [ValidatePattern("[^~\|]*")] [String] $Message, [Parameter(ParameterSetName='APRS-IS')] [ValidateSet( 'asia.aprs2.net', 'aunz.aprs2.net', 'euro.aprs2.net', 'noam.aprs2.net', 'rotate.aprs2.net', 'soam.aprs2.net' )] [String] $Server = 'rotate.aprs2.net', [Parameter(ParameterSetName='APRS-IS')] [ValidateNotNullOrEmpty()] [ValidateRange(1,65535)] [UInt16] $Port = 14580, [Parameter(ParameterSetName='APRS-IS')] [Switch] $Force ) $To = "BLN$AnnouncementID" $Arguments = @{ 'From' = $From 'To' = $To 'Message' = $Message 'WhatIf' = $WhatIfPreference 'Verbose' = $VerbosePreference 'Debug' = $DebugPreference } If ($PSCmdlet.ParameterSetName -eq 'APRS-IS') { $Arguments.Server = $Server $Arguments.Port = $Port $Arguments.Force = $Force Write-Debug "From:$From To:$To Msg:$Message - sending to ${Server}:$Port (Force:$Force)" } Else { Write-Debug "From:$From To:$To Msg:$Message - printing to screen" } Return (Send-APRSThing @Arguments) } Function Get-APRSISPasscode { [CmdletBinding()] [Alias('Get-APRSISPassword')] [OutputType([UInt16])] Param( [Parameter(Mandatory, Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('Call', 'InputObject')] [ValidatePattern('^[A-Z0-9]{3,}(?:\-(?:[0-9]|1[0-5]))?$')] [String] $Callsign ) #region Remove SSID from callsign $pos = $Callsign.IndexOf('-') If ($pos -ne -1) { $Callsign = $Callsign.Substring(0, $pos) } #endregion #region Calculate APRS-IS passcode # This function was adapted from PHP code by Peter Goodhall, 2M0SQL. # His project is not licensed, so I am using this code in good faith, # under the assumption that it was released into the public domain. # You can find it at https://github.com/magicbug/PHP-APRS-Passcode $Hash = [UInt16]0x73E2 For ($i = 0; $i -lt $Callsign.Length; $i += 2) { If ($i -lt $Callsign.Length - 1) { $Upper,$Lower = [Text.Encoding]::ASCII.GetBytes($Callsign.Substring($i,2)) } Else { $Upper = [Text.Encoding]::ASCII.GetBytes($Callsign[$i])[0] $Lower = 0 } [UInt16]$ToHash = $Upper $ToHash = $ToHash -shl 8 $ToHash = $ToHash -bor $Lower Write-Debug "Hash = $(Format-AsHex $Hash) -bxor $(Format-AsHex $ToHash)" $Hash = $Hash -bxor $ToHash } Write-Debug "Hash = $(Format-AsHex $Hash) -band 0x7FFF" $Hash = $Hash -band 0x7FFF Write-Debug "Hash is $(Format-AsHex $Hash) = $Hash" #endregion Write-Verbose "Your APRS-IS username is $Callsign, and your password is $Hash." Return $Hash } # This is a helper function used to generate debugging output inside the # previous function. Function Format-AsHex { [OutputType([String])] Param( [Parameter(Mandatory, Position=0)] [UInt16] $Number ) Return "0x$('{0:X}' -f $Number)" } # SIG # Begin signature block # MIIo5AYJKoZIhvcNAQcCoIIo1TCCKNECAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDkaSgtwPk7QWA/ # hW15n2s/mTRIe0BHK3wVcm8zyDNpVaCCI6swggR+MIIC5qADAgECAhEApna5vdQ8 # txEq0UQhUxLsMzANBgkqhkiG9w0BAQwFADBBMQswCQYDVQQGEwJVUzEQMA4GA1UE # ChMHQ2VydGVyYTEgMB4GA1UEAxMXQ2VydGVyYSBDb2RlIFNpZ25pbmcgQ0EwHhcN # MjIxMTI1MDAwMDAwWhcNMjUxMTI0MjM1OTU5WjBPMQswCQYDVQQGEwJVUzEUMBIG # A1UECAwLQ29ubmVjdGljdXQxFDASBgNVBAoMC0NvbGluIENvZ2xlMRQwEgYDVQQD # DAtDb2xpbiBDb2dsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIS0nGDy1zQpFKyt # Jcg1PiDfvpNR79NCbfgewfNj/SLANVb3XbggjeibCl1fcefKLnXFv0DXHIKjYg0e # hcFMbUQ1hqpwnnWQji1DcLeshAMdvWmTguYmtL6P4ik/BQDUuaOCAY8wggGLMB8G # A1UdIwQYMBaAFP7HyA+eaTU9w8t0+WyaszQGqVwJMB0GA1UdDgQWBBSO8z1ie4Xj # RAjUjX9ctrNH9aglYzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNV # HSUEDDAKBggrBgEFBQcDAzBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgJlMCUwIwYI # KwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEATBIBgNV # HR8EQTA/MD2gO6A5hjdodHRwOi8vQ2VydGVyYS5jcmwuc2VjdGlnby5jb20vQ2Vy # dGVyYUNvZGVTaWduaW5nQ0EuY3JsMIGABggrBgEFBQcBAQR0MHIwQwYIKwYBBQUH # MAKGN2h0dHA6Ly9DZXJ0ZXJhLmNydC5zZWN0aWdvLmNvbS9DZXJ0ZXJhQ29kZVNp # Z25pbmdDQS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly9DZXJ0ZXJhLm9jc3Auc2Vj # dGlnby5jb20wDQYJKoZIhvcNAQEMBQADggGBAAslTgxzcZ0FYetE3IOghFsEtGV+ # yEM03ZrGFRGt7/DmHe4MK15XUsORJzN60eyNzxchQhV1S90jqQflkl6ImuvdaRve # 586ZhYtW4tl2+2YbM26jwVqB9tT06W1SHb03+Vb29jjRbp5r+w3lEXxzGC660MFk # 1L8kRQcqKjt0izVeVm6qKfNVQyak5xWpeX8n8NVaCqVWfijWlLDr8Ydeg9XeJy4H # c9OweQ7+seRJzr/MgHQ0SFuXaRrbk0v5UmyoH83LZt/qo+XnrU+XeX870UVxucTl # AitkDB6t/dvmetmXQGE5stJMyIK5jgtMqQ/q/GIrTFYMmcAsXxNQh8uv+jFa0HhF # PZVhhdRbximJQUPyKb7IMuAzwdw1jrTcAF1FbkLlHXdu7dohbSfsN8ZA5Cr397wN # n7UBs939mMBb4ZR+nBPFhibj5RISssbICi8z3LNb6CNuayOn3PtG/NRcf5T8iFyW # /XbipYDJcxuQKwP8HWmlVIfQooRP6HR+Doee+DCCBY0wggR1oAMCAQICEA6bGI75 # 0C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG # A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAw # MFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD # ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGln # aUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuE # DcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNw # wrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs0 # 6wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e # 5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtV # gkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85 # tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+S # kjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1Yxw # LEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzl # DlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFr # b7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATow # ggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiu # HA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQE # AwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2 # hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290 # Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/ # Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNK # ei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHr # lnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4 # oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5A # Y8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNN # n3O3AamfV6peKOK5lDCCBd4wggPGoAMCAQICEAH9bTD8o8pRqBu8ZA41Ay0wDQYJ # KoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5 # MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO # ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0 # aG9yaXR5MB4XDTEwMDIwMTAwMDAwMFoXDTM4MDExODIzNTk1OVowgYgxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0 # eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VS # VHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sIs9CsVw127c0n00ytUINh4qogTQkt # ZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnGvDoZtF+mvX2do2NCtnbyqTsrkfji # b9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQIjy8/hPwhxR79uQfjtTkUcYRZ0YI # UcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfbIWax1Jt4A8BQOujM8Ny8nkz+rwWW # NR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0tyA9yn8iNK5+O2hmAUTnAU5GU5sz # YPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97Exwzf4TKuzJM7UXiVZ4vuPVb+DNBp # DxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNVicQNwZNUMBkTrNN9N6frXTpsNVzb # QdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5D9kCnusSTJV882sFqV4Wg8y4Z+Lo # E53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJWBp/kjbmUZIO8yZ9HE0XvMnsQybQ # v0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ5lhCLkMaTLTwJUdZ+gQek9QmRkpQ # gbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzGKAgEJTm4Diup8kyXHAc/DVL17e8v # gg8CAwEAAaNCMEAwHQYDVR0OBBYEFFN5v1qqK0rPVIDh2JvAnfKyA2bLMA4GA1Ud # DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBc # 1HwNz/cBfUGZZQxzxVKfy/jPmQZ/G9pDFZ+eAlVXlhTxUjwnh5Qo7R86ATeidvxT # UMCEm8ZrTrqMIU+ijlVikfNpFdi8iOPEqgv976jpS1UqBiBtVXgpGe5fMFxLJBFV # /ySabl4qK+4LTZ9/9wE4lBSVQwcJ+2Cp7hyrEoygml6nmGpZbYs/CPvI0UWvGBVk # kBIPcyguxeIkTvxY7PD0Rf4is+svjtLZRWEFwZdvqHZyj4uMNq+/DQXOcY3mpm8f # bKZxYsXY0INyDPFnEYkMnBNMcjTfvNVx36px3eG5bIw8El1l2r1XErZDa//l3k1m # EVHPma7sF7bocZGM3kn+3TVxohUnlBzPYeMmu2+jZyUhXebdHQsuaBs7gq/sg2eF # 1JhRdLG5mYCJ/394GVx5SmAukkCuTDcqLMnHYsgOXfc2W8rgJSUBtN0aB5x3AD/Q # 3NXsPdT6uz/MhdZvf6kt37kC9/WXmrU12sNnsIdKqSieI47/XCdr4bBP8wfuAC7U # WYfLUkGV6vRH1+5kQVV8jVkCld1incK57loodISlm7eQxwwH3/WJNnQy1ijBsLAL # 4JxMwxzW/ONptUdGgS+igqvTY0RwxI3/LTO6rY97tXCIrj4Zz0Ao2PzIkLtdmSL1 # UuZYxR+IMUPuiB3Xxo48Q2odpxjefT0W8WL5ypCo/TCCBjwwggQkoAMCAQICECFm # 8IpR6/yrzI9EMJGpSw4wDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UE # ChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNB # IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTIyMDkwNzAwMDAwMFoXDTMyMDkw # NjIzNTk1OVowQTELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0NlcnRlcmExIDAeBgNV # BAMTF0NlcnRlcmEgQ29kZSBTaWduaW5nIENBMIIBojANBgkqhkiG9w0BAQEFAAOC # AY8AMIIBigKCAYEAvp9xPhzayPelQMu7ycbIP8Kls73mzciRa7hO+f06rZl7Xw4F # DKuA1Cu7nen1GFCPuqRvCqEizDiO4/WnM4nQcfVFkfpXfZf24qUztHzq5qsxlwpK # W/Dkksj+I9A15W1dFbmToYswFElXzmKHSnZXoYMz+R4ZSwmnVB/XsvUPaAFi2dCr # KN54pMcsBweUOKFunKWkji/MMnnPJGebOF1fLeDgyEHQvYuzlVfOWU3xjMiZYfqY # gi8jo28qa0IYR17SdFZIgUWRlKhJnNKwyXfY8kElpfpeSbjM20jLch1+UhPXwTU/ # 5yHwXvUCSW4idXEihxbcleNXbeO8wfwfNHn2of4Y1w4mShxHFhDu/kPmzDIkpPct # AmDyJfJfcL1E+aRFqGYhJwCOiMNQE9dfDkYL11Rtue3zmcpkqKbH6P6EI3UQSG1t # H0OqY65xpSadXS/yGoXqOOEQpDf/U3trlyqroxhUhm0dN82CBqSXqMa23scYns1O # 3u2kSPPHIEULOVq5AgMBAAGjggFmMIIBYjAfBgNVHSMEGDAWgBRTeb9aqitKz1SA # 4dibwJ3ysgNmyzAdBgNVHQ4EFgQU/sfID55pNT3Dy3T5bJqzNAapXAkwDgYDVR0P # AQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUH # AwMwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICZTAIBgZngQwBBAEwUAYDVR0fBEkw # RzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNl # cnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHEGCCsGAQUFBwEBBGUwYzA6BggrBgEF # BQcwAoYuaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUFBQUNB # LmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkq # hkiG9w0BAQwFAAOCAgEAe11w9/hMUEgtubZdffaBE4vbRYL0hunnc2Yaup6rzig/ # GjVOaTA7gdoChGhuxDE0AoYMF1znfLBSuNrU6B8tO/ikxFprLayPz9IUmbhEd/Ry # VbMimZiC7z74OfjIVx86Y279nJ0VmX6lgHvwc8QcAVMN00Qse97OD9EeWMuY+hB7 # 1mKUp6pTipoqKJD4+hs2fOxjXew9OBYu6wjlgK6kbuBo+R2T7EuYyyfWubg9Cpwg # dzRSpWmRO5DMG+u0FojEtP8MITbtJ1bLOWZ0JVvGKDWqNLVBvxHE8DwaAx3IrlZ8 # 1lxLO3zEL/mpUnC6cdQlVkq3G7qdWfIdkaNhNAv3hu0tH3t8bLoXYDB6Kyp5hdGZ # 1XAO7H4b7MVW1amciuBXys6/VvfWmR/9Wh1rjWuYtP+y94oLg1gEisa7+Qid2qy/ # WSKC7cjpzwmg+6BGb2oEAO56pZToRc5a8vE9XcMPMO6hxI+MGbpqioQ/Nwa+94Ep # D2aGUkmqX3gP6kUBbvS4Pys0jLgKxlyZDfwJb+4CWQOoZaiZoLAr/Y9+9j2YkeQD # rt1A2zEDgOHRLlXYQDPuVNSu014pt8yAMY1OnHQSrTKwBZ2Y5H8AOw1yyIsMQISq # OcPiepvzMAwSMJtTedvFq51+kuBHgltH2AdDlPfT13i3CAqn3LcFhehUZU4VIPsw # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGwDCCBKigAwIBAgIQ # DE1pckuU+jwqSj0pB4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0 # ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAw # MDAwMFoXDTMzMTEyMTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp # Z2lDZXJ0MSQwIgYDVQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSF # dDMaJqzQHFUeHjZtvJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWv # M+xhiummKNuQY1y9iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyI # xvG+4C99O7HKU41Agx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3U # TZWEaOOAy2p50dIQkUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyV # R4aFeT4MXmaMGgokvpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQ # ln5N4d3CraV++C0bH+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq # 5Xwx5/PCUsTmFntafqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk # +lbP4PQK5hRtZHi7mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl # 5S4pkKa3YWT62SBsGFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7 # ucxnEweawXjtxojIsG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076 # XepFcxyEftfO4tQ6dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1Ud # EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZn # gQwBBAIwCwYJYIZIAYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCP # nshvMB0GA1UdDgQWBBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+g # TaBLhklodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRS # U0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCB # gDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUF # BzAChkxodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk # RzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUA # A4ICAQBVqioa80bzeFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4 # iGNVCUY5APxp1MqbKfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIr # UPwbtZ4IMAn65C3XCYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk # /9+dEKfrALpfSo8aOlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+Y # siaVOBmIRBTlClmia+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YB # ZJwAwuladHUNPeF5iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD # +5sTX2q1x+DzBcNZ3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQ # RWAzgOAj3vgDpPZFR+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+b # vdgcmlHEL5r2X6cnl7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTo # ntRamMifv427GFxD9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/ # Otrl5fbmm9x+LMz/F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDGCBI8wggSLAgEB # MFYwQTELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0NlcnRlcmExIDAeBgNVBAMTF0Nl # cnRlcmEgQ29kZSBTaWduaW5nIENBAhEApna5vdQ8txEq0UQhUxLsMzANBglghkgB # ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ # AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G # CSqGSIb3DQEJBDEiBCDZZqkWOJCeQlzEpNqVtUyf361nUwGRf3TUuI4vgEPh/jAL # BgcqhkjOPQIBBQAEZzBlAjB7w4LFHTNYFgyA7b30ypnkeRmtRVGhgSq7ajLSUzKb # W8KJxaqka05Sfx0zjv82swgCMQD4rt1QOGzA9IVGSTvFueFdhT4g05+fIMc4KGOJ # yKAyMessFhaXHOMR59tXrOyNqBahggMgMIIDHAYJKoZIhvcNAQkGMYIDDTCCAwkC # AQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5 # BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0 # YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFlAwQCAQUAoGkwGAYJ # KoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjMwMzIxMTA0 # MzAzWjAvBgkqhkiG9w0BCQQxIgQg+OVrbAZpIIZvI+fp5Bwz50CROJkONM0wq98f # VRQi58EwDQYJKoZIhvcNAQEBBQAEggIAEg8y8pIIW7jplL2JMXrUIFVHnQ8Rwmla # 5EokJeUQVg2VP+LZp62W3dGIunmDbAXDW2zFNOSNEkWd+vwtRf/Hg/ceJK+Ck0Ik # d8Q9uj+IwwEYdLlf5ijsJLXhKWK6ixl2XwC9Qh7M+FhHWb6Bsj4srrTsMesz8MeB # r5fTOMloBQGDMw2BJa2bT1uv4zUfoLaTBYA/IJXy2ExJkwVSEHwBVRu/+31NjSg3 # tC3Iz3kHwHwNdqTjv56MdNn2PeDuwIsMToc1aVo0VKH6fnfRPTcFPTgPw2TE0spG # 1ILTv76t7qpQ+4wD28lxvBr7KGe3wIFbrgQm43JBlIN6SX64uDRvDgxQqEYNj50q # CDzEiSQyxTUCXuvEP+slXQcsaHweietq0bY8L18C+bMZ+VI5D8hZaBkuks1kMLoU # MBcdXsBlLKi0VzneH9c2Lk/DlNObZz6GVmN4XCJMDaVjlxRalDBx8GVCA7AbUquE # KeCxAoy3sfP5SoIq4kSIedsny8qZ49PJ6sMxqSXUNUTCvQ6FeoZPS3PGLR7guKt6 # +9DXKpmuaU+vtfktGTnU28bcdKvGOXkOE88LxAQ1Rsi8eVOHQpGKhvzfJEd2wuYk # zwAXFJTJKCsxsDrB8VygbEtcUl/XRgANWpvUg7FCqF7bEFx8kVHVtsbZLUPnKGWP # zH6qUXT8JHY= # SIG # End signature block |