{-# LANGUAGE DeriveDataTypeable #-} {- | Module : Postmaster.FSM.Spooler Copyright : (c) 2004-2008 by Peter Simons License : GPL2 Maintainer : simons@cryp.to Stability : provisional Portability : Haskell 2-pre -} module Postmaster.FSM.Spooler where import Foreign import Control.Exception import Control.Concurrent.MVar import Control.Monad.State import System.Directory import System.IO import Postmaster.Base import Postmaster.IO import Postmaster.FSM.EventHandler import Postmaster.FSM.SessionState import Postmaster.FSM.DataHandler import Postmaster.FSM.MailID import Text.ParserCombinators.Parsec.Rfc2821 hiding ( path ) import OpenSSL.Digest import Data.Typeable data Spooler = S (Maybe FilePath) (Maybe WriteHandle) DigestState deriving (Typeable) spoolerState :: SmtpdVariable spoolerState = defineLocal "spoolerstate" getState :: Smtpd (MVar Spooler) getState = spoolerState getVar_ setState :: MVar Spooler -> Smtpd () setState st = spoolerState (`setVar` st) -- |The Standard Bad-Ass Payload Handler. Needs the path to -- the spool directory. handlePayload :: FilePath -> EventT handlePayload _ f Greeting = do liftIO (newMVar (S Nothing Nothing (DST nullPtr))) >>= setState setDataHandler feeder f Greeting handlePayload spool _ StartData = do st <- getState mid <- getMailID let path = spool ++ "/temp." ++ show mid liftIO . modifyMVar_ st $ \(S p' h' (DST c')) -> assert (p' == Nothing) $ assert (h' == Nothing) $ assert (c' == nullPtr) $ bracketOnError (openBinaryFile path WriteMode) (hClose) (\h -> bracketOnError ctxCreate ctxDestroy $ \ctx -> do hSetBuffering h NoBuffering when (ctx == nullPtr) (fail "can't initialize SHA1 digest context") md <- toMDEngine SHA1 when (md == nullPtr) (fail "can't initialize SHA1 digest engine") rc <- digestInit ctx md when (rc == 0) (fail "can't initialize SHA1 digest") return (S (Just path) (Just h) (DST ctx))) say 3 5 4 "terminate data with ." `fallback` say 4 5 1 "requested action aborted: error in processing" handlePayload spool _ Deliver = do st <- getState sha1 <- liftIO $ modifyMVar st $ \(S (Just p) (Just h) ctx@(DST c)) -> assert (c /= nullPtr) $ do hClose h sha1 <- fmap (>>= toHex) (evalStateT final ctx) let fname = spool ++ "/" ++ sha1 renameFile p fname return (S Nothing Nothing ctx, sha1) say 2 5 0 (sha1 ++ " message accepted for delivery") `fallback` say 4 5 1 "requested action aborted: error in processing" handlePayload _ f ResetState = clearState >> f ResetState handlePayload _ f Shutdown = clearState >> f Shutdown handlePayload _ f e = f e feeder :: DataHandler feeder buf@(Buf _ _ 0) = return (Nothing, buf) feeder buf@(Buf _ ptr n) = do xs <- liftIO (peekArray (fromIntegral n) ptr) let theEnd = map (toEnum . fromEnum) "\r\n.\r\n" (eod, i) = case strstr theEnd xs of Nothing -> (False, max 0 (n - 4)) Just j -> (True, fromIntegral (j-3)) i' = fromIntegral i st <- getState buf' <- liftIO . withMVar st $ \(S _ (Just h) ctx@(DST c)) -> assert (c /= nullPtr) $ do hPutBuf h ptr i' _ <- execStateT (update' (ptr, i')) ctx flush i buf if not eod then return (Nothing, buf') else do r <- trigger Deliver _ <- trigger ResetState -- TODO: this doesn't really setSessionState HaveHelo -- belong here return (Just r, buf') clearState :: Smtpd () clearState = do st <- getState liftIO . modifyMVar_ st $ \(S path h (DST ctx)) -> do let clean Nothing _ = return () clean (Just x) f = (try (f x) :: IO (Either SomeException ())) >> return () clean h hClose clean path removeFile when (ctx /= nullPtr) (ctxDestroy ctx) return (S Nothing Nothing (DST nullPtr))