packages feed

rpf-0.2.4: Milter/Switch.hs

{-# LANGUAGE OverloadedStrings #-}

module Milter.Switch (milter) where

import Control.Applicative
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 $
    flip catch errorHandle $ do
      rpkt <- getPacket hdl
      switch env hdl ref rpkt
      milter env hdl ref
  where
    errorHandle e = logDebug env ref $ show e
    withValidHandleDo block = do
        closed <- hIsClosed hdl
        eof <- hIsEOF hdl
        unless (eof || closed) block

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