domain-auth-0.2.4: Network/DomainAuth/PRD/PRD.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Purported Responsible Domain, RFC 4407.
module Network.DomainAuth.PRD.PRD (
PRD,
initialPRD,
pushPRD,
decidePRD,
decideFrom,
) where
import Control.Monad
import qualified Data.ByteString.Char8 as BS
import Data.Char
import Data.List (foldl')
import Network.DNS (Domain)
import Network.DomainAuth.Mail
import Network.DomainAuth.PRD.Domain
----------------------------------------------------------------
type HD = [(CanonFieldKey, RawFieldValue)]
data DST = DST_Zero | DST_Invalid | DST_Valid Domain deriving (Eq, Show)
-- | Abstract type for context to decide PRD(purported responsible domain)
-- according to RFC 4407.
data PRD = PRD
{ praFrom :: DST
, praSender :: DST
, praResentFrom :: DST
, praResentSender :: DST
, praHeader :: HD
}
deriving (Show)
-- | Initial context of PRD.
initialPRD :: PRD
initialPRD =
PRD
{ praFrom = DST_Zero
, praSender = DST_Zero
, praResentFrom = DST_Zero
, praResentSender = DST_Zero
, praHeader = []
}
----------------------------------------------------------------
-- | Pushing a field key and its value in to the PRD context.
pushPRD :: RawFieldKey -> RawFieldValue -> PRD -> PRD
pushPRD key val ctx = case ckey of
"from" -> pushFrom ctx' jdom
"sender" -> pushSender ctx' jdom
"resent-from" -> pushResentFrom ctx' jdom
"resent-sender" -> pushResentSender ctx' jdom
_ -> ctx'
where
ckey = BS.map toLower key
jdom = extractDomain val
ctx' = ctx{praHeader = (ckey, val) : praHeader ctx}
-- | Deciding PRD from the RPD context.
--
-- >>> let maddr1 = "alice@alice.example.jp"
-- >>> let maddr2 = "bob@bob.example.jp"
-- >>> let maddr3 = "chris@chris.example.jp"
-- >>> let maddr4 = "dave@dave.example.jp"
-- >>> decidePRD (pushPRD "from" "alice@alice.example.jp" initialPRD)
-- Just "alice.example.jp"
-- >>> :{
-- decidePRD (pushPRD "from" maddr1
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Nothing
--
-- >>> :{
-- decidePRD (pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "bob.example.jp"
--
-- >>> :{
-- decidePRD (pushPRD "sender" maddr2
-- $ pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Nothing
--
-- >>> :{
-- decidePRD (pushPRD "resent-from" maddr3
-- $ pushPRD "sender" maddr2
-- $ pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "chris.example.jp"
--
-- >>> :{
-- decidePRD (pushPRD "resent-sender" maddr4
-- $ pushPRD "resent-from" maddr3
-- $ pushPRD "sender" maddr2
-- $ pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "dave.example.jp"
--
-- >>> :{
-- decidePRD (pushPRD "resent-sender" maddr4
-- $ pushPRD "resent-from" maddr3
-- $ pushPRD "sender" maddr2
-- $ pushPRD "received" "dummy"
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "dave.example.jp"
--
-- >>> :{
-- decidePRD (pushPRD "resent-sender" maddr4
-- $ pushPRD "received" "dummy"
-- $ pushPRD "resent-from" maddr3
-- $ pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "chris.example.jp"
--
-- >>> :{
-- decidePRD (pushPRD "received" "dummy"
-- $ pushPRD "resent-sender" maddr4
-- $ pushPRD "resent-from" maddr3
-- $ pushPRD "sender" maddr2
-- $ pushPRD "from" maddr1 initialPRD)
-- :}
-- Just "dave.example.jp"
decidePRD :: PRD -> Maybe Domain
decidePRD ctx =
let jds =
[ praResentSender ctx
, praResentFrom ctx
, praSender ctx
, praFrom ctx
]
in foldl' mplus mzero $ map toMaybe jds
-- | Taking the value of From: from the RPD context.
--
-- >>> decideFrom (pushPRD "from" "alice@alice.example.jp" initialPRD)
-- Just "alice.example.jp"
decideFrom :: PRD -> Maybe Domain
decideFrom = toMaybe . praFrom
toMaybe :: DST -> Maybe Domain
toMaybe (DST_Valid d) = Just d
toMaybe _ = Nothing
----------------------------------------------------------------
pushFrom :: PRD -> Maybe Domain -> PRD
pushFrom ctx Nothing = ctx{praFrom = DST_Invalid}
pushFrom ctx (Just dom) = ctx{praFrom = from}
where
from = case praFrom ctx of
DST_Zero -> DST_Valid dom
_ -> DST_Invalid
pushSender :: PRD -> Maybe Domain -> PRD
pushSender ctx Nothing = ctx{praSender = DST_Invalid}
pushSender ctx (Just dom) = ctx{praSender = sender}
where
sender = case praSender ctx of
DST_Zero -> DST_Valid dom
_ -> DST_Invalid
pushResentFrom :: PRD -> Maybe Domain -> PRD
pushResentFrom ctx Nothing = ctx{praResentFrom = DST_Invalid}
pushResentFrom ctx (Just dom) = ctx{praResentFrom = rfrom}
where
rfrom = case praResentFrom ctx of
DST_Zero -> DST_Valid dom
DST_Valid d -> DST_Valid d
DST_Invalid -> DST_Invalid
pushResentSender :: PRD -> Maybe Domain -> PRD
pushResentSender ctx Nothing = ctx{praResentSender = DST_Invalid}
pushResentSender ctx (Just dom)
| praResentFrom ctx == DST_Zero = ctx{praResentSender = rsender}
| isFirstBlock (praHeader ctx) = ctx{praResentSender = DST_Valid dom}
| otherwise = ctx{praResentSender = DST_Invalid}
where
rsender = case praResentSender ctx of
DST_Zero -> DST_Valid dom
DST_Valid d -> DST_Valid d
DST_Invalid -> DST_Invalid
isFirstBlock :: HD -> Bool
isFirstBlock hdr = all rr . takeWhile end $ hdr
where
end = (/= "resent-from") . fst
rr = (`notElem` ["received", "return-path"]) . fst