dnsrbl-0.0.1: Network/DNSRBL.hs
{- |
Module : Network.DNSRBL
Copyright : (c) 2008 Holden Karau
License : LGPL
Maintainer : holden@pigscanfly.ca
Stability : provisional
Portability : portable
Anynchronously lookup a host on multiple DSNRBLs.
-}
module Network.DNSRBL(dorbls,dorblf,asanequery,sanequery) where
import ADNS (HostName,HostAddress,initResolver,Resolver,InitFlag(..),queryA)
import ADNS.Endian (readWord32)
import Control.Concurrent.Chan ( Chan, newChan, writeChan, readChan )
import Control.Monad ( replicateM )
import Control.Concurrent ( forkIO )
import Data.List (sort,group)
-- |A 'RBL' data type contains the information about a real time blacklist.
-- The names are the names of the different black lists
-- and are paired with the expected result
-- The server is the server which does the resolution
-- ip is true if the RBL support lookups on IP addresses (i.e. 127.0.0.1)
-- name is true if the RBL lookups names (i.e. foo.com)
data RBL = RBL { namexp :: [(String,String)],
server :: String,
ip :: Bool,
name :: Bool} deriving Show
-- |'rbls' is the list of real time black lists used
rbls :: [RBL]
rbls = (RBL [("SBL","127.0.0.2"),
("CBL","127.0.0.4"),
("NJBL","127.0.0.5"),
("PBLI","127.0.0.10"),
("PBLS","127.0.0.11")]
"zen.spamhaus.org"
True
False):(RBL [("INTERSERVE","127.0.0.2")]
"rbl.interserver.net"
True
False):[]
-- | 'toRR' convers a HostAddress to a string in reverse
-- (i.e. 127.0.0.1 printed as 1.0.0.127)
toRR :: HostAddress -> String
toRR ha = shows b4 . ('.':) .
shows b3 . ('.':) .
shows b2 . ('.':) .
shows b1 $ ""
where
(b1,b2,b3,b4) = readWord32 ha
-- | 'toPR' converts a HostAddress to a string
toPR :: HostAddress -> String
toPR ha = shows b1 . ('.':).
shows b2 . ('.':).
shows b3 . ('.':).
shows b4 $ ""
where
(b1,b2,b3,b4) = readWord32 ha
-- |Wrap queryA and return the result
-- or in the event of an error an empty list
myqueryA :: Resolver -> HostName -> IO [HostAddress]
myqueryA resolver host = queryA resolver host >>=(\a -> case a of
(Just b) -> return b
_ -> return [] )
-- |Get the lookup strings host name in all the RBLs
-- (resolving its IP address for the IP based RBLS)
rstrs :: String -> [HostAddress] -> [(RBL,String)]
rstrs host ips = (namerstrs host )++(concatMap iprstrs ips)
-- |Get the lookup strings for name based RBLs
namerstrs :: HostName -> [(RBL,String)]
namerstrs host = zip (filter name rbls) (map (\rbl -> (host++"."++(server rbl) )) (filter name rbls) )
-- Get the lookup string for the ip based RBLs
iprstrs :: HostAddress -> [(RBL,String)]
iprstrs host = zip (filter ip rbls) (map (\rbl -> ((toRR host)++"."++(server rbl) )) (filter ip rbls) )
-- |Is match compares a HostAddress and a String and sees if they are a match
ismatch :: String -> HostAddress -> Bool
ismatch str haddr = ((toPR haddr) == str)
-- |Takes and RBL and a HostAddress list and return a list of strings
-- (where the string is the name of the RBL) & the bool is where it is listed or not
rrtsb :: RBL -> [HostAddress] -> [(String,Bool)]
rrtsb rbl res = (map (\x -> ((fst x), foldr (||) False (map (ismatch (snd x)) res))) (namexp rbl) )
-- | dowork does the semi-heavy lifting
dowork :: Resolver -> Chan [(String,Bool)] -> (RBL, HostName) -> IO ()
dowork resolver channel query=do
a <- (myqueryA resolver (snd query) )
writeChan channel (rrtsb (fst query) a)
-- |'dorbls' is a friendly wrapper around
-- dorblf which only requires a hostname
dorbls :: String -> IO [(String,Bool)]
dorbls host = initResolver [NoErrPrint, NoServerWarn] $ \resolver -> do
hostip <- (myqueryA resolver host )
results <- (dorblf host hostip resolver)
return results
-- |'dorblf' returns a list of (String,Bool) where
-- the string is the RBL name and Bool is if it was found or not
-- Note: There may be multiple instances of the same string
-- with different Bool values since one hostname may resolve to multiple IPs
-- some of which may match and some of which may not match
dorblf :: String -> [HostAddress] -> Resolver -> IO [(String,Bool)]
dorblf host ips resolver = do
rrChannel <- newChan :: IO (Chan [(String,Bool)])
--results <- getChanContents rrChannel
mapM_ (\h -> forkIO (dowork resolver rrChannel h)) queries
--wait (length queries) results 0
--return results
s <- replicateM (length queries) (readChan rrChannel )
return (concat s)
where
queries = (rstrs host ips)
-- | 'sanquery' is a Wrapper of "dorbls" which has only one instance
-- of each RBL and
-- if any of the elements were found in the RBL (name, any of the IPs)
-- it is true, otherwise it is false
sanequery :: String -> IO [(String,Bool)]
sanequery host = do
results <- dorbls host
return (map (\x-> ((fst (head x)),(foldl (\y z -> y || (snd z)) False x) ) )
(group (sort (results))) )
-- | 'asanequery' is a wrapper of dorblf which has only one instance of RBL and
-- if any of the elements were found in the RBL (name, any of the IPs)
-- if it is true otherwise it is false.
asanequery :: String -> [HostAddress] -> Resolver -> IO [(String,Bool)]
asanequery host ipl resolver = do
results <- dorblf host ipl resolver
return (map (\x-> ((fst (head x)),(foldl (\y z -> y || (snd z)) False x) ) )
(group (sort (results))) )