1 module NmcTransform ( seedNmcDom
5 import Prelude hiding (lookup)
6 import Data.ByteString.Lazy (ByteString)
7 import Data.Text.Lazy (splitOn, pack, unpack)
8 import Data.Map.Lazy (empty, lookup, delete, size, singleton
9 , foldrWithKey, insert, insertWith)
10 import Control.Monad (foldM)
11 import Data.Aeson (decode)
12 import Data.Default.Class (def)
16 -- | Perform query and return error string or parsed domain object
18 (String -> IO (Either String ByteString)) -- ^ query operation action
20 -> IO (Either String NmcDom) -- ^ error string or domain
21 queryNmcDom queryOp key = do
24 Left estr -> return $ Left estr
25 Right str -> case decode str :: Maybe NmcDom of
26 Nothing -> return $ Left $ "Unparseable value: " ++ (show str)
27 Just dom -> return $ Right dom
29 -- | Try to fetch "delegate" or "import" object and merge them into the
30 -- base domain. Original "import" element is removed, but newly
31 -- merged data may contain new "import" or "delegate", so the objects
32 -- that are about to be merged are processed recursively until there
33 -- are no more "import" and "deletage" attributes (or the depth gauge
36 (String -> IO (Either String ByteString)) -- ^ query operation action
37 -> Int -- ^ recursion counter
38 -> NmcDom -- ^ base domain
39 -> IO (Either String NmcDom) -- ^ result with merged import
40 mergeIncl queryOp depth base = do
42 mbase = (expandSrv . splitSubdoms . mergeSelf) base
43 base' = mbase {domDelegate = Nothing, domImport = Nothing}
45 if depth <= 0 then return $ Left "Nesting of imports is too deep"
46 else case ((domDelegate mbase), (domImport mbase)) of
47 (Nothing, Nothing ) -> return $ Right base'
48 (Nothing, Just keys) -> foldM mergeIncl1 (Right base') keys
49 (Just key, _ ) -> mergeIncl1 (Right def) key
51 mergeIncl1 (Left err) _ = return $ Left err -- can never happen
52 mergeIncl1 (Right acc) key = do
53 sub <- queryNmcDom queryOp key
55 Left err -> return $ Left err
56 Right sub' -> mergeIncl queryOp (depth - 1) $ sub' `mergeNmcDom` acc
58 -- | If there is an element in the map with key "", merge the contents
59 -- and remove this element. Do this recursively.
60 mergeSelf :: NmcDom -> NmcDom
64 base' = base {domMap = removeSelf map}
65 removeSelf Nothing = Nothing
66 removeSelf (Just map) = if size map' == 0 then Nothing else Just map'
67 where map' = delete "" map
72 case lookup "" map' of
74 Just sub -> (mergeSelf sub) `mergeNmcDom` base'
75 -- recursion depth limited by the size of the record
77 -- | replace Service with Srv down in the Map
78 expandSrv :: NmcDom -> NmcDom
81 base' = base { domService = Nothing }
83 case domService base of
85 Just sl -> foldr addSrvMx base' sl
87 addSrvMx sr acc = sub1 `mergeNmcDom` acc
89 sub1 = def { domMap = Just (singleton proto sub2)
91 sub2 = def { domMap = Just (singleton srvid sub3) }
92 sub3 = def { domSrv = Just [srvStr] }
93 proto = "_" ++ (srvProto sr)
94 srvid = "_" ++ (srvName sr)
95 srvStr = (show (srvPrio sr)) ++ "\t"
96 ++ (show (srvWeight sr)) ++ " "
97 ++ (show (srvPort sr)) ++ " "
100 if srvName sr == "smtp"
101 && srvProto sr == "tcp"
103 then Just [(show (srvPrio sr)) ++ "\t" ++ (srvHost sr)]
106 -- | replace Tls with Tlsa down in the Map
107 -- This function is almost, but not quite, entirely unlike expandSrv.
108 expandTls :: NmcDom -> NmcDom
111 base' = base { domTls = Nothing }
115 Just sl -> foldr addTlsa base' sl
117 addTlsa sr acc = sub1 `mergeNmcDom` acc
119 sub1 = def { domMap = Just (singleton proto sub2) }
120 sub2 = def { domMap = Just (singleton port sub3) }
121 sub3 = def { domTlsa = Just [tlsStr] }
122 proto = "_" ++ (tlsProto sr)
123 port = "_" ++ (tlsName sr)
124 tlsStr = (show (tlsPrio sr)) ++ "\t"
125 ++ (show (tlsWeight sr)) ++ " "
126 ++ (show (tlsPort sr)) ++ " "
130 -- | Convert map elements of the form "subN...sub2.sub1.dom.bit"
131 -- into nested map and merge it
132 splitSubdoms :: NmcDom -> NmcDom
135 base' = base { domMap = Nothing }
139 Just sdmap -> (def { domMap = Just sdmap' }) `mergeNmcDom` base'
141 sdmap' = foldrWithKey stow empty sdmap
142 stow fqdn sdom acc = insertWith mergeNmcDom fqdn' sdom' acc
145 nest (filter (/= "") (splitOnDots fqdn), sdom)
146 splitOnDots s = map unpack (splitOn (pack ".") (pack s))
147 nest ([], v) = (fqdn, v) -- can split result be empty?
148 nest ([k], v) = (k, v)
150 nest (ks, def { domMap = Just (singleton k v) })
152 -- | transfer some elements of `base` into `sub`, notably TLSA
153 propagate :: NmcDom -> NmcDom -> NmcDom
154 propagate base sub = sub -- FIXME implement it
156 -- | Presence of some elements require removal of some others
157 normalizeDom :: NmcDom -> NmcDom
158 normalizeDom dom = foldr id dom [ translateNormalizer
162 nsNormalizer dom = case domNs dom of
164 Just ns -> def { domNs = domNs dom, domEmail = domEmail dom }
165 translateNormalizer dom = case domTranslate dom of
167 Just tr -> dom { domMap = Nothing }
169 -- | Merge imports and Selfs and follow the maps tree to get dom
171 (String -> IO (Either String ByteString)) -- ^ query operation action
172 -> [String] -- ^ subdomain chain
173 -> NmcDom -- ^ base domain
174 -> IO (Either String NmcDom) -- ^ fully processed result
175 descendNmcDom queryOp subdom base = do
176 base' <- mergeIncl queryOp 10 base
178 [] -> return $ fmap normalizeDom base'
181 Left err -> return base'
183 case domMap base'' of
184 Nothing -> return $ Right def
187 Nothing -> return $ Right def
188 Just sub -> descendNmcDom queryOp ds $ propagate base'' sub
190 -- | Initial NmcDom populated with "import" only, suitable for "descend"
192 String -- ^ domain key (without namespace prefix)
193 -> NmcDom -- ^ resulting seed domain
194 seedNmcDom dn = def { domImport = Just (["d/" ++ dn])}