Metadata.hs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 Data.SMTP.Address
  7. import Data.SMTP.Account
  8. import qualified Data.SMTP.Mime as Mime
  9. import Data.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 Data.Attoparsec.ByteString.Char8 (Parser)
  17. import qualified Data.Attoparsec.ByteString.Char8 as A
  18. import Data.Attoparsec.ByteString.Char8.Extras
  19. import qualified Data.ByteString as BS
  20. import qualified Data.ByteString.Char8 as C8
  21. import qualified Data.List as List
  22. -- | All the actions that walrus may request from a backend
  23. data BackendAction =
  24. -- | Recieve new resource
  25. DELIVER |
  26. -- | Verify if a backend will handle the rcpt to addresses
  27. WILLHANDLE |
  28. -- | Verifies if accounts exist as in the Data.SMTP.VRFY command
  29. VERIFY |
  30. -- | Fetch a resource
  31. FETCH |
  32. -- | Delete a resource
  33. DELETE deriving (Show, Read, Eq, Ord, Bounded, Enum)
  34. data FtchQuery = FtchQueryAll deriving (Eq, Ord, Show, Read)
  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. _mftchRecursive :: Bool, _mftchHeaders :: Bool, _mftchQuery :: FtchQuery,
  44. _mftchOffset :: Int, _mftchSize :: Maybe Int
  45. } deriving (Show, Ord, Eq)
  46. -- | All the data of a backend metadata, as the walrus specification.
  47. data Metadata = Metadata {_clientId :: ClientIdentity, _auth :: Maybe ByteString,
  48. _recvDate :: UTCTime, _unrecognized :: [ByteString],
  49. _dataSize :: Int, _actionData :: ActionData} deriving (Show, Ord, Eq)
  50. data ActionData = Deliver DeliverData |
  51. WillHandle HandleData |
  52. Verify HandleData |
  53. FetchResc FetchRescData |
  54. FetchHdr FetchHdrData |
  55. Delete DeliverData deriving (Show, Ord, Eq)
  56. data DeliverData = DeliverData {clientName :: ByteString,
  57. mailFrom :: Account, rcptTo :: [Address], rcptFailed :: [(Address, Response)],
  58. bodyEnc :: Mime.BodyEncoding, smtpUtf8 :: Bool
  59. } deriving (Show, Ord, Eq)
  60. data HandleData = HandleAddress Address |
  61. HandleResponse (Address, Response) |
  62. HandleOk deriving (Show, Ord, Eq)
  63. {- |
  64. Data for fetching resource:
  65. @
  66. FetchRescData clientName account offset size target_or_error
  67. @
  68. -}
  69. data FetchRescData = FetchRescData
  70. -- Client name
  71. ByteString
  72. -- From account
  73. Account
  74. -- Offset
  75. Int
  76. -- Size
  77. Int
  78. -- Target or fetch error
  79. HandleData deriving (Show, Ord, Eq)
  80. {- |
  81. Data for fetching headers:
  82. @
  83. FetchHdrData clientName account onlyHeaders query
  84. @
  85. -}
  86. data FetchHdrData = FetchHdrData
  87. -- Client name
  88. ByteString
  89. -- From account
  90. Account
  91. -- Only headers
  92. Bool
  93. -- Query
  94. FtchQuery
  95. -- Target or fetch error
  96. HandleData deriving (Show, Ord, Eq)
  97. makeLenses ''ClientIdentity
  98. makeLenses ''MaybeMetadata
  99. makeLenses ''Metadata
  100. instance Default MaybeMetadata where
  101. def = MaybeMetadata Nothing Nothing Nothing [] [] Nothing Nothing
  102. Mime.B7BitEncoding False Nothing [] Nothing Nothing Nothing
  103. False False FtchQueryAll 0 Nothing
  104. -- | Creates an empty metadata with just the client identity
  105. metadataForClient :: IP -> Int -> MaybeMetadata
  106. metadataForClient c p = def & mclientId .~ Just (ClientIdentity c p)
  107. -- | Blanks the data as necessary for the RSET Data.SMTP.command
  108. resetMetadata :: MaybeMetadata -> MaybeMetadata
  109. resetMetadata d = def & mclientId .~ d^.mclientId & mclientName .~ d^.mclientName
  110. -- | Converts a fully filled MaybeMetadata into its strict version
  111. strictMetadata :: MaybeMetadata -> Maybe Metadata
  112. strictMetadata m = do
  113. act <- m^.maction
  114. cid <- m^.mclientId
  115. let usr = m^.mauth
  116. rcv <- m^.mrecvDate
  117. let unrq = m^.munrecognized
  118. sz <- m^.mdataSize
  119. let m' = Metadata cid usr rcv unrq sz
  120. headers = m^.mftchHeaders
  121. case act of
  122. DELIVER -> m' <$> Deliver <$> getDeliverData
  123. DELETE -> m' <$> Delete <$> getDeliverData
  124. FETCH -> if headers
  125. then m' <$> FetchHdr <$> getFetchHdr
  126. else m' <$> FetchResc <$> getFetchResc
  127. WILLHANDLE -> m' <$> WillHandle <$> getHandleData
  128. VERIFY -> m' <$> Verify <$> getHandleData
  129. where
  130. getDeliverData :: Maybe DeliverData
  131. getDeliverData = do
  132. cnm <- m^.mclientName
  133. rfm <- m^.mmailFrom
  134. let rto = m^.mrcptTo
  135. rfail = m^.mrcptFailed
  136. enc = m^.mbodyEnc
  137. utf = m^.msmtpUtf8
  138. return $ DeliverData cnm rfm rto rfail enc utf
  139. getHandleData :: Maybe HandleData
  140. getHandleData = let
  141. rto = m^.mrcptTo
  142. rfail = m^.mrcptFailed
  143. in case rto of
  144. (t:_) -> return $ HandleAddress t
  145. [] -> case rfail of
  146. (f:_) -> return $ HandleResponse f
  147. [] -> return $ HandleOk
  148. getFetchResc :: Maybe FetchRescData
  149. getFetchResc = do
  150. cnm <- m^.mclientName
  151. rfm <- m^.mmailFrom
  152. let ofst = m^.mftchOffset
  153. sz <- m^.mftchSize
  154. return $ FetchRescData cnm rfm ofst sz getFetchHandle
  155. getFetchHdr :: Maybe FetchHdrData
  156. getFetchHdr = do
  157. cnm <- m^.mclientName
  158. rfm <- m^.mmailFrom
  159. let r = m^.mftchRecursive
  160. q = m^.mftchQuery
  161. return $ FetchHdrData cnm rfm r q getFetchHandle
  162. getFetchHandle = case m^.mtargetResc of
  163. Just t -> HandleAddress t
  164. Nothing -> case m^.mtargetFailure of
  165. Just f -> HandleResponse f
  166. Nothing -> HandleOk
  167. -- | Converts the metadata to text on the format required by walrus backends.
  168. renderMetadata :: Metadata -> ByteString
  169. renderMetadata m = BS.concat $ serializeDt ++ serializeMain ++ ["\r\n"]
  170. where
  171. serializeMain :: [ByteString]
  172. serializeMain = let
  173. cid = m^.clientId
  174. usr = m^.auth
  175. rcv = m^.recvDate
  176. sz = m^.dataSize
  177. usrStr = case usr of
  178. Nothing -> []
  179. Just u -> ["Auth-User: ", u, "\r\n"]
  180. unrec = m^.unrecognized
  181. h = [
  182. "Client-Ip: ", show $ cid^.clientIp, "\r\n",
  183. "Client-Port: ", show $ cid^.clientPort, "\r\n",
  184. "Recv-Date: ", formatISO8601 rcv, "\r\n",
  185. "Data-Size: ", show sz, "\r\n"
  186. ] :: [String]
  187. in map s h ++ usrStr ++ unrec
  188. serializeDt = case m^.actionData of
  189. Deliver dt -> "Action: DELIVER\r\n" : serializeDeliver dt
  190. Delete dt -> "Action: DELETE\r\n" : serializeDeliver dt
  191. WillHandle dt -> "Action: WILLHANDLE\r\n" : serializeHandle dt
  192. Verify dt -> "Action: VERIFY\r\n" : serializeHandle dt
  193. FetchResc dt -> "Action: FETCH\r\n" : serializeFetchResc dt
  194. FetchHdr dt -> "Action: FETCH\r\n": serializeFetchHdr dt
  195. serializeDeliver d = let
  196. cnm = clientName d
  197. rfm = mailFrom d
  198. rto = rcptTo d
  199. rfail = rcptFailed d
  200. enc = bodyEnc d
  201. utf = smtpUtf8 d
  202. toStr = List.concatMap (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  203. failStr = List.concatMap (\(a, r) -> ["Failed: ", renderMetadataAddress a, "; ", renderLineResponse r, "\r\n"]) rfail
  204. h = [
  205. "Client-Name: ", s cnm, "\r\n",
  206. "Return-Path: ", s . normalize $ rfm, "\r\n",
  207. "Body-Encoding: ", show enc, "\r\n",
  208. "SMTP-UTF8: ", serialBool utf, "\r\n"
  209. ] :: [String]
  210. in map s h ++ toStr ++ failStr
  211. serializeHandle (HandleAddress a) = ["To: ", renderMetadataAddress a, "\r\n"]
  212. serializeHandle (HandleResponse (a, r)) = ["Failed: ", renderMetadataAddress a, "; ", renderLineResponse r, "\r\n"]
  213. serializeHandle HandleOk = []
  214. serializeFetchResc (FetchRescData cnm rfm ofst sz hnd) =
  215. ["Client-Name: ", s cnm, "\r\n",
  216. "Return-Path: ", s . normalize $ rfm, "\r\n",
  217. "Headers: No\r\n",
  218. "Offset: ", s . show $ ofst, "\r\n",
  219. "Block-Size: ", s . show $ sz, "\r\n"] ++
  220. serializeHandle hnd
  221. serializeFetchHdr (FetchHdrData cnm rfm r q hnd) =
  222. ["Client-Name: ", s cnm, "\r\n",
  223. "Return-Path: ", s . normalize $ rfm, "\r\n",
  224. "Headers: Yes\r\n",
  225. "Recursive: ", serialBool r, "\r\n",
  226. "Query: ", serializeFtchQuery q, "\r\n"] ++
  227. serializeHandle hnd
  228. serialBool b = if b then "Yes" else "No"
  229. serializeFtchQuery :: FtchQuery -> ByteString
  230. serializeFtchQuery _ = "()"
  231. parseFtchQuery :: A.Parser FtchQuery
  232. parseFtchQuery = A.string "()" >> return FtchQueryAll
  233. -- | Reads a metadata from a textual representation on the format expected by the walrus backends
  234. parseMetadata :: A.Parser Metadata
  235. parseMetadata = do
  236. (m', h', p') <- parserFold parseField (def, Nothing, Nothing)
  237. A.endOfLine
  238. let i = do
  239. h <- h'
  240. p <- p'
  241. return $ ClientIdentity h p
  242. m = set mclientId i m'
  243. case strictMetadata m of
  244. Just sm -> return sm
  245. Nothing -> fail "missing required fields"
  246. where
  247. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  248. parseField = A.choice [
  249. do
  250. act <- hdr "Action" parseEnumCI
  251. return $ \(m, ip, p) -> (set maction (Just act) m, ip, p),
  252. do
  253. ip <- hdr "Client-Ip" parseRead
  254. return $ \(m, _, p) -> (m, Just ip, p),
  255. do
  256. p <- hdr "Client-Port" parseRead
  257. return $ \(m, ip, _) -> (m, ip, Just p),
  258. do
  259. nm <- hdr "Client-Name" (A.takeTill A.isSpace)
  260. return $ \(m, ip, p) -> (set mclientName (Just nm) m, ip, p),
  261. do
  262. frm <- hdr "Return-Path" parseAccount
  263. return $ \(m, ip, p) -> (set mmailFrom (Just frm) m, ip, p),
  264. do
  265. rtp <- hdr "To" parseAddress
  266. return $ \(m, ip, p) -> let
  267. crtp = m^.mrcptTo
  268. in (set mrcptTo (rtp:crtp) $ set mtargetResc (Just rtp) m, ip, p),
  269. do
  270. rfl <- hdr "Failed" parseAddressingReason
  271. return $ \(m, ip, p) -> let
  272. fld = m^.mrcptFailed
  273. in (set mrcptFailed (rfl:fld) $ set mtargetFailure (Just rfl) m, ip, p),
  274. do
  275. recv <- hdr "Recv-Date" parseISO8601Val
  276. return $ \(m, ip, p) -> (set mrecvDate (Just recv) m, ip, p),
  277. do
  278. enc <- hdr "Body-Encoding" Mime.parseBodyEncoding
  279. return $ \(m, ip, p) -> (set mbodyEnc enc m, ip, p),
  280. do
  281. utf <- hdr "SMTP-UTF8" parseMetadataBool
  282. return $ \(m, ip, p) -> (set msmtpUtf8 utf m, ip, p),
  283. do
  284. usr <- hdr "Auth-User" A.takeByteString
  285. return $ \(m, ip, p) -> (set mauth (Just usr) m, ip, p),
  286. do
  287. sz <- hdr "Data-Size" A.decimal
  288. return $ \(m, ip, p) -> (set mdataSize (Just sz) m, ip, p),
  289. do
  290. off <- hdr "Offset" A.decimal
  291. return $ \(m, ip, p) -> (m &mftchOffset.~off, ip, p),
  292. do
  293. sz <- hdr "Block-Size" A.decimal
  294. return $ \(m, ip, p) -> (m &mftchSize.~Just sz, ip, p),
  295. do
  296. h <- hdr "Headers" parseMetadataBool
  297. return $ \(m, ip, p) -> (m &mftchHeaders.~h, ip, p),
  298. do
  299. r <- hdr "Recursive" parseMetadataBool
  300. return $ \(m, ip, p) -> (m &mftchRecursive.~r, ip, p),
  301. do
  302. q <- hdr "Query" parseFtchQuery
  303. return $ \(m, ip, p) -> (m &mftchQuery.~q, ip, p),
  304. do
  305. u <- entireHdr
  306. return $ \(m, ip, p) -> let
  307. uu = m^.munrecognized
  308. in (set munrecognized (u:uu) m, ip, p)
  309. ]
  310. entireHdr :: Parser ByteString
  311. entireHdr = do
  312. a <- A.satisfy (not . A.isEndOfLine . asW8)
  313. t <- A.takeTill (A.isEndOfLine . asW8)
  314. A.endOfLine
  315. l <- takeLines
  316. return $ BS.concat [C8.cons a t, "\r\n", l]
  317. takeLines :: Parser ByteString
  318. takeLines = do
  319. c' <- A.peekChar
  320. case c' of
  321. Nothing -> return ""
  322. Just c -> if isCHorizontalSpace c
  323. then do
  324. l <- A.takeTill (A.isEndOfLine . asW8)
  325. A.endOfLine
  326. ll <- takeLines
  327. return $ BS.concat [l, "\r\n", ll]
  328. else return ""
  329. hdr :: ByteString -> Parser a -> Parser a
  330. hdr pt f = do
  331. skipHorizontalSpace
  332. A.stringCI pt
  333. skipHorizontalSpace
  334. A.char ':'
  335. skipHorizontalSpace
  336. t <- bsval
  337. r <- case A.parseOnly f t of
  338. Left _ -> fail $ "failed parsing value of " ++ s pt
  339. Right v -> return v
  340. skipHorizontalSpace
  341. return r
  342. bsval :: Parser ByteString
  343. bsval = do
  344. ll <- entireHdr
  345. let (b, _) = BS.spanEnd (\x -> x == asW8 '\r' || x == asW8 '\n') ll
  346. return b
  347. parseRead :: Read a => Parser a
  348. parseRead = do
  349. v <- A.takeTill A.isSpace
  350. case readMaybe . s $ v of
  351. Nothing -> fail "failed parsing value"
  352. Just i -> return i
  353. parseISO8601Val = do
  354. v <- A.takeTill A.isSpace
  355. case parseISO8601 . s $ v of
  356. Nothing -> fail "failed parsing ISO8601 date"
  357. Just t -> return t
  358. parseMetadataBool :: Parser Bool
  359. parseMetadataBool = A.choice [
  360. A.stringCI "YES" *> return True,
  361. A.stringCI "NO" *> return False
  362. ]
  363. parseAddressingReason :: Parser (Address, Response)
  364. parseAddressingReason = do
  365. a <- parseMetadataAddress
  366. skipHorizontalSpace
  367. A.char ';'
  368. skipHorizontalSpace
  369. r <- parseLineResponse
  370. return (a, r)
  371. getDeliver :: (DeliverData -> a) -> Metadata -> Maybe a
  372. getDeliver f Metadata{_actionData=act} = case act of
  373. Deliver dt -> Just $ f dt
  374. Delete dt -> Just $ f dt
  375. _ -> Nothing
  376. getTo :: Metadata -> [Address]
  377. getTo Metadata{_actionData=act} = case act of
  378. Deliver dt -> rcptTo dt
  379. Delete dt -> rcptTo dt
  380. WillHandle dt -> hdl dt
  381. Verify dt -> hdl dt
  382. FetchResc (FetchRescData _ _ _ _ dt) -> hdl dt
  383. FetchHdr (FetchHdrData _ _ _ _ dt) -> hdl dt
  384. where
  385. hdl (HandleAddress a) = [a]
  386. hdl _ = []
  387. getHandle :: Metadata -> Maybe HandleData
  388. getHandle Metadata{_actionData=act} = case act of
  389. WillHandle dt -> Just dt
  390. Verify dt -> Just dt
  391. FetchResc (FetchRescData _ _ _ _ dt) -> Just dt
  392. FetchHdr (FetchHdrData _ _ _ _ dt) -> Just dt
  393. _ -> Nothing