module DNS ( LookupResult, dns_tests, lookupMX', lookupNS', normalize, normalize_case, resolve_address ) where import Control.Applicative ((<$>)) import Control.Monad (liftM) import qualified Data.ByteString.Char8 as BS ( append, last, map, pack ) import Data.Char (toLower) import Data.IP (IPv4) import Network.DNS ( Domain, DNSFormat(..), Resolver, RDATA(..), TYPE(..), defaultResolvConf, lookupA, lookupMX, lookupNS, lookupRaw, makeResolvSeed, rdata, rrtype, withResolver ) import Test.Framework (Test, testGroup) import Test.Framework.Providers.HUnit (testCase) import Test.HUnit (assertEqual) import Text.Read (readMaybe) type LookupResult = (Domain, Maybe [Domain]) -- | Perform a query, but take the result from the authority section -- of the response rather than the answer section. Code shamelessly -- stolen from Network.DNS.lookup. lookup_authority :: Resolver -> Domain -> TYPE -> IO (Maybe [RDATA]) lookup_authority rlv dom typ = (>>= toRDATA) <$> lookupRaw rlv dom typ where correct r = rrtype r == typ listToMaybe [] = Nothing listToMaybe xs = Just xs toRDATA = listToMaybe . map rdata . filter correct . authority -- | Like lookupNS, except we take the result from the authority -- section of the response (as opposed to the answer section). lookupNS_authority :: Resolver -> Domain -> IO (Maybe [Domain]) lookupNS_authority rlv dom = toNS <$> DNS.lookup_authority rlv dom NS where toNS = fmap (map unTag) unTag (RD_NS dm) = dm unTag _ = error "lookupNS_authority" resolve_address :: String -> IO (Maybe IPv4) resolve_address s = case read_result of Just _ -> return read_result Nothing -> do default_rs <- makeResolvSeed defaultResolvConf withResolver default_rs $ \resolver -> do result <- lookupA resolver (BS.pack s) return $ case result of Just (x:_) -> Just x _ -> Nothing where read_result :: Maybe IPv4 read_result = readMaybe s lookupMX' :: Resolver -> Domain -> IO LookupResult lookupMX' resolver domain = liftM (pair_em . drop_priority) $ lookupMX resolver domain where drop_priority :: Maybe [(Domain, Int)] -> Maybe [Domain] drop_priority = fmap (map fst) pair_em :: a -> (Domain, a) pair_em = (,) domain -- This function keeps the domain matches with its NS records. lookupNS' :: Resolver -> Domain -> IO LookupResult lookupNS' resolver domain = do answer_result <- lookupNS resolver domain auth_result <- lookupNS_authority resolver domain liftM pair_em $ return $ combine answer_result auth_result where pair_em :: a -> (Domain, a) pair_em = (,) domain combine :: (Maybe [Domain]) -> (Maybe [Domain]) -> (Maybe [Domain]) combine Nothing Nothing = Nothing combine m1 Nothing = m1 combine Nothing m2 = m2 combine (Just ds1) (Just ds2) = Just (ds1 ++ ds2) -- | Normalize the given name by lowercasing and appending a trailing -- dot (the root) if necessary. normalize :: Domain -> Domain normalize = normalize_case . normalize_root normalize_root :: Domain -> Domain normalize_root d | BS.last d == '.' = d | otherwise = d `BS.append` trailing_dot where trailing_dot = BS.pack "." normalize_case :: Domain -> Domain normalize_case = BS.map toLower test_normalize_case :: Test test_normalize_case = testCase desc $ assertEqual desc expected actual where desc = "normalize_case lowercases DNS names" expected = BS.pack "example.com" actual = normalize_case $ BS.pack "ExAmPlE.COM" dns_tests :: Test dns_tests = testGroup "DNS Tests" [ test_normalize_case ]