ssh-known-hosts (empty) → 0.1.0.0
raw patch · 6 files changed
+827/−0 lines, 6 filesdep +HUnitdep +QuickCheckdep +basesetup-changed
Dependencies added: HUnit, QuickCheck, base, iproute, ssh-known-hosts, test-framework, test-framework-hunit, test-framework-quickcheck2, text
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- readknownhosts.hs +5/−0
- src/Network/SSH/KnownHosts.hs +180/−0
- ssh-known-hosts.cabal +52/−0
- test/test_ssh_known_hosts.hs +558/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Kevin Quick (c) 2016, 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ readknownhosts.hs view
@@ -0,0 +1,5 @@+module Main where++import Network.SSH.KnownHosts++main = mapM_ (putStrLn . show) =<< readKnownHosts
+ src/Network/SSH/KnownHosts.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE OverloadedStrings #-}++-- Module : KnownHosts+-- Description : Parses SSH known_hosts file into local data objects+-- Copyright : (c) Kevin Quick, 2016+-- License : BSD-3+-- Maintainer : quick@sparq.org+-- Stability : stable+-- Portability : POSIX+--+-- Remote hosts specified in the .ssh/known_hosts file can take one of+-- the following forms:+--+-- * 192.168.3.1+-- * ::1+-- * shortname+-- * sys.domain.name+--+-- Any of the above can be enclosed in square brackets and followed by+-- :PORTNUM+--+-- Multiples of any of the above (with or without port specifications)+-- can occur with comma separators, representing equivalent addresses+-- for the line.+--+-- The known_hosts key file can be hashed (ssh-keygen -H), in which+-- case all of the above (including port specifications) are replaced+-- by hash values of the form "|1|partone|part2=". Note that comma+-- separators are not used for multiple hash values: multiple lines+-- are used instead.+--+-- This library parses all of the above forms, returning an array of+-- 'SSHRemote' specifications. In addition, this library provides a+-- 'targetAddr' function to get the actual target address from an+-- entry.+++module Network.SSH.KnownHosts+ ( readKnownHosts+ , readKnownHostsFile+ , parseRemotes+ , SSHRemoteAddr(..), SSHRemoteAddrs(..)+ , KeyAlgorithm, SSHKey, DNSName, SSHRemote(..)+ , IPv4, IPv6+ , targetAddr+ ) where++import Data.IP.Internal+import Data.List (intercalate)+import Data.Maybe+import Data.Monoid ((<>))+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import System.Environment+import Text.Read+++type DNSName = [T.Text]++data SSHRemoteAddr = RemoteV4 IPv4+ | RemoteV4Port IPv4 Int+ | RemoteV6 IPv6+ | RemoteV6Port IPv6 Int+ | RemoteDNS [T.Text]+ | RemoteDNSPort [T.Text] Int+ | RemoteHashed String+ deriving (Show, Eq)++data SSHRemoteAddrs = SSH1Addr SSHRemoteAddr+ | SSHAddrs [SSHRemoteAddr]+ deriving (Show, Eq)++type KeyAlgorithm = String+type SSHKey = String++data SSHRemote = SSHRemote SSHRemoteAddrs KeyAlgorithm SSHKey+ deriving (Show, Eq)++-- | targetAddr returns the target address and port, using the best+-- (most explicit) form of the address in the case where there are+-- multiple addresses for the same remote system.+targetAddr :: SSHRemote -> (T.Text, Int)+targetAddr (SSHRemote (SSH1Addr a) _ _) =+ let addr (RemoteV4 a) = (T.pack $ show a, 22)+ addr (RemoteV4Port a p) = (T.pack $ show a, p)+ addr (RemoteV6 a) = (T.pack $ show a, 22)+ addr (RemoteV6Port a p) = (T.pack $ show a, p)+ addr (RemoteDNS d) = (dotPack $ reverse d, 22)+ addr (RemoteDNSPort d p) = (dotPack $ reverse d, p)+ addr (RemoteHashed h) = ("[{hashed}]",22) -- unuseable+ in addr a+targetAddr (SSHRemote (SSHAddrs m) a b) =+ targetAddr $ SSHRemote (SSH1Addr $ foldr1 findBest m) a b+ where findBest best@(RemoteV4Port _ _) _ = best+ findBest _ best@(RemoteV4Port _ _) = best+ findBest best@(RemoteV4 _) _ = best+ findBest _ best@(RemoteV4 _) = best+ findBest best@(RemoteV6Port _ _) _ = best+ findBest _ best@(RemoteV6Port _ _) = best+ findBest best@(RemoteV6 _) _ = best+ findBest _ best@(RemoteV6 _) = best+ findBest best@(RemoteDNSPort _ _) _ = best+ findBest _ best@(RemoteDNSPort _ _) = best+ findBest best@(RemoteDNS _) _ = best+ findBest _ best@(RemoteDNS _) = best+ findBest best _ = best++dotPack = T.intercalate (T.pack ".")++splitDNS :: T.Text -> [T.Text]+splitDNS n = let split = T.splitOn "." n+ dottedQuad = and [ length split == 4+ , T.null $ snd $ T.span isNumOrDot n]+ isNumOrDot = flip elem ("0123456789." :: String)+ in if dottedQuad then [] else reverse split++parseAddr :: T.Text -> SSHRemoteAddrs+parseAddr addrSpec =+ let addrs = T.splitOn "," addrSpec+ in if null (tail addrs) then SSH1Addr (parse1Addr addrSpec)+ else SSHAddrs (map parse1Addr addrs)++parse1Addr :: T.Text -> SSHRemoteAddr+parse1Addr addrSpec =+ let hasPort = T.head addrSpec == '[' && T.head (snd parts) == ']'+ hashedAddr = T.head addrSpec == '|'+ (a',p) = if hasPort+ then (T.tail $ fst parts,+ read (T.unpack $ T.tail $ T.tail $ snd parts))+ else (addrSpec, 22)+ parts = T.break (== ']') addrSpec+ dns = splitDNS a'+ addr = case readMaybe (T.unpack a') of+ Just a -> if hasPort+ then RemoteV4Port a p+ else RemoteV4 a+ Nothing -> case readMaybe (T.unpack a') of+ Just a -> if hasPort+ then RemoteV6Port a p+ else RemoteV6 a+ Nothing -> if hashedAddr+ then RemoteHashed $ T.unpack addrSpec+ else if hasPort+ then RemoteDNSPort dns p+ else RemoteDNS dns+ in addr+++parseRemote :: T.Text -> SSHRemote+parseRemote line =+ let w = T.words line+ expfmt = "address algorithm key"+ addr = parseAddr $ head w+ alg = T.unpack $ head $ tail w+ hash = T.unpack $ head $ tail $ tail w+ in if length w == 3+ then SSHRemote addr alg hash+ else error $ "Expected \"" <> expfmt <> "\" but got: " <> show line++-- | Passed the specification of remote ssh known hosts and returns+-- the parsed SSHRemote structures representing those hosts. The+-- input text block is expected to be the raw contents of a+-- known_hosts file (e.g. comparable to a @Data.Text.IO.readFile+-- "$HOME/.ssh/known_hosts@) and the output is the parsed data+-- structures.+parseRemotes :: T.Text -> [SSHRemote]+parseRemotes = map parseRemote . T.lines++-- | Reads the current user's known_hosts file and returns the+-- SSHRemote structures representing the contents of that file (by+-- calling 'parseRemotes').+readKnownHosts :: IO [SSHRemote]+readKnownHosts =+ do home <- getEnv "HOME"+ readKnownHostsFile $ home <> "/.ssh/known_hosts"++-- | Reads the specified file and returns the SSHRemote structures+-- representing the contents of that file (by calling 'parseRemotes').+readKnownHostsFile :: String -> IO [SSHRemote]+readKnownHostsFile f = parseRemotes <$> TIO.readFile f
+ ssh-known-hosts.cabal view
@@ -0,0 +1,52 @@+name: ssh-known-hosts+version: 0.1.0.0+synopsis: Read and interpret the SSH known-hosts file++description:++ Utility to read the contents of the SSH known_hosts file and create+ local in-memory data structures for each of the remote entities+ listed.++homepage: http://hub.darcs.net/kquick/ssh-known-hosts+license: BSD3+license-file: LICENSE+author: Kevin Quick+maintainer: quick@sparq.org+copyright: 2016 Kevin Quick+category: Network+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Network.SSH.KnownHosts+ build-depends: base >= 4.7 && < 5+ , iproute >= 1.5+ , text+ default-language: Haskell2010++Flag sample_progs+ Description: Build sample programs+ Default: False++executable readknownhosts+ main-is: readknownhosts.hs+ build-depends: base, ssh-known-hosts+ if !flag(sample_progs)+ buildable: False+ default-language: Haskell2010++test-suite test_parsing+ type: exitcode-stdio-1.0+ main-is: test_ssh_known_hosts.hs+ hs-source-dirs: test+ default-language: Haskell2010+ build-depends: base, HUnit, QuickCheck, text, iproute+ , test-framework, test-framework-hunit+ , test-framework-quickcheck2+ , ssh-known-hosts++source-repository head+ type: darcs+ location: http://hub.darcs.net/kquick/ssh-known-hosts
+ test/test_ssh_known_hosts.hs view
@@ -0,0 +1,558 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Data.IP.Internal+import Data.Monoid ((<>))+import qualified Data.Text as T+import Network.SSH.KnownHosts+import System.Environment+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import Test.HUnit+import Test.QuickCheck++main = defaultMain+ [ testGroup "Parsing entries"+ [ testParse1+ , testParse2+ , testParse3+ , testParse4+ , testParse5+ , testParse6+ , testParse7+ , testParse8+ , testParse9+ , testParse12+ , testParse13+ , testParse14+ , testParse15+ , testParse16+ , testParse17+ , testParse18+ , testParse19+ , testParse20+ , testParse21+ , testParse22+ ]+ , testGroup "Read and parse existing $HOME/.ssh/known_hosts"+ [ testReadFile+ ]+ , testGroup "Target address recovery"+ [ testTgtAddr1+ , testTgtAddr2+ , testTgtAddr3+ , testTgtAddr4+ , testTgtAddr5+ , testTgtAddr6+ , testTgtAddr7+ , testTgtAddr8+ , testTgtAddr9+ , testTgtAddr12+ , testTgtAddr13+ , testTgtAddr14+ , testTgtAddr15+ , testTgtAddr16+ , testTgtAddr17+ , testTgtAddr18+ , testTgtAddr19+ , testTgtAddr20+ , testTgtAddr22+ ]+ , testGroup "Best address from group"+ [ testBestTgtAddr1+ , testBestTgtAddr2+ , testBestTgtAddr3+ , testBestTgtAddr4+ , testBestTgtAddr5+ , testBestTgtAddr6+ , testBestTgtAddr7+ , testBestTgtAddr8+ , testBestTgtAddr9+ , testBestTgtAddr10+ , testBestTgtAddr11+ , testBestTgtAddr12+ , testBestTgtAddr13+ , testBestTgtAddr14+ , testBestTgtAddr15+ , testBestTgtAddr16+ , testBestTgtAddr17+ , testBestTgtAddr18+ , testBestTgtAddr19+ ]+ ]++testReadFile = testCase "read file" $+ do h <- readKnownHosts+ home <- getEnv "HOME"+ l <- lines <$> readFile (home <> "/.ssh/known_hosts")+ length h @?= length l++testParse1 = testCase "empty parse"+ $ parseRemotes (T.pack "") @?= []++testTgtAddr1 = testCase "target IPv4 address"+ $ (targetAddr $ SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg1" "key1")+ @?= ("1.2.3.4", 22)++addr2 = T.pack "1.2.3.4 alg hash"+testParse2 = testCase "single IPv4 parse"+ $ parseRemotes addr2+ @?= [SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg" "hash"]+testTgtAddr2 = testCase "target IPv4 address"+ $ (targetAddr $ head $ parseRemotes addr2)+ @?= ("1.2.3.4", 22)++addr3 = T.pack "1.2.3.4 alg hash\n11.22.33.44 alg2 hash2\n"+testParse3 = testCase "multiple IPv4 parse"+ $ parseRemotes addr3+ @?= [ SSHRemote (SSH1Addr $ RemoteV4 "1.2.3.4") "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteV4 "11.22.33.44") "alg2" "hash2"+ ]+testTgtAddr3 = testCase "target IPv4 address"+ $ (targetAddr $ head $ parseRemotes addr3)+ @?= ("1.2.3.4", 22)++addr4 = T.pack "::1 alg hash"+testParse4 = testCase "single IPv6 address"+ $ parseRemotes addr4+ @?= [SSHRemote (SSH1Addr $ RemoteV6 "::1") "alg" "hash"]+testTgtAddr4 = testCase "target IPv6 address"+ $ (targetAddr $ head $ parseRemotes addr4)+ @?= ("::1", 22)++addr5 = T.pack "::1 alg hash\n11::2bed alg2 hash2\n"+testParse5 = testCase "multiple IPv6 parse"+ $ parseRemotes addr5+ @?= [ SSHRemote (SSH1Addr $ RemoteV6 "::1") "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteV6 "11::2bed") "alg2" "hash2"+ ]+testTgtAddr5 = testCase "multiple IPv6 address"+ $ (map targetAddr $ parseRemotes addr5)+ @?= [ ("::1", 22)+ , ("11::2bed", 22)+ ]+++addr6 = (T.pack "myhost alg hash")+testParse6 = testCase "single name address parse"+ $ parseRemotes addr6+ @?= [SSHRemote (SSH1Addr $ RemoteDNS ["myhost"]) "alg" "hash"]+testTgtAddr6 = testCase "single name address"+ $ (map targetAddr $ parseRemotes addr6)+ @?= [ ("myhost", 22) ]++addr7 = T.pack "localhost alg hash\nmyhost alg2 hash2\n"+testParse7 = testCase "multiple name parse"+ $ parseRemotes addr7+ @?= [ SSHRemote (SSH1Addr $ RemoteDNS ["localhost"]) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteDNS ["myhost"]) "alg2" "hash2"+ ]+testTgtAddr7 = testCase "multiple name address"+ $ (map targetAddr $ parseRemotes addr7)+ @?= [ ("localhost", 22)+ , ("myhost", 22)+ ]++addr8 = T.pack "my.host.org alg hash"+testParse8 = testCase "single FQDN address parse"+ $ parseRemotes addr8+ @?= [SSHRemote (SSH1Addr $ RemoteDNS ["org", "host", "my"]) "alg" "hash"]+testTgtAddr8 = testCase "single FQDN address"+ $ (map targetAddr $ parseRemotes addr8)+ @?= [ ("my.host.org", 22)]++addr9 = T.pack "localhost.com alg hash\nmy.host.org alg2 hash2\na.b.c.d.f.g alg3 hash3\n"+testParse9 = testCase "multiple FQDN parse"+ $ parseRemotes addr9+ @?= [ SSHRemote (SSH1Addr $ RemoteDNS ["com", "localhost"]) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteDNS ["org", "host", "my"]) "alg2" "hash2"+ , SSHRemote (SSH1Addr $ RemoteDNS ["g", "f", "d", "c", "b", "a"]) "alg3" "hash3"+ ]+testTgtAddr9 = testCase "multiple FQDN address"+ $ (map targetAddr $ parseRemotes addr9)+ @?= [ ("localhost.com", 22)+ , ("my.host.org", 22)+ , ("a.b.c.d.f.g", 22)+ ]++addr12 = T.pack "[1.2.3.4]:56 alg hash"+testParse12 = testCase "single IPv4 port parse"+ $ parseRemotes addr12+ @?= [SSHRemote (SSH1Addr $ RemoteV4Port "1.2.3.4" 56) "alg" "hash"]+testTgtAddr12 = testCase "single IPv4 port address"+ $ (map targetAddr $ parseRemotes addr12)+ @?= [ ("1.2.3.4", 56) ]++addr13 = T.pack "[1.2.3.4]:5 alg hash\n[11.22.33.44]:9876 alg2 hash2\n"+testParse13 = testCase "multiple IPv4 port parse"+ $ parseRemotes addr13+ @?= [ SSHRemote (SSH1Addr $ RemoteV4Port "1.2.3.4" 5) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteV4Port "11.22.33.44" 9876) "alg2" "hash2"+ ]+testTgtAddr13 = testCase "multiple IPv4 port address"+ $ (map targetAddr $ parseRemotes addr13)+ @?= [ ("1.2.3.4", 5)+ , ("11.22.33.44", 9876)+ ]++addr14 = T.pack "[::1]:1234 alg hash"+testParse14 = testCase "single IPv6 port address"+ $ parseRemotes addr14+ @?= [SSHRemote (SSH1Addr $ RemoteV6Port "::1" 1234) "alg" "hash"]+testTgtAddr14 = testCase "single IPv6 port address"+ $ (map targetAddr $ parseRemotes addr14)+ @?= [ ("::1", 1234) ]++addr15 = T.pack "[::1]:100 alg hash\n[11::2bed]:8 alg2 hash2\n"+testParse15 = testCase "multiple IPv4 port address parse"+ $ parseRemotes addr15+ @?= [ SSHRemote (SSH1Addr $ RemoteV6Port "::1" 100) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteV6Port "11::2bed" 8) "alg2" "hash2"+ ]+testTgtAddr15 = testCase "multiple IPv6 port address"+ $ (map targetAddr $ parseRemotes addr15)+ @?= [ ("::1", 100)+ , ("11::2bed", 8)+ ]++addr16 = T.pack "[myhost]:234 alg hash"+testParse16 = testCase "single name port address parse"+ $ parseRemotes addr16+ @?= [SSHRemote (SSH1Addr $ RemoteDNSPort ["myhost"] 234) "alg" "hash"]+testTgtAddr16 = testCase "single name port address"+ $ (map targetAddr $ parseRemotes addr16)+ @?= [ ("myhost", 234) ]+++addr17 = T.pack "[localhost]:80 alg hash\n[myhost]:8080 alg2 hash2\n"+testParse17 = testCase "multiple name port address parse"+ $ parseRemotes addr17+ @?= [ SSHRemote (SSH1Addr $ RemoteDNSPort ["localhost"] 80) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteDNSPort ["myhost"] 8080) "alg2" "hash2"+ ]+testTgtAddr17 = testCase "multiple name port address"+ $ (map targetAddr $ parseRemotes addr17)+ @?= [ ("localhost", 80)+ , ("myhost", 8080)+ ]++addr18 = T.pack "[my.host.org]:975 alg hash"+testParse18 = testCase "single FQDN port address parse"+ $ parseRemotes addr18+ @?= [SSHRemote (SSH1Addr $ RemoteDNSPort ["org", "host", "my"] 975) "alg" "hash"]+testTgtAddr18 = testCase "single FQDN port address"+ $ (map targetAddr $ parseRemotes addr18)+ @?= [ ("my.host.org", 975) ]++addr19 = T.pack "[localhost.com]:79 alg hash\n[my.host.org]:135 alg2 hash2\n[a.b.c.d.f.g]:6 alg3 hash3\n"+testParse19 = testCase "multiple FQDN port address parse"+ $ parseRemotes addr19+ @?= [ SSHRemote (SSH1Addr $ RemoteDNSPort ["com", "localhost"] 79) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteDNSPort ["org", "host", "my"] 135) "alg2" "hash2"+ , SSHRemote (SSH1Addr $ RemoteDNSPort ["g", "f", "d", "c", "b", "a"] 6) "alg3" "hash3"+ ]+testTgtAddr19 = testCase "multiple FQDN port address"+ $ (map targetAddr $ parseRemotes addr19)+ @?= [ ("localhost.com", 79)+ , ("my.host.org", 135)+ , ("a.b.c.d.f.g", 6)+ ]++testParse20 = testCase "single hashed address parse"+ $ let hash = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="+ in parseRemotes (T.pack $ hash <> " alg hash")+ @?= [SSHRemote (SSH1Addr $ RemoteHashed hash) "alg" "hash"]+testTgtAddr20 = testCase "single hashed address"+ $ let hash = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="+ addr20 = T.pack $ hash <> " alg hash"+ in (map targetAddr $ parseRemotes addr20)+ @?= [ ("[{hashed}]", 22) ]+++testParse21 = testCase "multiple hashed address parse"+ $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="+ hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="+ hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="+ in parseRemotes (T.pack $ hash1 <> " alg hash\n"+ <> hash2 <> " alg2 hash2\n"+ <> hash3 <> " alg3 hash3")+ @?= [ SSHRemote (SSH1Addr $ RemoteHashed hash1) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteHashed hash2) "alg2" "hash2"+ , SSHRemote (SSH1Addr $ RemoteHashed hash3) "alg3" "hash3"+ ]++testParse22 = testCase "multiple mixed parse"+ $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="+ hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="+ hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="+ ipv61 = "::1"+ ipv62 = "bad2::74e:b0de"+ ipv41 = "127.0.0.1"+ ipv42 = "9.22.8.33"+ dns1 = "we.are.the.champions"+ dns2 = "no.time"+ in parseRemotes (T.pack $ hash1 <> " alg hash\n"+ <> ipv41 <> " alg4 hash4\n"+ <> "localhost alg0 hash0\n"+ <> hash2 <> " alg2 hash2\n"+ <> ipv61 <> " alg5 hash5\n"+ <> "[" <> ipv42 <> "]:18 alg6 hash6\n"+ <> "[" <> dns1 <> "]:444 alg7 hash7\n"+ <> hash3 <> " alg3 hash3\n"+ <> "[" <> ipv62 <> "]:10101 alg8 hash8\n"+ <> dns2 <> " alg9 hash9\n"+ )+ @?= [ SSHRemote (SSH1Addr $ RemoteHashed hash1) "alg" "hash"+ , SSHRemote (SSH1Addr $ RemoteV4 $ read ipv41) "alg4" "hash4"+ , SSHRemote (SSH1Addr $ RemoteDNS ["localhost"]) "alg0" "hash0"+ , SSHRemote (SSH1Addr $ RemoteHashed hash2) "alg2" "hash2"+ , SSHRemote (SSH1Addr $ RemoteV6 $ read ipv61) "alg5" "hash5"+ , SSHRemote (SSH1Addr $ RemoteV4Port (read ipv42) 18) "alg6" "hash6"+ , SSHRemote (SSH1Addr $ RemoteDNSPort (map T.pack [ "champions"+ , "the"+ , "are"+ , "we"+ ] ) 444) "alg7" "hash7"+ , SSHRemote (SSH1Addr $ RemoteHashed hash3) "alg3" "hash3"+ , SSHRemote (SSH1Addr $ RemoteV6Port (read ipv62) 10101) "alg8" "hash8"+ , SSHRemote (SSH1Addr $ RemoteDNS (map T.pack ["time", "no"])) "alg9" "hash9"+ ]+testTgtAddr22 = testCase "multiple mixed address"+ $ let hash1 = "|1|KRWKNXaEs155ig4mQkpRu5S7/qM=|EL9HQZe22u+xgX7WK7x+794IN6o="+ hash2 = "|1|KRKNXaWE155igs4mkpRu5QS7/qM=|ELHQZe922+xgX7uWKx+7947IN6o="+ hash3 = "|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAA|BBBBBBBBBBBBBBBBBBBBBBBBBBB="+ ipv61 = "::1"+ ipv62 = "bad2::74e:b0de"+ ipv41 = "127.0.0.1"+ ipv42 = "9.22.8.33"+ dns1 = "we.are.the.champions"+ dns2 = "no.time"+ in (map targetAddr $+ parseRemotes (T.pack $ hash1 <> " alg hash\n"+ <> ipv41 <> " alg4 hash4\n"+ <> "localhost alg0 hash0\n"+ <> hash2 <> " alg2 hash2\n"+ <> ipv61 <> " alg5 hash5\n"+ <> "[" <> ipv42 <> "]:18 alg6 hash6\n"+ <> "[" <> dns1 <> "]:444 alg7 hash7\n"+ <> hash3 <> " alg3 hash3\n"+ <> "[" <> ipv62 <> "]:10101 alg8 hash8\n"+ <> dns2 <> " alg9 hash9\n"+ ))+ @?= [ ("[{hashed}]", 22)+ , (T.pack ipv41, 22)+ , ("localhost", 22)+ , ("[{hashed}]", 22)+ , (T.pack ipv61, 22)+ , (T.pack ipv42, 18)+ , (T.pack dns1, 444)+ , ("[{hashed}]", 22)+ , (T.pack ipv62, 10101)+ , (T.pack dns2, 22)+ ]+++testBestTgtAddr1 = testCase "ipv4 addr port first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV4Port "9.10.11.12" 13)+ , (RemoteV4 "1.2.3.4")+ , (RemoteV4 "11.22.33.44")+ , (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("9.10.11.12", 13)++testBestTgtAddr2 = testCase "ipv4 addr port middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteV4 "1.2.3.4")+ , (RemoteV4Port "9.10.11.12" 13)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteV4 "11.22.33.44")+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("9.10.11.12", 13)++testBestTgtAddr3 = testCase "ipv4 port addr end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ , (RemoteV4 "1.2.3.4")+ , (RemoteV4Port "9.10.11.12" 13)+ ]) "alg" "key")+ @?= ("9.10.11.12", 13)++testBestTgtAddr4 = testCase "ipv4 addr first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV4 "1.2.3.4")+ , (RemoteV4 "11.22.33.44")+ , (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("1.2.3.4", 22)++testBestTgtAddr5 = testCase "ipv4 addr middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteV4 "1.2.3.4")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteV4 "11.22.33.44")+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("1.2.3.4", 22)++testBestTgtAddr6 = testCase "ipv4 addr end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteV6Port "::2" 3)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ , (RemoteV4 "1.2.3.4")+ ]) "alg" "key")+ @?= ("1.2.3.4", 22)++testBestTgtAddr7 = testCase "ipv6 addr port first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6Port "::2" 3)+ , (RemoteV6 "::1")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("::2", 3)++testBestTgtAddr8 = testCase "ipv6 addr port middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteV6Port "::2" 3)+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("::2", 3)++testBestTgtAddr9 = testCase "ipv6 addr port end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ , (RemoteV6Port "::2" 3)+ ]) "alg" "key")+ @?= ("::2", 3)++testBestTgtAddr10 = testCase "ipv6 addr first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteV6 "::1")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("::1", 22)++testBestTgtAddr11 = testCase "ipv6 addr middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNS ["foo", "bar"])+ , (RemoteV6 "::1")+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("::1", 22)++testBestTgtAddr12 = testCase "ipv6 addr end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ , (RemoteV6 "::1")+ ]) "alg" "key")+ @?= ("::1", 22)++testBestTgtAddr13 = testCase "dns addr port first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteDNS ["foo", "bar"])+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("moo.cow", 89)++testBestTgtAddr14 = testCase "dns addr port middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNS ["foo", "bar"])+ , (RemoteDNSPort ["cow", "moo"] 89)+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("moo.cow", 89)++testBestTgtAddr15 = testCase "dns addr port end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNS ["foo", "bar"])+ , (RemoteHashed "|1|hash")+ , (RemoteDNSPort ["cow", "moo"] 89)+ ]) "alg" "key")+ @?= ("moo.cow", 89)++testBestTgtAddr16 = testCase "dns addr first"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteDNS ["foo", "bar"])+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("bar.foo", 22)++testBestTgtAddr17 = testCase "dns addr middle"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteHashed "|1|hash")+ , (RemoteDNS ["foo", "bar"])+ , (RemoteHashed "|1|hash")+ ]) "alg" "key")+ @?= ("bar.foo", 22)++testBestTgtAddr18 = testCase "dns addr end"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteHashed "|1|hash")+ , (RemoteDNS ["foo", "bar"])+ ]) "alg" "key")+ @?= ("bar.foo", 22)++testBestTgtAddr19 = testCase "settle for hashed"+ $ (targetAddr $+ SSHRemote (SSHAddrs+ [ (RemoteHashed "|1|hash1")+ , (RemoteHashed "|1|hash2")+ , (RemoteHashed "|1|hash3")+ ]) "alg" "key")+ @?= ("[{hashed}]", 22)