Metadata.hs 14 KB

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