Metadata.hs 15 KB

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