3 module Distribution
.Client
.Security
.DNS
4 ( queryBootstrapMirrors
8 import Distribution
.Client
.Compat
.Prelude
9 import Network
.URI
(URI
(..), URIAuth
(..), parseURI
)
10 import Control
.Exception
(try)
11 import Distribution
.Simple
.Utils
13 #if defined
(MIN_VERSION_resolv
) || defined
(MIN_VERSION_windns
)
14 import Network
.DNS
(queryTXT
, Name
(..), CharStr
(..))
15 import qualified Data
.ByteString
.Char8
as BS
.Char8
17 import Distribution
.Simple
.Program
.Db
18 ( emptyProgramDb
, addKnownProgram
19 , configureAllKnownPrograms
, lookupProgram
)
20 import Distribution
.Simple
.Program
23 , getProgramInvocationOutput
)
26 -- | Try to lookup RFC1464-encoded mirror urls for a Hackage
27 -- repository url by performing a DNS TXT lookup on the
28 -- @_mirrors.@-prefixed URL hostname.
30 -- Example: for @http://hackage.haskell.org/@
31 -- perform a DNS TXT query for the hostname
32 -- @_mirrors.hackage.haskell.org@ which may look like e.g.
34 -- > _mirrors.hackage.haskell.org. 300 IN TXT
35 -- > "0.urlbase=http://hackage.fpcomplete.com/"
36 -- > "1.urlbase=http://objects-us-west-1.dream.io/hackage-mirror/"
38 -- NB: hackage-security doesn't require DNS lookups being trustworthy,
39 -- as the trust is established via the cryptographically signed TUF
40 -- meta-data that is retrieved from the resolved Hackage repository.
41 -- Moreover, we already have to protect against a compromised
42 -- @hackage.haskell.org@ DNS entry, so an the additional
43 -- @_mirrors.hackage.haskell.org@ DNS entry in the same SOA doesn't
44 -- constitute a significant new attack vector anyway.
46 queryBootstrapMirrors
:: Verbosity
-> URI
-> IO [URI
]
48 #if defined
(MIN_VERSION_resolv
) || defined
(MIN_VERSION_windns
)
49 -- use @resolv@ package for performing DNS queries
50 queryBootstrapMirrors verbosity repoUri
51 | Just auth
<- uriAuthority repoUri
= do
52 let mirrorsDnsName
= Name
(BS
.Char8
.pack
("_mirrors." ++ uriRegName auth
))
55 txts
<- queryTXT mirrorsDnsName
56 evaluate
(force
$ extractMirrors
(map snd txts
))
58 mirrors
<- case mirrors
' of
60 warn verbosity
("Caught exception during _mirrors lookup:"++
61 displayException
(e
:: SomeException
))
66 then warn verbosity
("No mirrors found for " ++ show repoUri
)
67 else do info verbosity
("located " ++ show (length mirrors
) ++
68 " mirrors for " ++ show repoUri
++ " :")
69 for_ mirrors
$ \url
-> info verbosity
("- " ++ show url
)
73 |
otherwise = return []
75 -- | Extract list of mirrors from 'queryTXT' result
76 extractMirrors
:: [[CharStr
]] -> [URI
]
77 extractMirrors txtChunks
= mapMaybe (parseURI
. snd) . sort $ vals
79 vals
= [ (kn
,v
) | CharStr e
<- concat txtChunks
80 , Just
(k
,v
) <- [splitRfc1464
(BS
.Char8
.unpack e
)]
81 , Just kn
<- [isUrlBase k
]
84 ----------------------------------------------------------------------------
85 #else /* !defined
(MIN_VERSION_resolv
) */
86 -- use external method via @nslookup@
87 queryBootstrapMirrors verbosity repoUri
88 | Just auth
<- uriAuthority repoUri
= do
89 progdb
<- configureAllKnownPrograms verbosity
$
90 addKnownProgram nslookupProg emptyProgramDb
92 case lookupProgram nslookupProg progdb
of
94 warn verbosity
"'nslookup' tool missing - can't locate mirrors"
98 let mirrorsDnsName
= "_mirrors." ++ uriRegName auth
101 out
<- getProgramInvocationOutput verbosity
$
102 programInvocation nslookup
["-query=TXT", mirrorsDnsName
]
103 evaluate
(force
$ extractMirrors mirrorsDnsName out
)
105 mirrors
<- case mirrors
' of
107 warn verbosity
("Caught exception during _mirrors lookup:"++
108 displayException
(e
:: SomeException
))
113 then warn verbosity
("No mirrors found for " ++ show repoUri
)
114 else do info verbosity
("located " ++ show (length mirrors
) ++
115 " mirrors for " ++ show repoUri
++ " :")
116 for_ mirrors
$ \url
-> info verbosity
("- " ++ show url
)
120 |
otherwise = return []
122 nslookupProg
= simpleProgram
"nslookup"
124 -- | Extract list of mirrors from @nslookup -query=TXT@ output.
125 extractMirrors
:: String -> String -> [URI
]
126 extractMirrors hostname s0
= mapMaybe (parseURI
. snd) . sort $ vals
128 vals
= [ (kn
,v
) |
(h
,ents
) <- fromMaybe [] $ parseNsLookupTxt s0
131 , Just
(k
,v
) <- [splitRfc1464 e
]
132 , Just kn
<- [isUrlBase k
]
135 -- | Parse output of @nslookup -query=TXT $HOSTNAME@ tolerantly
136 parseNsLookupTxt
:: String -> Maybe [(String,[String])]
137 parseNsLookupTxt
= go0
[] []
139 -- approximate grammar:
140 -- <entries> := { <entry> }
141 -- (<entry> starts at begin of line, but may span multiple lines)
142 -- <entry> := ^ <hostname> TAB "text =" { <qstring> }
143 -- <qstring> := string enclosed by '"'s ('\' and '"' are \-escaped)
145 -- scan for ^ <word> <TAB> "text ="
146 go0
[] _
[] = Nothing
147 go0 res _
[] = Just
(reverse res
)
148 go0 res _
('\n':xs
) = go0 res
[] xs
149 go0 res lw
('\t':'t
':'e
':'x
':'t
':' ':'=':xs
) = go1 res
(reverse lw
) [] (dropWhile isSpace xs
)
150 go0 res lw
(x
:xs
) = go0 res
(x
:lw
) xs
152 -- collect at least one <qstring>
153 go1 res lw qs
('"':xs) = case qstr "" xs of
154 Just (s, xs') -> go1 res lw (s:qs) (dropWhile isSpace xs')
155 Nothing -> Nothing -- bad quoting
156 go1 _ _ [] _ = Nothing -- missing qstring
157 go1 res lw qs xs = go0 ((lw,reverse qs):res) [] xs
159 qstr _ ('\n':_) = Nothing -- We don't support unquoted LFs
160 qstr acc ('\\':'\\':cs) = qstr ('\\':acc) cs
161 qstr acc ('\\':'"':cs
) = qstr
('"':acc) cs
162 qstr acc ('"':cs
) = Just
(reverse acc
, cs
)
163 qstr acc
(c
:cs
) = qstr
(c
:acc
) cs
167 ----------------------------------------------------------------------------
169 -- | Helper used by 'extractMirrors' for extracting @urlbase@ keys from Rfc1464-encoded data
170 isUrlBase
:: String -> Maybe Int
172 |
".urlbase" `
isSuffixOf` s
, not (null ns
), all isDigit ns
= readMaybe ns
173 |
otherwise = Nothing
175 ns
= take (length s
- 8) s
177 -- | Split a TXT string into key and value according to RFC1464.
178 -- Returns 'Nothing' if parsing fails.
179 splitRfc1464
:: String -> Maybe (String,String)
183 go acc
('`
':c
:cs
) = go
(c
:acc
) cs
184 go acc
('=':cs
) = go2
(reverse acc
) "" cs
186 |
isSpace c
= go acc cs
187 |
otherwise = go
(c
:acc
) cs
189 go2 k acc
[] = Just
(k
,reverse acc
)
190 go2 _ _
['`
'] = Nothing
191 go2 k acc
('`
':c
:cs
) = go2 k
(c
:acc
) cs
192 go2 k acc
(c
:cs
) = go2 k
(c
:acc
) cs