summaryrefslogtreecommitdiff
path: root/ymir/mlmmj-expose
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2018-09-06 17:29:54 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2018-09-06 17:29:54 +0200
commite28b7def6cb2e4542c5fcbfc478dde85cd73ea3f (patch)
tree50891015f579511bdb4bc236562a6622d4e601b5 /ymir/mlmmj-expose
parentbec99bf3ad9c1ab09ee08fade347d9191a986fe1 (diff)
downloadnixos-e28b7def6cb2e4542c5fcbfc478dde85cd73ea3f.tar
nixos-e28b7def6cb2e4542c5fcbfc478dde85cd73ea3f.tar.gz
nixos-e28b7def6cb2e4542c5fcbfc478dde85cd73ea3f.tar.bz2
nixos-e28b7def6cb2e4542c5fcbfc478dde85cd73ea3f.tar.xz
nixos-e28b7def6cb2e4542c5fcbfc478dde85cd73ea3f.zip
Better mlmmj-expose
Diffstat (limited to 'ymir/mlmmj-expose')
-rw-r--r--ymir/mlmmj-expose/FoxReplace.hs67
-rw-r--r--ymir/mlmmj-expose/mlmmj-expose.hs143
2 files changed, 210 insertions, 0 deletions
diff --git a/ymir/mlmmj-expose/FoxReplace.hs b/ymir/mlmmj-expose/FoxReplace.hs
new file mode 100644
index 00000000..ca28fa43
--- /dev/null
+++ b/ymir/mlmmj-expose/FoxReplace.hs
@@ -0,0 +1,67 @@
1{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
2
3module FoxReplace where
4
5import Data.Aeson
6
7import Data.Set (Set)
8import qualified Data.Set as Set
9
10
11newtype FoxReplace = FoxReplace (Set FoxReplaceGroup)
12 deriving (Ord, Eq, Show)
13
14data FoxReplaceGroup = FoxReplaceGroup
15 { groupName :: String
16 , groupUrls :: Set String
17 , groupSubs :: Set FoxReplaceSub
18 , groupHtmlMode :: FoxReplaceHTML
19 }
20 deriving (Ord, Eq, Show)
21
22data FoxReplaceHTML = NoHTML | OutputOnlyHTML | BothHTML
23 deriving (Ord, Eq, Enum, Show)
24
25data FoxReplaceSub = FoxReplaceSub
26 { rInput, rOutput :: String
27 , rInputType :: SubInput
28 , rCaseSensitive :: Bool
29 }
30 deriving (Ord, Eq, Show)
31
32data SubInput = TextInput | WordInput | RegexpInput
33 deriving (Ord, Eq, Enum, Show)
34
35
36instance ToJSON FoxReplace where
37 toJSON (FoxReplace groupSet) = object
38 [ "version" .= ("0.15" :: String)
39 , "groups" .= groupSet
40 ]
41
42instance ToJSON FoxReplaceGroup where
43 toJSON FoxReplaceGroup{..} = object
44 [ "name" .= groupName
45 , "html" .= groupHtmlMode
46 , "enabled" .= True
47 , "urls" .= groupUrls
48 , "substitutions" .= groupSubs
49 ]
50
51instance ToJSON FoxReplaceHTML where
52 toJSON NoHTML = String "none"
53 toJSON OutputOnlyHTML = String "output"
54 toJSON BothHTML = String "inputoutput"
55
56instance ToJSON FoxReplaceSub where
57 toJSON FoxReplaceSub{..} = object
58 [ "input" .= rInput
59 , "output" .= rOutput
60 , "inputType" .= rInputType
61 , "caseSensitive" .= rCaseSensitive
62 ]
63
64instance ToJSON SubInput where
65 toJSON TextInput = String "text"
66 toJSON WordInput = String "wholewords"
67 toJSON RegexpInput = String "regexp"
diff --git a/ymir/mlmmj-expose/mlmmj-expose.hs b/ymir/mlmmj-expose/mlmmj-expose.hs
new file mode 100644
index 00000000..a7b9ec48
--- /dev/null
+++ b/ymir/mlmmj-expose/mlmmj-expose.hs
@@ -0,0 +1,143 @@
1{-# LANGUAGE ViewPatterns, RecordWildCards, DataKinds #-}
2
3import System.IO
4import System.IO.Error
5import System.FilePath
6import System.Environment
7import System.Exit
8import System.Directory
9import System.Process
10import Text.Printf
11
12import Data.Char
13
14import Control.Monad
15import Control.Monad.State
16
17import Crypto.Hash
18
19import qualified Data.ByteString.Lazy as LBS
20import qualified Data.ByteString.Char8 as CBS
21
22import qualified Data.UUID as UUID (toString)
23import qualified Data.UUID.V4 as UUID (nextRandom)
24
25import Data.Aeson
26import Data.Aeson.Encode.Pretty
27
28import Data.Set (Set)
29import qualified Data.Set as Set
30
31
32import FoxReplace
33
34
35data SubscriberIdent = SubscriberIdent
36 { siList, siIdent, siEmail :: String
37 } deriving (Ord, Eq, Show)
38
39hashIdent :: SubscriberIdent -> String
40hashIdent SubscriberIdent{..} = map toLower . show $ digest
41 where
42 digest :: Digest (SHAKE128 128)
43 digest = hashFinalize . flip execState hashInit $ do
44 let hashUpdate' = modify . flip hashUpdate . CBS.pack
45 hashUpdate' siList
46 hashUpdate' siIdent
47 hashUpdate' siEmail
48
49identEmail :: Bool {- ^ RegEx? -}
50 -> SubscriberIdent
51 -> String
52identEmail False si@SubscriberIdent{..} = siList ++ "+" ++ hashIdent si ++ "@subs.lists.yggdrasil.li"
53identEmail True si@SubscriberIdent{..} = siList ++ "\\+" ++ hashIdent si ++ "(@subs\\.lists\\.yggdrasil\\.li)?"
54
55
56main :: IO ()
57main = do
58 progName <- takeFileName <$> getProgName
59 case progName of
60 "mlmmj-exposed" -> do
61 args <- getArgs
62 case args of
63 [(dropTrailingPathSeparator -> listDir@(takeBaseName -> siList)), (map toLower -> extension)] -> do
64 setCurrentDirectory listDir
65 identities <- getIdentities
66 subscribers <- getSubscribers
67 let hashes = filter ((==) extension . snd) [((siIdent, siEmail), hashIdent SubscriberIdent{..}) | siIdent <- identities, siEmail <- subscribers ]
68 case hashes of
69 [((_, recipient), _)] -> do
70 uuid <- UUID.nextRandom
71 let fName = "queue" </> "exposed" <.> uuidTrans uuid
72 uuidTrans = uuidTrans' . UUID.toString
73 where
74 uuidTrans' [] = []
75 uuidTrans' ('-':xs) = uuidTrans' xs
76 uuidTrans' (x:xs) = x : uuidTrans' xs
77 getContents >>= writeFile fName
78 hPrintf stdout "Forwarding mail to <%s>, subscribed to %s\n" recipient (takeBaseName listDir)
79 callProcess "@mlmmj@/bin/mlmmj-send" ["-L", listDir, "-l", "6", "-m", fName, "-T", recipient]
80 removeFile fName
81 [] -> die "Unknown extension"
82 _ -> die "Ambiguous extension"
83 _ -> hPutStrLn stderr ("Called without expected arguments (<listDirectory> <recipientExtension>)") >> exitWith (ExitFailure 2)
84 "mlmmj-expose" -> do
85 args <- getArgs
86 case args of
87 [listDir, (map toLower -> ident)] -> do
88 setCurrentDirectory listDir
89 identities <- getIdentities
90 case ident `elem` identities of
91 True -> putStrLn "Identity is already known"
92 False -> writeFile "exposed.ids" . unlines $ ident : identities
93 _ -> hPutStrLn stderr ("Called without expected arguments (<listDirectory> <senderIdentity>)") >> exitWith (ExitFailure 2)
94 "mlmmj-get-exposed" -> do
95 args <- getArgs
96 case args of
97 [(dropTrailingPathSeparator -> listDir@(takeBaseName -> siList)), (map toLower -> siIdent)] -> do
98 setCurrentDirectory listDir
99 identities <- getIdentities
100 unless (siIdent `elem` identities) . die $ "Unknown sender: ‘" ++ siIdent ++ "’"
101 mapM_ (\siEmail -> putStrLn $ siEmail ++ " " ++ identEmail False SubscriberIdent{..}) =<< getSubscribers
102 (dropTrailingPathSeparator -> listDir@(takeBaseName -> siList)) : (map toLower -> siIdent) : (map (map toLower) -> recipients) -> do
103 setCurrentDirectory listDir
104 identities <- getIdentities
105 unless (siIdent `elem` identities) . die $ "Unknown sender: ‘" ++ siIdent ++ "’"
106 subscribers <- getSubscribers
107 forM_ recipients $ \siEmail -> do
108 unless (siEmail `elem` subscribers) . die $ "Unknown recipient: ‘" ++ siEmail ++ "’"
109 putStrLn $ identEmail False SubscriberIdent{..}
110 _ -> hPutStrLn stderr ("Called without expected arguments (<listDirectory> <senderIdentity> [<recipient> [...]])") >> exitWith (ExitFailure 2)
111 "mlmmj-serve-exposed" -> do
112 args <- getArgs
113 case args of
114 [(dropTrailingPathSeparator -> listDir@(takeBaseName -> siList))] -> do
115 setCurrentDirectory listDir
116 subscribers <- getSubscribers
117 identities <- getIdentities
118
119 let
120 replaceGroup siIdent = FoxReplaceGroup { groupName = siIdent ++ "." ++ siList
121 , groupHtmlMode = NoHTML
122 , groupUrls = Set.empty
123 , groupSubs = Set.fromList $ map (replaceSub siIdent) subscribers
124 }
125 replaceSub siIdent siEmail = FoxReplaceSub { rInput = identEmail True SubscriberIdent{..}
126 , rOutput = siEmail
127 , rInputType = RegexpInput
128 , rCaseSensitive = False
129 }
130
131 LBS.putStr . encodePretty . FoxReplace . Set.fromList $ map replaceGroup identities
132 putChar '\n'
133 _ -> hPutStrLn stderr "Called without expected arguments (<listDirectory>)" >> exitWith (ExitFailure 2)
134 _ -> hPutStrLn stderr ("Called under unsupported name ‘" ++ progName ++ "’") >> exitWith (ExitFailure 2)
135
136
137getIdentities :: IO [String]
138getIdentities = (filter (not . null) . lines <$> readFile "exposed.ids") `catchIOError` (\e -> if isDoesNotExistError e then return [] else ioError e)
139
140getSubscribers :: IO [String]
141getSubscribers = map (map toLower) . concat <$> mapM (flip catchIOError (\e -> if isDoesNotExistError e then return [] else ioError e) . readDir) ["subscribers.d", "digesters.d"]
142 where
143 readDir dir = concat <$> (mapM (fmap lines . readFile) . map (dir </>) . filter (not . (`elem` [".", ".."]))=<< (getDirectoryContents dir))