Metadata.hs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. {-# LANGUAGE OverloadedStrings #-}
  2. {-# LANGUAGE TemplateHaskell #-}
  3. module Walrus.Backend.Metadata where
  4. import Data.ByteString (ByteString)
  5. import Data.Time.Clock (UTCTime)
  6. import SMTP.Address
  7. import SMTP.Account
  8. import qualified SMTP.Mime as Mime
  9. import SMTP.Response
  10. import Text.StringConvert
  11. import Data.Time.ISO8601
  12. import Data.IP
  13. import Data.Default.Class
  14. import Control.Lens
  15. import Text.Read (readMaybe)
  16. import Debug.Trace
  17. import Data.Attoparsec.ByteString.Char8 (Parser)
  18. import qualified Data.Attoparsec.ByteString.Char8 as A
  19. import Data.Attoparsec.ByteString.Char8.Extras
  20. import qualified Data.ByteString as BS
  21. import qualified Data.ByteString.Char8 as C8
  22. import qualified Data.List as List
  23. -- | All the actions that walrus may request from a backend
  24. data BackendAction =
  25. -- | Recieve new resource
  26. DELIVER |
  27. -- | Verify if a backend will handle the rcpt to addresses
  28. WILLHANDLE |
  29. -- | Verifies if accounts exist as in the SMTP VRFY command
  30. VERIFY |
  31. -- | Fetch a resource
  32. FETCH |
  33. -- | Delete a resource
  34. DELETE deriving (Show, Read, Eq, Ord, Bounded, Enum)
  35. -- | The network data of a client (IP and port)
  36. data ClientIdentity = ClientIdentity {_clientIp :: IP, _clientPort :: Int} deriving (Show, Read, Ord, Eq)
  37. -- | A possibly empty version of Metadata for iterative filling. Convert with strictMetadata.
  38. data MaybeMetadata = MaybeMetadata {_mclientId :: Maybe ClientIdentity, _mclientName :: Maybe ByteString,
  39. _mmailFrom :: Maybe Account, _mrcptTo :: [Address], _mrcptFailed :: [(Address, Response)],
  40. _mauth :: Maybe ByteString, _mrecvDate :: Maybe UTCTime, _mbodyEnc :: Mime.BodyEncoding,
  41. _msmtpUtf8 :: Bool, _maction :: Maybe BackendAction, _munrecognized :: [ByteString],
  42. _mdataSize :: Maybe Int, _mtargetResc :: Maybe Address, _mtargetFailure :: Maybe (Address, Response)
  43. } deriving (Show, Ord, Eq)
  44. -- | All the data of a backend metadata, as the walrus specification.
  45. data Metadata = Metadata {_clientId :: ClientIdentity, _auth :: Maybe ByteString,
  46. _recvDate :: UTCTime, _unrecognized :: [ByteString],
  47. _dataSize :: Int, _actionData :: ActionData} deriving (Show, Ord, Eq)
  48. data ActionData = Deliver DeliverData |
  49. WillHandle HandleData |
  50. Verify HandleData |
  51. Fetch FetchData |
  52. Delete DeliverData deriving (Show, Ord, Eq)
  53. data DeliverData = DeliverData {clientName :: ByteString,
  54. mailFrom :: Account, rcptTo :: [Address], rcptFailed :: [(Address, Response)],
  55. bodyEnc :: Mime.BodyEncoding, smtpUtf8 :: Bool
  56. } deriving (Show, Ord, Eq)
  57. data HandleData = HandleAddress Address |
  58. HandleResponse (Address, Response) |
  59. HandleOk deriving (Show, Ord, Eq)
  60. data FetchData = FetchData
  61. -- | Client name
  62. ByteString
  63. -- | From account
  64. Account
  65. -- | Target or fetch error
  66. HandleData deriving (Show, Ord, Eq)
  67. makeLenses ''ClientIdentity
  68. makeLenses ''MaybeMetadata
  69. makeLenses ''Metadata
  70. instance Default MaybeMetadata where
  71. def = MaybeMetadata Nothing Nothing Nothing [] [] Nothing Nothing Mime.B7BitEncoding False Nothing [] Nothing Nothing Nothing
  72. -- | Creates an empty metadata with just the client identity
  73. metadataForClient :: IP -> Int -> MaybeMetadata
  74. metadataForClient c p = def & mclientId .~ Just (ClientIdentity c p)
  75. -- | Blanks the data as necessary for the RSET SMTP command
  76. resetMetadata :: MaybeMetadata -> MaybeMetadata
  77. resetMetadata d = def & mclientId .~ d^.mclientId & mclientName .~ d^.mclientName
  78. -- | Converts a fully filled MaybeMetadata into its strict version
  79. strictMetadata :: MaybeMetadata -> Maybe Metadata
  80. strictMetadata m = do
  81. act <- m^.maction
  82. cid <- m^.mclientId
  83. let usr = m^.mauth
  84. rcv <- m^.mrecvDate
  85. let unrq = m^.munrecognized
  86. sz <- m^.mdataSize
  87. let m' = Metadata cid usr rcv unrq sz
  88. case act of
  89. DELIVER -> m' <$> Deliver <$> getDeliverData
  90. DELETE -> m' <$> Delete <$> getDeliverData
  91. FETCH -> m' <$> Fetch <$> getFetchData
  92. WILLHANDLE -> m' <$> WillHandle <$> getHandleData
  93. VERIFY -> m' <$> Verify <$> getHandleData
  94. where
  95. getDeliverData :: Maybe DeliverData
  96. getDeliverData = do
  97. cnm <- m^.mclientName
  98. rfm <- m^.mmailFrom
  99. let rto = m^.mrcptTo
  100. rfail = m^.mrcptFailed
  101. enc = m^.mbodyEnc
  102. utf = m^.msmtpUtf8
  103. return $ DeliverData cnm rfm rto rfail enc utf
  104. getHandleData :: Maybe HandleData
  105. getHandleData = let
  106. rto = m^.mrcptTo
  107. rfail = m^.mrcptFailed
  108. in case rto of
  109. (t:_) -> return $ HandleAddress t
  110. [] -> case rfail of
  111. (f:_) -> return $ HandleResponse f
  112. [] -> return $ HandleOk
  113. getFetchData :: Maybe FetchData
  114. getFetchData = do
  115. cnm <- m^.mclientName
  116. rfm <- m^.mmailFrom
  117. let m' = FetchData cnm rfm
  118. case m^.mtargetResc of
  119. Just t -> return . m' $ HandleAddress t
  120. Nothing -> case m^.mtargetFailure of
  121. Just f -> return . m' $ HandleResponse f
  122. Nothing -> return . m' $ HandleOk
  123. -- | Converts the metadata to text on the format required by walrus backends.
  124. renderMetadata :: Metadata -> ByteString
  125. renderMetadata m = BS.concat $ serializeDt ++ serializeMain ++ ["\r\n"]
  126. where
  127. serializeMain :: [ByteString]
  128. serializeMain = let
  129. cid = m^.clientId
  130. usr = m^.auth
  131. rcv = m^.recvDate
  132. sz = m^.dataSize
  133. usrStr = case usr of
  134. Nothing -> []
  135. Just u -> ["Auth-User: ", u, "\r\n"]
  136. unrec = m^.unrecognized
  137. h = [
  138. "Client-Ip: ", show $ cid^.clientIp, "\r\n",
  139. "Client-Port: ", show $ cid^.clientPort, "\r\n",
  140. "Recv-Date: ", formatISO8601 rcv, "\r\n",
  141. "Data-Size: ", show sz, "\r\n"
  142. ] :: [String]
  143. in map s h ++ usrStr ++ unrec
  144. serializeDt = case m^.actionData of
  145. Deliver dt -> "Action: DELIVER\r\n" : serializeDeliver dt
  146. Delete dt -> "Action: DELETE\r\n" : serializeDeliver dt
  147. WillHandle dt -> "Action: WILLHANDLE\r\n" : serializeHandle dt
  148. Verify dt -> "Action: VERIFY\r\n" : serializeHandle dt
  149. Fetch dt -> "Action: FETCH\r\n" : serializeFetch dt
  150. serializeDeliver d = let
  151. cnm = clientName d
  152. rfm = mailFrom d
  153. rto = rcptTo d
  154. rfail = rcptFailed d
  155. enc = bodyEnc d
  156. utf = smtpUtf8 d
  157. toStr = List.concatMap (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  158. failStr = List.concatMap (\(a, r) -> ["Failed: ", renderMetadataAddress a, "; ", renderLineResponse r, "\r\n"]) rfail
  159. h = [
  160. "Client-Name: ", s cnm, "\r\n",
  161. "Return-Path: ", s . normalAccountName $ rfm, "\r\n",
  162. "Body-Encoding: ", show enc, "\r\n",
  163. "SMTP-UTF8: ", if utf then "Yes" else "No", "\r\n"
  164. ] :: [String]
  165. in map s h ++ toStr ++ failStr
  166. serializeHandle (HandleAddress a) = ["To: ", renderMetadataAddress a, "\r\n"]
  167. serializeHandle (HandleResponse (a, r)) = ["Failed: ", renderMetadataAddress a, "; ", renderLineResponse r, "\r\n"]
  168. serializeHandle HandleOk = []
  169. serializeFetch (FetchData cnm rfm hnd) =
  170. ["Client-Name: ", s cnm, "\r\n",
  171. "Return-Path: ", s . normalAccountName $ rfm, "\r\n"] ++
  172. serializeHandle hnd
  173. -- | Reads a metadata from a textual representation on the format expected by the walrus backends
  174. parseMetadata :: A.Parser Metadata
  175. parseMetadata = do
  176. (m', h', p') <- parserFold parseField (def, Nothing, Nothing)
  177. A.endOfLine
  178. let i = do
  179. h <- h'
  180. p <- p'
  181. return $ ClientIdentity h p
  182. m = set mclientId i m'
  183. case strictMetadata m of
  184. Just sm -> return sm
  185. Nothing -> fail "missing required fields"
  186. where
  187. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  188. parseField = A.choice [
  189. do
  190. act <- hdr "Action" parseEnumCI
  191. return $ \(m, ip, p) -> (set maction (Just act) m, ip, p),
  192. do
  193. ip <- hdr "Client-Ip" parseRead
  194. return $ \(m, _, p) -> (m, Just ip, p),
  195. do
  196. p <- hdr "Client-Port" parseRead
  197. return $ \(m, ip, _) -> (m, ip, Just p),
  198. do
  199. nm <- hdr "Client-Name" (A.takeTill A.isSpace)
  200. return $ \(m, ip, p) -> (set mclientName (Just nm) m, ip, p),
  201. do
  202. frm <- hdr "Return-Path" parseAccount
  203. return $ \(m, ip, p) -> (set mmailFrom (Just frm) m, ip, p),
  204. do
  205. rtp <- hdr "To" parseAddress
  206. return $ \(m, ip, p) -> let
  207. crtp = m^.mrcptTo
  208. in (set mrcptTo (rtp:crtp) $ set mtargetResc (Just rtp) m, ip, p),
  209. do
  210. rfl <- hdr "Failed" parseAddressingReason
  211. return $ \(m, ip, p) -> let
  212. fld = m^.mrcptFailed
  213. in (set mrcptFailed (rfl:fld) $ set mtargetFailure (Just rfl) m, ip, p),
  214. do
  215. recv <- hdr "Recv-Date" parseISO8601Val
  216. return $ \(m, ip, p) -> (set mrecvDate (Just recv) m, ip, p),
  217. do
  218. enc <- hdr "Body-Encoding" Mime.parseBodyEncoding
  219. return $ \(m, ip, p) -> (set mbodyEnc enc m, ip, p),
  220. do
  221. utf <- hdr "SMTP-UTF8" parseMetadataBool
  222. return $ \(m, ip, p) -> (set msmtpUtf8 utf m, ip, p),
  223. do
  224. usr <- hdr "Auth-User" A.takeByteString
  225. return $ \(m, ip, p) -> (set mauth (Just usr) m, ip, p),
  226. do
  227. sz <- hdr "Data-Size" A.decimal
  228. return $ \(m, ip, p) -> (set mdataSize (Just sz) m, ip, p),
  229. do
  230. u <- entireHdr
  231. return $ \(m, ip, p) -> let
  232. uu = m^.munrecognized
  233. in (set munrecognized (u:uu) m, ip, p)
  234. ]
  235. entireHdr :: Parser ByteString
  236. entireHdr = do
  237. a <- A.satisfy (not . A.isEndOfLine . asW8)
  238. t <- A.takeTill (A.isEndOfLine . asW8)
  239. A.endOfLine
  240. l <- takeLines
  241. return $ BS.concat [C8.cons a t, "\r\n", l]
  242. takeLines :: Parser ByteString
  243. takeLines = do
  244. c' <- A.peekChar
  245. case c' of
  246. Nothing -> return ""
  247. Just c -> if isCHorizontalSpace c
  248. then do
  249. l <- A.takeTill (A.isEndOfLine . asW8)
  250. A.endOfLine
  251. ll <- takeLines
  252. return $ BS.concat [l, "\r\n", ll]
  253. else return ""
  254. hdr :: ByteString -> Parser a -> Parser a
  255. hdr pt f = do
  256. skipHorizontalSpace
  257. A.stringCI pt
  258. skipHorizontalSpace
  259. A.char ':'
  260. skipHorizontalSpace
  261. t <- bsval
  262. r <- case A.parseOnly f t of
  263. Left _ -> fail $ "failed parsing value of " ++ s pt
  264. Right v -> return v
  265. skipHorizontalSpace
  266. return r
  267. bsval :: Parser ByteString
  268. bsval = do
  269. ll <- entireHdr
  270. let (b, _) = BS.spanEnd (\x -> x == asW8 '\r' || x == asW8 '\n') ll
  271. return b
  272. parseRead :: Read a => Parser a
  273. parseRead = do
  274. v <- A.takeTill A.isSpace
  275. case readMaybe . s $ v of
  276. Nothing -> fail "failed parsing value"
  277. Just i -> return i
  278. parseISO8601Val = do
  279. v <- A.takeTill A.isSpace
  280. case parseISO8601 . s $ v of
  281. Nothing -> fail "failed parsing ISO8601 date"
  282. Just t -> return t
  283. parseMetadataBool :: Parser Bool
  284. parseMetadataBool = A.choice [
  285. A.stringCI "YES" *> return True,
  286. A.stringCI "NO" *> return False
  287. ]
  288. parseAddressingReason :: Parser (Address, Response)
  289. parseAddressingReason = do
  290. a <- parseMetadataAddress
  291. skipHorizontalSpace
  292. A.char ';'
  293. skipHorizontalSpace
  294. r <- parseLineResponse
  295. return (a, r)
  296. getDeliver :: (DeliverData -> a) -> Metadata -> Maybe a
  297. getDeliver f Metadata{_actionData=act} = case act of
  298. Deliver dt -> Just $ f dt
  299. Delete dt -> Just $ f dt
  300. _ -> Nothing
  301. getTo :: Metadata -> [Address]
  302. getTo Metadata{_actionData=act} = case act of
  303. Deliver dt -> rcptTo dt
  304. Delete dt -> rcptTo dt
  305. WillHandle dt -> hdl dt
  306. Verify dt -> hdl dt
  307. Fetch (FetchData _ _ dt) -> hdl dt
  308. where
  309. hdl (HandleAddress a) = [a]
  310. hdl _ = []
  311. getHandle :: Metadata -> Maybe HandleData
  312. getHandle Metadata{_actionData=act} = case act of
  313. WillHandle dt -> Just dt
  314. Verify dt -> Just dt
  315. Fetch (FetchData _ _ dt) -> Just dt
  316. _ -> Nothing