{-# LANGUAGE OverloadedStrings #-} module Milter.Switch (milter) where import Control.Applicative import Control.Exception import Control.Monad (unless) import Data.ByteString.Char8 (ByteString) import Data.IORef import MailSpec import Milter.Base import Milter.Env import Milter.Log import Milter.Types import Network.DomainAuth import RPF import System.IO ---------------------------------------------------------------- milter :: Env -> Handle -> IORef State -> IO () milter env hdl ref = withValidHandleDo $ handle errorHandle $ do rpkt <- getPacket hdl switch env hdl ref rpkt milter env hdl ref where errorHandle (SomeException e) = logDebug env ref $ show e withValidHandleDo blk = do closed <- hIsClosed hdl eof <- hIsEOF hdl unless (eof || closed) blk switch :: Env -> Handle -> IORef State -> Packet -> IO () switch env hdl ref (Packet 'O' bs) = open env hdl ref bs switch env hdl ref (Packet 'C' bs) = conn env hdl ref bs switch env hdl ref (Packet 'H' bs) = helo env hdl ref bs switch env hdl ref (Packet 'M' bs) = mfro env hdl ref bs switch _ hdl _ (Packet 'R' _ ) = continue hdl switch _ hdl _ (Packet 'A' _ ) = continue hdl -- xxx switch env hdl ref (Packet 'L' bs) = hedr env hdl ref bs switch env hdl ref (Packet 'N' bs) = eohd env hdl ref bs switch env hdl ref (Packet 'B' bs) = body env hdl ref bs switch env hdl ref (Packet 'E' bs) = eoms env hdl ref bs switch _ _ _ (Packet 'D' _) = return () switch _ _ _ (Packet 'Q' _) = return () switch env _ ref (Packet x _) = logError env ref $ "Switch: " ++ [x] ---------------------------------------------------------------- mfilter :: Env -> Handle -> IORef State -> MailSpec -> BlockName -> IO () mfilter env hdl ref ms bn = let ply = policy env in case evalRPF ms ply bn of (l,A_Accept) -> doit accept "Accepted" l (l,A_Discard) -> doit discard "Discarded" l (l,A_Hold) -> doit hold "Held" l (l,A_Reject) -> doit reject "Rejected" l (_,A_Continue) -> if bn == B_Body then logResult env ref "Accepted by default" >> continue hdl else logMonitor env ref "continue" >> continue hdl where doit act m l = do logResult env ref $ m ++ " in line " ++ show l if logonly env then accept hdl else act hdl ---------------------------------------------------------------- type Filter = Env -> Handle -> IORef State -> ByteString -> IO () ---------------------------------------------------------------- open :: Filter open env hdl ref _ = do logMonitor env ref "Milter opened" negotiate hdl ---------------------------------------------------------------- conn :: Filter conn env hdl ref bs = do st <- readIORef ref let ip = getIP bs ms = (mailspec st) { msPeerIP = ip } writeIORef ref st { mailspec = ms } -- after IP set for logging logResult env ref "SMTP connected" logDebug env ref $ '\t' : show ms mfilter env hdl ref ms B_Connect ---------------------------------------------------------------- helo :: Filter helo env hdl ref _ = do logMonitor env ref "HELO" continue hdl ---------------------------------------------------------------- mfro :: Filter mfro env hdl ref bs = do logMonitor env ref "MAIL FROM" let jmailfrom = extractDomain bs case jmailfrom of Nothing -> continue hdl -- xxx Just dom -> do st <- readIORef ref xspf <- getSPF dom st let ms = (mailspec st) { msSPFResult = xspf, msMailFrom = jmailfrom } writeIORef ref st { mailspec = ms } logDebug env ref $ '\t' : show ms mfilter env hdl ref ms B_MailFrom where getSPF dom st = do let ip = msPeerIP (mailspec st) spf env dom ip ---------------------------------------------------------------- hedr :: Filter hedr env hdl ref bs = do logMonitor env ref "DATA HEADER FIELD" st <- readIORef ref let (key,val) = getKeyVal bs ckey = canonicalizeKey key xm = pushField key val (xmail st) prd = pushPRD key val (prdspec st) (pv,ms) = checkField ckey val (parsedv st) (mailspec st) writeIORef ref $ st { xmail = xm, mailspec = ms, prdspec = prd, parsedv = pv} logDebug env ref $ "\t" ++ show xm ++ "\n\t" ++ show ms ++ "\n\t" ++ show prd continue hdl where checkField ckey val pv ms | ckey == dkFieldKey = case parseDK val of Nothing -> (pv, ms) x@(Just pdk) -> (pv { mpdk = x}, ms { msSigDK = True , msDKFrom = Just (dkDomain pdk) }) | ckey == dkimFieldKey = case parseDKIM val of Nothing -> (pv, ms) x@(Just pdkim) -> (pv { mpdkim = x}, ms { msSigDKIM = True , msDKIMFrom = Just (dkimDomain pdkim) }) | otherwise = (pv, ms) ---------------------------------------------------------------- eohd :: Filter eohd env hdl ref _ = do logMonitor env ref "DATA HEADER END" st <- readIORef ref let jfrom = getFrom st jprd = decidePRD (prdspec st) sid <- getSenderID jprd let ms = (mailspec st) { msSenderIDResult = sid, msFrom = jfrom, msPRA = jprd } writeIORef ref st { mailspec = ms } mfilter env hdl ref ms B_Header where getFrom = decideFrom . prdspec getSenderID jprd = case jprd of Nothing -> return DAPermError Just dom -> do ms <- mailspec <$> readIORef ref if jprd == msMailFrom ms then return $ msSPFResult ms else let ip = msPeerIP ms in spf env dom ip ---------------------------------------------------------------- body :: Filter body env hdl ref bs = do logMonitor env ref "DATA BODY" st <- readIORef ref let bc = getBody bs xm = pushBody bc (xmail st) writeIORef ref st { xmail = xm } continue hdl ---------------------------------------------------------------- eoms :: Filter eoms env hdl ref _ = do logMonitor env ref "DATA BODY END" st <- readIORef ref let mail = finalizeMail (xmail st) mdk = mpdk (parsedv st) mdkim = mpdkim (parsedv st) xdk <- maybe (return DANone) (dk env mail) mdk xdkim <- maybe (return DANone) (dkim env mail) mdkim let ms = (mailspec st) { msDKResult = xdk, msDKIMResult = xdkim } mfilter env hdl ref ms B_Body