Metadata.hs 15 KB

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