1 -- This file is part of Intricacy
2 -- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
13 import Control
.Applicative
15 import Control
.Monad
.IO.Class
16 import Control
.Monad
.Trans
.Reader
17 import qualified Data
.ByteString
.Char8
as CS
18 import qualified Data
.ByteString
.Lazy
.Char8
as CL
19 import Data
.Char (toUpper)
21 import Data
.Tuple
(swap
)
22 import System
.Directory
23 import System
.FilePath
26 import Crypto
.Hash
(Digest
, SHA1
, hashlazy
)
27 import Crypto
.PubKey
.RSA
.Types
(PrivateKey
, PublicKey
)
28 import Data
.ByteArray
.Encoding
(Base
(..), convertToBase
)
35 sha1
:: CL
.ByteString
-> Digest SHA1
37 hash
:: String -> String
38 hash
= CS
.unpack
. digestToHexByteString
. sha1
. CL
.pack
39 where digestToHexByteString
= convertToBase Base16
42 = RecPasswordLegacy Codename
43 | RecPasswordArgon2 Codename
45 | RecUserInfo Codename
46 | RecUserInfoLog Codename
50 | RecRetiredLocks Codename
55 deriving (Eq
, Ord
, Show)
57 = RCPasswordLegacy Password
58 | RCPasswordArgon2
String
59 | RCUserInfo VersionedUInfo
60 | RCUserInfoDeltas
[UserInfoDelta
]
63 | RCLockHashes
[String]
64 | RCLockSpecs
[LockSpec
]
65 | RCServerInfo ServerInfo
66 | RCEmail CS
.ByteString
67 | RCPublicKey PublicKey
68 | RCSecretKey PrivateKey
71 rcOfServerResp
(ServedServerInfo x
) = RCServerInfo x
72 rcOfServerResp
(ServedLock x
) = RCLock x
73 rcOfServerResp
(ServedSolution x
) = RCSolution x
74 rcOfServerResp
(ServedUserInfo x
) = RCUserInfo x
75 rcOfServerResp
(ServedRetired x
) = RCLockSpecs x
76 rcOfServerResp
(ServedPublicKey x
) = RCPublicKey x
77 rcOfServerResp _
= error "no corresponding rc"
79 invariantRecord
(RecUserInfo _
) = False
80 invariantRecord
(RecUserInfoLog _
) = False
81 invariantRecord
(RecPasswordLegacy _
) = False
82 invariantRecord
(RecPasswordArgon2 _
) = False
83 invariantRecord
(RecRetiredLocks _
) = False
84 invariantRecord
(RecNote _
) = False
85 invariantRecord
(RecEmail _
) = False
86 invariantRecord _
= True
88 askForRecord RecServerInfo
= GetServerInfo
89 askForRecord
(RecUserInfo name
) = GetUserInfo name Nothing
90 askForRecord
(RecLock ls
) = GetLock ls
91 askForRecord
(RecNote note
) = GetSolution note
92 askForRecord
(RecRetiredLocks name
) = GetRetired name
93 askForRecord RecPublicKey
= GetPublicKey
94 askForRecord _
= error "no corresponding request"
96 type DBM
= ReaderT
FilePath IO
97 withDB
:: FilePath -> DBM a
-> IO a
98 withDB
= flip runReaderT
100 recordExists
:: Record
-> DBM
Bool
101 recordExists rec
= recordPath rec
>>= liftIO
. doesFileExist
103 getRecord
:: Record
-> DBM
(Maybe RecordContents
)
105 path
<- recordPath rec
106 liftIO
. ignoreIOErrAlt
$ do
107 h
<- openFile path ReadMode
108 getRecordh rec h
<* hClose h
109 getRecordh
(RecPasswordLegacy _
) h
= (RCPasswordLegacy
<$>) . tryRead
<$> hGetStrict h
110 getRecordh
(RecPasswordArgon2 _
) h
= (RCPasswordArgon2
<$>) . tryRead
<$> hGetStrict h
111 getRecordh
(RecEmail _
) h
= (RCEmail
<$>) . tryRead
<$> hGetStrict h
112 getRecordh
(RecUserInfo _
) h
= (RCUserInfo
<$>) . tryRead
<$> hGetStrict h
113 getRecordh
(RecUserInfoLog _
) h
= (RCUserInfoDeltas
<$>) . tryRead
<$> hGetStrict h
114 getRecordh
(RecLock _
) h
= (RCLock
<$>) . tryRead
<$> hGetStrict h
115 getRecordh
(RecNote _
) h
= (RCSolution
<$>) . tryRead
<$> hGetStrict h
116 getRecordh RecLockHashes h
= (RCLockHashes
<$>) . tryRead
<$> hGetStrict h
117 getRecordh
(RecRetiredLocks name
) h
= (RCLockSpecs
<$>) . tryRead
<$> hGetStrict h
118 getRecordh RecServerInfo h
= (RCServerInfo
<$>) . tryRead
<$> hGetStrict h
119 getRecordh RecServerEmail h
= (RCEmail
<$>) . tryRead
<$> hGetStrict h
120 getRecordh RecPublicKey h
= (RCPublicKey
<$>) . tryRead
<$> hGetStrict h
121 getRecordh RecSecretKey h
= (RCSecretKey
<$>) . tryRead
<$> hGetStrict h
123 hGetStrict h
= CS
.unpack
<$> concatMWhileNonempty
(repeat $ CS
.hGet h
1024)
124 where concatMWhileNonempty
(m
:ms
) = do
128 else (bs `CS
.append`
) <$> concatMWhileNonempty ms
130 putRecord
:: Record
-> RecordContents
-> DBM
()
131 putRecord rec rc
= do
132 path
<- recordPath rec
135 h
<- openFile path WriteMode
138 putRecordh
(RCPasswordLegacy hpw
) h
= hPutStr h
$ show hpw
139 putRecordh
(RCPasswordArgon2 hpw
) h
= hPutStr h
$ show hpw
140 putRecordh
(RCEmail addr
) h
= hPutStr h
$ show addr
141 putRecordh
(RCUserInfo info
) h
= hPutStr h
$ show info
142 putRecordh
(RCUserInfoDeltas deltas
) h
= hPutStr h
$ show deltas
143 putRecordh
(RCLock lock
) h
= hPutStr h
$ show lock
144 putRecordh
(RCSolution solution
) h
= hPutStr h
$ show solution
145 putRecordh
(RCLockHashes hashes
) h
= hPutStr h
$ show hashes
146 putRecordh
(RCLockSpecs lss
) h
= hPutStr h
$ show lss
147 putRecordh
(RCServerInfo sinfo
) h
= hPutStr h
$ show sinfo
148 putRecordh
(RCPublicKey publicKey
) h
= hPutStr h
$ show publicKey
149 putRecordh
(RCSecretKey secretKey
) h
= hPutStr h
$ show secretKey
151 modifyRecord
:: Record
-> (RecordContents
-> RecordContents
) -> DBM
()
152 modifyRecord rec f
= do
153 h
<- recordPath rec
>>= liftIO
. flip openFile ReadWriteMode
155 Just rc
<- getRecordh rec h
156 hSeek h AbsoluteSeek
0
158 hTell h
>>= hSetFileSize h
161 delRecord
:: Record
-> DBM
()
162 delRecord rec
= recordPath rec
>>= liftIO
. removeFile
164 newLockRecord
:: Lock
-> DBM LockSpec
165 newLockRecord lock
= do
167 let path
= dbpath
++[pathSeparator
]++"lastlock"
168 h
<- liftIO
$ openFile path ReadWriteMode
169 contents
<- liftIO
$ hGetStrict h
170 let ls
= if null contents
then 0 else 1 + read contents
171 liftIO
$ hSeek h AbsoluteSeek
0
172 liftIO
$ hPutStr h
$ show ls
174 putRecord
(RecLock ls
) (RCLock lock
)
177 listUsers
:: DBM
[Codename
]
180 liftIO
$ (unpathifyName
<$>) . filter ((==3).length) <$>
181 getDirectoryContents (dbpath
++[pathSeparator
]++"users")
183 recordPath
:: Record
-> DBM
FilePath
185 (++ (pathSeparator
: recordPath
' rec
)) <$> ask
187 recordPath
' (RecPasswordLegacy name
) = userDir name
++ "passwd"
188 recordPath
' (RecPasswordArgon2 name
) = userDir name
++ "passwd_argon2"
189 recordPath
' (RecEmail name
) = userDir name
++ "email"
190 recordPath
' (RecUserInfo name
) = userDir name
++ "info"
191 recordPath
' (RecUserInfoLog name
) = userDir name
++ "log"
192 recordPath
' (RecLock ls
) = locksDir
++ show ls
193 recordPath
' (RecNote
(NoteInfo name _ alock
)) =
194 userDir name
++ "notes" ++ [pathSeparator
] ++ alockFN alock
195 recordPath
' (RecRetiredLocks name
) = userDir name
++ "retired"
196 recordPath
' RecLockHashes
= "lockHashes"
197 recordPath
' RecServerInfo
= "serverInfo"
198 recordPath
' RecServerEmail
= "serverEmail"
199 recordPath
' RecPublicKey
= "publicKey"
200 recordPath
' RecSecretKey
= "secretKey"
202 userDir name
= "users" ++ [pathSeparator
] ++ pathifyName name
++ [pathSeparator
]
203 alockFN
(ActiveLock name idx
) = pathifyName name
++":"++ show idx
204 locksDir
= "locks"++[pathSeparator
]
206 pathifyName
= winSux
. dummyPunctuation
208 -- | Hilariously, "CON", "PRN", "AUX", and "NUL" are reserved on DOS, and
209 -- Windows apparently crashes rather than write a directory with that name!
210 winSux name
= if (toUpper <$> name
) `
elem`
["CON","PRN", "AUX","NUL"]
214 -- | Dummy out characters which are disallowed on unix or dos.
215 -- We use lowercase characters as dummies.
216 -- To avoid collisions on case-insensitive filesystems, we use '_' as an
218 dummyPunctuation
= concatMap $ \c
->
219 maybe [c
] (('_
':) . pure
) (lookup c pathifyAssocs
)
220 unpathifyName
= concatMap $ \c
-> case c
of
222 _
-> pure
$ fromMaybe c
(lookup c
$ swap
<$> pathifyAssocs
)