1 {-# LANGUAGE DoAndIfThenElse #-}
2 {-# LANGUAGE RecordWildCards #-}
7 import Control.Concurrent.ParallelIO.Global (
10 import Control.Monad ( unless )
11 import qualified Data.ByteString.Char8 as BS (
21 ResolvConf( resolvTimeout ),
25 import Network.DNS.Lookup ( lookupA, lookupMX )
26 import System.Directory ( doesFileExist )
27 import System.Exit ( exitWith, ExitCode( ExitFailure ) )
38 Args( Args, accept_a, input_file, output_file, rfc5322 ),
44 import ExitCodes ( exit_input_file_doesnt_exist )
47 -- | Resolver parameters. We increase the default timeout from 3 to 10
49 resolv_conf :: ResolvConf
50 resolv_conf = defaultResolvConf { resolvTimeout = 10 * 1000 * 1000 }
52 -- | A list of common domains, there's no need to waste MX lookups
54 common_domains :: [Domain]
55 common_domains = map BS.pack [ "aol.com",
63 -- | Check whether the given domain has a valid MX record.
64 validate_mx :: Resolver -> Domain -> IO Bool
65 validate_mx resolver domain
66 | domain `elem` common_domains = return True
68 result <- lookupMX resolver domain
70 -- A list of one or more elements?
71 Right (_:_) -> return True
75 -- | Check whether the given domain has a valid A record.
76 validate_a :: Resolver -> Domain -> IO Bool
77 validate_a resolver domain
78 | domain `elem` common_domains = return True
80 result <- lookupA resolver domain
82 Right (_:_) -> return True
86 -- | Validate an email address by doing some simple syntax checks and
87 -- (if those fail) an MX lookup. We don't count an A record as a mail
89 validate :: Resolver -> Bool -> Bool -> Address -> IO (Address, Bool)
90 validate resolver accept_a rfc5322 address = do
91 let valid_syntax = validate_syntax rfc5322 address
92 if valid_syntax then do
93 let (_,domain) = parts address
94 mx_result <- validate_mx resolver domain
96 then return (address, True)
100 a_result <- validate_a resolver domain
101 return (address, a_result)
103 return (address, False)
105 return (address, False)
113 -- Get the input from either stdin, or the file given on the command
115 input <- case input_file of
116 Nothing -> BS.hGetContents stdin
118 is_file <- doesFileExist path
120 exitWith (ExitFailure exit_input_file_doesnt_exist)
123 -- Do the same for the output handle and stdout.
124 output_handle <- case output_file of
125 Nothing -> return stdout
126 Just path -> openFile path WriteMode
128 -- Split the input into lines.
129 let addresses = BS.lines input
131 -- And remove the empty ones.
132 let nonempty_addresses = filter (not . BS.null) addresses
134 rs <- makeResolvSeed resolv_conf
135 let validate' addr = withResolver rs $ \resolver ->
136 validate resolver accept_a rfc5322 addr
138 -- Construct a list of [IO (Address, Bool)]. The withResolver calls
139 -- are the ones that should be run in parallel.
140 let actions = map validate' nonempty_addresses
142 -- Run the lookup actions in parallel.
143 results <- parallelInterleaved actions
145 -- Filter the bad ones.
146 let valid_results = filter snd results
148 -- Output the results.
149 let valid_addresses = map fst valid_results
150 mapM_ (BS.hPutStrLn output_handle) valid_addresses
154 -- Clean up. It's safe to try to close stdout.