diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,32 @@
+dnscache license
+Copyright (c) 2010, Ertugrul Soeylemez
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in
+      the documentation and/or other materials provided with the
+      distribution.
+
+    * Neither the name of the author nor the names of any contributors
+      may be used to endorse or promote products derived from this
+      software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MassDns.hs b/MassDns.hs
new file mode 100644
--- /dev/null
+++ b/MassDns.hs
@@ -0,0 +1,56 @@
+-- |
+-- Module:     Main
+-- Copyright:  (c) 2010 Ertugrul Soeylemez
+-- License:    BSD3
+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
+-- Stability:  experimental
+
+module Main where
+
+import Network.DnsCache
+import System.Environment
+import System.IO
+import Text.Printf
+
+
+printEntry :: String -> String -> [String] -> IO ()
+printEntry _ _ [] = return ()
+printEntry d t es = printf "%s %s %s\n" d t (unwords es)
+
+
+printDns :: Int -> MassType -> [String] -> IO ()
+printDns numThreads mtype domains =
+    seq mtype . withDnsCache numThreads 300 $ \dns ->
+        massLookupReport dns mtype domains $ \res -> do
+            let dom = massDomain res
+            printEntry dom "A"    $ map show (massA res)
+            printEntry dom "AAAA" $ map show (massAAAA res)
+            printEntry dom "MX"   $ massMX res
+
+
+putHelp :: IO ()
+putHelp = do
+    progName <- getProgName
+    hPrintf stderr "Usage: %s NUMTHREADS TYPE [DOMAINS]\n\n" progName
+    hPutStrLn stderr "If no domains are specified, domain list is read from stdin."
+    hPutStrLn stderr "TYPE can be one of 'A', 'AAAA', 'MX' and 'all'."
+
+
+toMassType :: String -> MassType
+toMassType "A" = MassA
+toMassType "AAAA" = MassAAAA
+toMassType "MX" = MassMX
+toMassType "all" = MassAll
+toMassType _ = error "Unknown resource type."
+
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+      ["-h"]             -> putHelp
+      ["--help"]         -> putHelp
+      [ntStr,typeStr]    -> fmap lines getContents >>=
+                            printDns (read ntStr) (toMassType typeStr)
+      ntStr:typeStr:doms -> printDns (read ntStr) (toMassType typeStr) doms
+      _                  -> putHelp
diff --git a/Network/DnsCache.hs b/Network/DnsCache.hs
new file mode 100644
--- /dev/null
+++ b/Network/DnsCache.hs
@@ -0,0 +1,318 @@
+-- |
+-- Module:     Network.DnsCache
+-- Copyright:  (c) 2010 Ertugrul Soeylemez
+-- License:    BSD3
+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
+-- Stability:  experimental
+--
+-- This module implements an asynchronous, caching DNS resolver.
+
+{-# LANGUAGE GADTs, TypeFamilies #-}
+
+module Network.DnsCache
+    ( -- * DNS cache
+      DnsCache,
+      withDnsCache,
+
+      -- * DNS lookup
+      resolveA,
+      resolveAAAA,
+      resolveMX,
+
+      -- * DNS mass lookup
+      MassResult(..),
+      MassType(..),
+      massLookup,
+      massLookupReport
+    )
+    where
+
+import qualified Control.Exception as E
+import qualified Data.Map as M
+import Control.Concurrent
+import Control.ContStuff
+import Data.Char
+import Data.IP
+import Data.List
+import Data.Map (Map)
+import Data.Ord
+import Data.Time.Clock
+import Network.DNS hiding (lookup)
+
+
+-- | Command to a DNS cache.
+
+data CacheCmd res
+    = CacheGet Domain (Waiter res)
+    | CacheQuit (IO ())
+    | CacheRequest Domain (Bool -> IO ())
+    | CacheSetNotFound Domain
+    | CacheSetResolved Domain [res]
+
+
+-- | DNS cache configuration.
+
+data DnsCache =
+    DnsCache {
+      dnsAVar    :: MVar (CacheCmd IPv4),
+      dnsAaaaVar :: MVar (CacheCmd IPv6),
+      dnsMxVar   :: MVar (CacheCmd Domain),
+      dnsResVar  :: MVar ResolverCmd
+    }
+
+
+-- | DNS cache entry.
+
+data Entry res
+    = NotFound
+    | Resolving [Waiter res]
+    | Resolved UTCTime [res]
+
+
+-- | Result of a mass lookup.
+
+data MassResult =
+    MassResult {
+      massDomain :: Domain,
+      massA      :: [IPv4],
+      massAAAA   :: [IPv6],
+      massMX     :: [Domain]
+    }
+    deriving Show
+
+
+-- | The type of resources to look up.
+
+data MassType = MassA | MassAAAA | MassMX | MassAll
+
+
+-- | DNS resolver command.
+
+data ResolverCmd where
+    Resolve ::
+        Domain ->
+        MVar (CacheCmd res) ->
+        (Resolver -> Domain -> IO (Maybe [res])) ->
+        Waiter res ->
+        ResolverCmd
+    ResolverQuit :: IO () -> ResolverCmd
+
+
+-- | DNS resolver waiter.
+
+type Waiter res = Maybe [res] -> IO ()
+
+
+-- | DNS cache thread.
+
+dnsCache :: NominalDiffTime -> MVar (CacheCmd res) -> IO ()
+dnsCache timeout cmdVar =
+    evalStateT M.empty . forever $ do
+        cmd <- io $ takeMVar cmdVar
+        case cmd of
+          CacheGet dom waiter -> do
+              mCache <- getField (M.lookup dom)
+              case mCache of
+                Nothing       -> io $ waiter Nothing
+                Just NotFound -> io $ waiter Nothing
+                Just (Resolved _ res) -> io $ waiter (Just res)
+                Just (Resolving ws) ->
+                    modify (M.insert dom (Resolving (waiter:ws)))
+
+          CacheQuit c -> io c >> abort ()
+
+          CacheRequest dom c -> do
+              mCache <- getField (M.lookup dom)
+              curTime <- io getCurrentTime
+              case mCache of
+                Just (Resolving _) -> io $ c False
+                Just (Resolved time _) | diffUTCTime curTime time < timeout ->
+                    io $ c False
+                _ -> do
+                    modify (M.insert dom (Resolving []))
+                    io $ c True
+
+          CacheSetNotFound dom -> do
+              mCache <- getField (M.lookup dom)
+              case mCache of
+                Just (Resolving cs) -> io $ mapM_ ($ Nothing) cs
+                _                   -> return ()
+              modify (M.insert dom NotFound)
+
+          CacheSetResolved dom res -> do
+              mCache <- getField (M.lookup dom)
+              case mCache of
+                Just (Resolving cs) -> io $ mapM_ ($ Just res) cs
+                _                   -> return ()
+              curTime <- io getCurrentTime
+              modify (M.insert dom (Resolved curTime res))
+
+
+-- | Perform a mass lookup.
+
+massLookup :: DnsCache -> MassType -> [Domain] -> IO (Map Domain MassResult)
+massLookup dns mtype domains = do
+    let numDomains = length domains
+    resultVar <- newEmptyMVar
+    massResolver dns mtype domains resultVar
+    execStateT M.empty . replicateM_ numDomains $ do
+        m@(MassResult dom _ _ _) <- io $ takeMVar resultVar
+        modify (M.insert dom m)
+
+
+-- | Perform a mass lookup with report function.
+
+massLookupReport ::
+    (Base m ~ IO, LiftBase m, Monad m) =>
+    DnsCache -> MassType -> [Domain] -> (MassResult -> m ()) -> m ()
+massLookupReport dns mtype domains rep = do
+    let numDomains = length domains
+    resultVar <- io newEmptyMVar
+    io $ massResolver dns mtype domains resultVar
+    replicateM_ numDomains $ io (takeMVar resultVar) >>= rep
+
+
+-- | Mass resolver.
+
+massResolver :: DnsCache -> MassType -> [Domain] -> MVar MassResult -> IO ()
+massResolver dns mtype domains resultVar = do
+    forM_ domains $ \domain ->
+        forkIO $ do
+            case mtype of
+              MassA -> do
+                  resVar <- newEmptyMVar
+                  forkIO $ resolveA dns domain >>= putMVar resVar . maybe [] id
+                  res <- takeMVar resVar
+                  putMVar resultVar (MassResult domain res [] [])
+
+              MassAAAA -> do
+                  resVar <- newEmptyMVar
+                  forkIO $ resolveAAAA dns domain >>= putMVar resVar . maybe [] id
+                  res <- takeMVar resVar
+                  putMVar resultVar (MassResult domain [] res [])
+
+              MassMX -> do
+                  resVar <- newEmptyMVar
+                  forkIO $ resolveMX dns domain >>= putMVar resVar . maybe [] id
+                  res <- takeMVar resVar
+                  putMVar resultVar (MassResult domain [] [] res)
+
+              MassAll -> do
+                  aVar <- newEmptyMVar
+                  aaaaVar <- newEmptyMVar
+                  mxVar <- newEmptyMVar
+
+                  forkIO $ resolveA dns domain >>= putMVar aVar . maybe [] id
+                  forkIO $ resolveAAAA dns domain >>= putMVar aaaaVar . maybe [] id
+                  forkIO $ resolveMX dns domain >>= putMVar mxVar . maybe [] id
+
+                  a <- takeMVar aVar
+                  aaaa <- takeMVar aaaaVar
+                  mx <- takeMVar mxVar
+                  putMVar resultVar (MassResult domain a aaaa mx)
+
+
+-- | Resolve an A record.
+
+resolveA :: DnsCache -> Domain -> IO (Maybe [IPv4])
+resolveA cfg dom = do
+    let cacheVar = dnsAVar cfg
+    let resVar = dnsResVar cfg
+
+    answerVar <- newEmptyMVar
+    putMVar resVar (Resolve dom cacheVar lookupA (putMVar answerVar))
+    takeMVar answerVar
+
+
+-- | Resolve an AAAA record.
+
+resolveAAAA :: DnsCache -> Domain -> IO (Maybe [IPv6])
+resolveAAAA cfg dom = do
+    let cacheVar = dnsAaaaVar cfg
+    let resVar = dnsResVar cfg
+
+    answerVar <- newEmptyMVar
+    putMVar resVar (Resolve dom cacheVar lookupAAAA (putMVar answerVar))
+    takeMVar answerVar
+
+
+-- | Resolve an MX record.
+
+resolveMX :: DnsCache -> Domain -> IO (Maybe [Domain])
+resolveMX cfg dom = do
+    let cacheVar = dnsMxVar cfg
+    let resVar = dnsResVar cfg
+
+    let mxLookup r d = fmap (map fst . sortBy (comparing snd)) <$> lookupMX r d
+
+    answerVar <- newEmptyMVar
+    putMVar resVar (Resolve dom cacheVar mxLookup (putMVar answerVar))
+    takeMVar answerVar
+
+
+-- | DNS resolver thread.
+
+resolver :: MVar ResolverCmd -> IO ()
+resolver cmdVar = do
+    seed <- makeResolvSeed defaultResolvConf
+    withResolver seed $ \resolver ->
+        evalContT . forever $ do
+            cmd <- io $ takeMVar cmdVar
+            case cmd of
+              Resolve dom' cacheVar dnsLookup waiter ->
+                  io $ do
+                      let dom = map toLower dom'
+                      mayResVar <- newEmptyMVar
+                      putMVar cacheVar (CacheRequest dom (putMVar mayResVar))
+                      mayResolve <- takeMVar mayResVar
+
+                      if mayResolve
+                        then do
+                            mRes <- dnsLookup resolver dom
+                            case mRes of
+                              Nothing ->
+                                  putMVar cacheVar (CacheSetNotFound dom) >>
+                                  waiter Nothing
+                              Just res ->
+                                  putMVar cacheVar (CacheSetResolved dom res) >>
+                                  waiter (Just res)
+                        else do
+                            resVar <- newEmptyMVar
+                            putMVar cacheVar (CacheGet dom (putMVar resVar))
+                            takeMVar resVar >>= waiter
+
+              ResolverQuit c -> io c >> abort ()
+
+
+-- | Start a DNS cache with the given number of resolver threads and the
+-- given cache timeout.
+
+withDnsCache :: Int -> NominalDiffTime -> (DnsCache -> IO a) -> IO a
+withDnsCache numThreads timeout comp = do
+    resolverVar <- newEmptyMVar
+    aVar <- newEmptyMVar
+    aaaaVar <- newEmptyMVar
+    mxVar <- newEmptyMVar
+
+    replicateM_ numThreads . forkIO $ resolver resolverVar
+    forkIO $ dnsCache timeout aVar
+    forkIO $ dnsCache timeout aaaaVar
+    forkIO $ dnsCache timeout mxVar
+
+    let cfg = DnsCache {
+                dnsAVar = aVar,
+                dnsAaaaVar = aaaaVar,
+                dnsMxVar = mxVar,
+                dnsResVar = resolverVar
+              }
+
+    E.finally (comp cfg) $ do
+        quitVar <- newEmptyMVar
+        replicateM_ numThreads $ do
+            putMVar resolverVar (ResolverQuit $ putMVar quitVar ())
+            takeMVar quitVar
+
+        putMVar aVar (CacheQuit (putMVar quitVar ()))
+        putMVar aaaaVar (CacheQuit (putMVar quitVar ()))
+        putMVar mxVar (CacheQuit (putMVar quitVar ()))
+        replicateM_ 3 $ takeMVar quitVar
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,12 @@
+dnscache setup script
+Copyright (C) 2010, Ertugrul Soeylemez
+
+Please see the LICENSE file for terms and conditions of use,
+modification and distribution of this package, including this file.
+
+> module Main where
+>
+> import Distribution.Simple
+>
+> main :: IO ()
+> main = defaultMain
diff --git a/dnscache.cabal b/dnscache.cabal
new file mode 100644
--- /dev/null
+++ b/dnscache.cabal
@@ -0,0 +1,34 @@
+Name:          dnscache
+Version:       0.1.0
+Category:      Network
+Synopsis:      Caching DNS resolver library and mass DNS resolver utility
+Maintainer:    Ertugrul Söylemez <es@ertes.de>
+Author:        Ertugrul Söylemez <es@ertes.de>
+Copyright:     (c) 2010 Ertugrul Söylemez
+License:       BSD3
+License-file:  LICENSE
+Build-type:    Simple
+Stability:     experimental
+Cabal-version: >= 1.6
+Description:
+
+    This library implements a caching, asynchronous DNS resolver and
+    provides a command line utility for mass DNS resolution.
+
+Library
+    Build-depends:
+        base >= 4 && <= 5,
+        containers >= 0.3.0.0,
+        contstuff >= 0.7.0,
+        dns >= 0.1.0,
+        iproute >= 1.2.0,
+        time >= 1.1.4
+    GHC-Options: -W
+    Exposed-modules:
+        Network.DnsCache
+
+Executable massdns
+    Build-depends:
+        base >= 4 && <= 5
+    GHC-Options: -W -threaded
+    Main-is: MassDns.hs
