summaryrefslogtreecommitdiff
path: root/ymir/mlmmj-expose/mlmmj-expose.hs
diff options
context:
space:
mode:
Diffstat (limited to 'ymir/mlmmj-expose/mlmmj-expose.hs')
-rw-r--r--ymir/mlmmj-expose/mlmmj-expose.hs143
1 files changed, 143 insertions, 0 deletions
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))