Metadata.hs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 email
  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 deriving (Show, Read, Eq, Ord, Bounded, Enum)
  30. -- | The network data of a client (IP and port)
  31. data ClientIdentity = ClientIdentity {_clientIp :: IP, _clientPort :: Int} deriving (Show, Read, Ord, Eq)
  32. -- | A possibly empty version of Metadata for iterative filling. Convert with strictMetadata.
  33. data MaybeMetadata = MaybeMetadata {_mclientId :: Maybe ClientIdentity, _mclientName :: Maybe ByteString,
  34. _mmailFrom :: Maybe Account, _mrcptTo :: [Address], _mrcptFailed :: [(Address, Response)],
  35. _mauth :: Maybe ByteString, _mrecvDate :: Maybe UTCTime, _mbodyEnc :: Mime.BodyEncoding,
  36. _msmtpUtf8 :: Bool, _maction :: Maybe BackendAction, _munrecognized :: [ByteString],
  37. _mdataSize :: Maybe Int
  38. } deriving (Show, Ord, Eq)
  39. -- | All the data of a backend metadata, as the walrus specification.
  40. data Metadata = Metadata {_clientId :: ClientIdentity, _clientName :: ByteString,
  41. _mailFrom :: Account, _rcptTo :: [Address], _rcptFailed :: [(Address, Response)],
  42. _auth :: Maybe ByteString, _recvDate :: UTCTime, _bodyEnc :: Mime.BodyEncoding,
  43. _smtpUtf8 :: Bool, _action :: BackendAction, _unrecognized :: [ByteString],
  44. _dataSize :: Int
  45. } deriving (Show, Ord, Eq)
  46. makeLenses ''ClientIdentity
  47. makeLenses ''MaybeMetadata
  48. makeLenses ''Metadata
  49. instance Default MaybeMetadata where
  50. def = MaybeMetadata Nothing Nothing Nothing [] [] Nothing Nothing Mime.B7BitEncoding False Nothing [] Nothing
  51. -- | Creates an empty metadata with just the client identity
  52. metadataForClient :: IP -> Int -> MaybeMetadata
  53. metadataForClient c p = def & mclientId .~ Just (ClientIdentity c p)
  54. -- | Blanks the data as necessary for the RSET SMTP command
  55. resetMetadata :: MaybeMetadata -> MaybeMetadata
  56. resetMetadata d = def & mclientId .~ d^.mclientId & mclientName .~ d^.mclientName
  57. -- | Converts a fully filled MaybeMetadata into its strict version
  58. strictMetadata :: MaybeMetadata -> Maybe Metadata
  59. strictMetadata m = do
  60. act <- m^.maction
  61. cid <- m^.mclientId
  62. cnm <- m^.mclientName
  63. rfm <- m^.mmailFrom
  64. let rto = m^.mrcptTo
  65. rfail = m^.mrcptFailed
  66. usr = m^.mauth
  67. rcv <- m^.mrecvDate
  68. let enc = m^.mbodyEnc
  69. utf = m^.msmtpUtf8
  70. unrq = m^.munrecognized
  71. sz <- m^.mdataSize
  72. return $ Metadata cid cnm rfm rto rfail usr rcv enc utf act unrq sz
  73. -- | Converts the metadata to text on the format required by walrus backends.
  74. renderMetadata :: Metadata -> ByteString
  75. renderMetadata m = BS.concat serialize
  76. where
  77. serialize :: [ByteString]
  78. serialize = let
  79. act = m^.action
  80. cid = m^.clientId
  81. cnm = m^.clientName
  82. rfm = m^.mailFrom
  83. rto = m^.rcptTo
  84. rfail = m^.rcptFailed
  85. usr = m^.auth
  86. rcv = m^.recvDate
  87. enc = m^.bodyEnc
  88. utf = m^.smtpUtf8
  89. sz = m^.dataSize
  90. usrStr = case usr of
  91. Nothing -> []
  92. Just u -> ["Auth-User: ", u, "\r\n"]
  93. toStr = List.concatMap (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  94. failStr = List.concatMap (\(a, r) -> ["Failed: ", renderMetadataAddress a, "; ", renderLineResponse r, "\r\n"]) rfail
  95. unrec = m^.unrecognized
  96. h = [
  97. "Action: ", show act, "\r\n",
  98. "Client-Ip: ", show $ cid^.clientIp, "\r\n",
  99. "Client-Port: ", show $ cid^.clientPort, "\r\n",
  100. "Client-Name: ", s cnm, "\r\n",
  101. "Return-Path: ", s . normalAccountName $ rfm, "\r\n",
  102. "Recv-Date: ", formatISO8601 rcv, "\r\n",
  103. "Body-Encoding: ", show enc, "\r\n",
  104. "SMTP-UTF8: ", if utf then "Yes" else "No", "\r\n",
  105. "Data-Size: ", show sz, "\r\n"
  106. ] :: [String]
  107. in map s h ++ toStr ++ failStr ++ usrStr ++ unrec ++ ["\r\n"]
  108. -- | Reads a metadata from a textual representation on the format expected by the walrus backends
  109. parseMetadata :: A.Parser Metadata
  110. parseMetadata = do
  111. (m', h', p') <- parserFold parseField (def, Nothing, Nothing)
  112. A.endOfLine
  113. let i = do
  114. h <- h'
  115. p <- p'
  116. return $ ClientIdentity h p
  117. m = set mclientId i m'
  118. case strictMetadata m of
  119. Just sm -> return sm
  120. Nothing -> fail "missing required fields"
  121. where
  122. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  123. parseField = A.choice [
  124. do
  125. act <- hdr "Action" parseEnumCI
  126. return $ \(m, ip, p) -> (set maction (Just act) m, ip, p),
  127. do
  128. ip <- hdr "Client-Ip" parseRead
  129. return $ \(m, _, p) -> (m, Just ip, p),
  130. do
  131. p <- hdr "Client-Port" parseRead
  132. return $ \(m, ip, _) -> (m, ip, Just p),
  133. do
  134. nm <- hdr "Client-Name" (A.takeTill A.isSpace)
  135. return $ \(m, ip, p) -> (set mclientName (Just nm) m, ip, p),
  136. do
  137. frm <- hdr "Return-Path" parseAccount
  138. return $ \(m, ip, p) -> (set mmailFrom (Just frm) m, ip, p),
  139. do
  140. rtp <- hdr "To" parseAddress
  141. return $ \(m, ip, p) -> let
  142. crtp = m^.mrcptTo
  143. in (set mrcptTo (rtp:crtp) m, ip, p),
  144. do
  145. rfl <- hdr "Failed" parseAddressingReason
  146. return $ \(m, ip, p) -> let
  147. fld = m^.mrcptFailed
  148. in (set mrcptFailed (rfl:fld) m, ip, p),
  149. do
  150. recv <- hdr "Recv-Date" parseISO8601Val
  151. return $ \(m, ip, p) -> (set mrecvDate (Just recv) m, ip, p),
  152. do
  153. enc <- hdr "Body-Encoding" Mime.parseBodyEncoding
  154. return $ \(m, ip, p) -> (set mbodyEnc enc m, ip, p),
  155. do
  156. utf <- hdr "SMTP-UTF8" parseMetadataBool
  157. return $ \(m, ip, p) -> (set msmtpUtf8 utf m, ip, p),
  158. do
  159. usr <- hdr "Auth-User" A.takeByteString
  160. return $ \(m, ip, p) -> (set mauth (Just usr) m, ip, p),
  161. do
  162. sz <- hdr "Data-Size" A.decimal
  163. return $ \(m, ip, p) -> (set mdataSize (Just sz) m, ip, p),
  164. do
  165. u <- entireHdr
  166. return $ \(m, ip, p) -> let
  167. uu = m^.munrecognized
  168. in (set munrecognized (u:uu) m, ip, p)
  169. ]
  170. entireHdr :: Parser ByteString
  171. entireHdr = do
  172. a <- A.satisfy (not . A.isEndOfLine . asW8)
  173. t <- A.takeTill (A.isEndOfLine . asW8)
  174. A.endOfLine
  175. l <- takeLines
  176. return $ BS.concat [C8.cons a t, "\r\n", l]
  177. takeLines :: Parser ByteString
  178. takeLines = do
  179. c' <- A.peekChar
  180. case c' of
  181. Nothing -> return ""
  182. Just c -> if isCHorizontalSpace c
  183. then do
  184. l <- A.takeTill (A.isEndOfLine . asW8)
  185. A.endOfLine
  186. ll <- takeLines
  187. return $ BS.concat [l, "\r\n", ll]
  188. else return ""
  189. hdr :: ByteString -> Parser a -> Parser a
  190. hdr pt f = do
  191. skipHorizontalSpace
  192. A.stringCI pt
  193. skipHorizontalSpace
  194. A.char ':'
  195. skipHorizontalSpace
  196. t <- bsval
  197. r <- case A.parseOnly f t of
  198. Left _ -> fail $ "failed parsing value of " ++ s pt
  199. Right v -> return v
  200. skipHorizontalSpace
  201. return r
  202. bsval :: Parser ByteString
  203. bsval = do
  204. ll <- entireHdr
  205. let (b, _) = BS.spanEnd (\x -> x == asW8 '\r' || x == asW8 '\n') ll
  206. return b
  207. parseRead :: Read a => Parser a
  208. parseRead = do
  209. v <- A.takeTill A.isSpace
  210. case readMaybe . s $ v of
  211. Nothing -> fail "failed parsing value"
  212. Just i -> return i
  213. parseISO8601Val = do
  214. v <- A.takeTill A.isSpace
  215. case parseISO8601 . s $ v of
  216. Nothing -> fail "failed parsing ISO8601 date"
  217. Just t -> return t
  218. parseMetadataBool :: Parser Bool
  219. parseMetadataBool = A.choice [
  220. A.stringCI "YES" *> return True,
  221. A.stringCI "NO" *> return False
  222. ]
  223. parseAddressingReason :: Parser (Address, Response)
  224. parseAddressingReason = do
  225. a <- parseMetadataAddress
  226. skipHorizontalSpace
  227. A.char ';'
  228. skipHorizontalSpace
  229. r <- parseLineResponse
  230. return (a, r)