haskdeep 0.1.0.0 → 0.2.0.0
raw patch · 10 files changed
+197/−136 lines, 10 filesdep +base16-bytestringdep +cryptohash-cryptoapidep +old-localedep ~attoparsec-conduitdep ~basedep ~conduit
Dependencies added: base16-bytestring, cryptohash-cryptoapi, old-locale, time
Dependency ranges changed: attoparsec-conduit, base, conduit, crypto-api, crypto-conduit, cryptohash, filesystem-conduit, optparse-applicative
Files
- CHANGELOG.md +11/−0
- LICENSE +1/−1
- README.md +8/−5
- haskdeep.cabal +30/−16
- src/HaskDeep/Computation.hs +15/−5
- src/HaskDeep/ComputationMode.hs +7/−37
- src/HaskDeep/Configuration.hs +9/−4
- src/HaskDeep/KnownHash/Writer.hs +31/−21
- src/Main.hs +2/−1
- src/Options.hs +83/−46
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+## Version 0.2.0.0 (15 Sep 2013)++- Added option -v for version information++- Added option -f and -t to filter files based on modification time++- Changed option -i (exclude based to regex) to -e++## Version 0.1.0.0 (14 Dec 2012)++- Initial release.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012 Mauro Taraborelli+Copyright (c) 2013 Mauro Taraborelli All rights reserved.
README.md view
@@ -14,15 +14,18 @@ user@host:~$ haskdeep - Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+ Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] Computes hashes and audit a set of files Available options:+ -h,--help Show this help text+ -v,--version Show version information -c,--computation MODE md5 | sha1 | sha256 | skein512 - default md5 -r,--root DIRNAME Root directory - default current directory -k,--known FILENAME Known hashes file - default known.haskdeep- -i,--ignore RULE Regex to ignore files or directories- -h,--help Show this help text+ -e,--excl-regex REGEX Exclude files or directories based on regex+ -f,--incl-mod-from DATE Include files modified from yyyy-mm-ddThh:mm:ssZ+ -t,--incl-mod-upto DATE Include files modified up to yyyy-mm-ddThh:mm:ssZ Available commands: compute Computes file hashes and saves them to known hashes file@@ -32,11 +35,11 @@ 1. create known hashes of files contained in a root directory (traversed recursively) - user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -e "tmp|\.log" 2. verify a copy of the same files comparing them with known hashes - user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -e "tmp|\.log" Licensing ---------
haskdeep.cabal view
@@ -1,10 +1,10 @@ name: haskdeep-version: 0.1.0.0+version: 0.2.0.0 cabal-version: >=1.8 build-type: Simple license: BSD3 license-file: LICENSE-copyright: © 2012 Mauro Taraborelli+copyright: © 2013 Mauro Taraborelli author: Mauro Taraborelli maintainer: maurotaraborelli@gmail.com stability: experimental@@ -22,15 +22,18 @@ . > user@host:~$ haskdeep >- > Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] [-i|--ignore RULE]+ > Usage: haskdeep COMMAND [-c|--computation MODE] [-r|--root DIRNAME] [-k|--known FILENAME] > Computes hashes and audit a set of files > > Available options:+ > -h,--help Show this help text+ > -v,--version Show version information > -c,--computation MODE md5 | sha1 | sha256 | skein512 - default md5 > -r,--root DIRNAME Root directory - default current directory > -k,--known FILENAME Known hashes file - default known.haskdeep- > -i,--ignore RULE Regex to ignore files or directories- > -h,--help Show this help text+ > -e,--excl-regex REGEX Exclude files or directories based on regex+ > -f,--incl-mod-from DATE Include files modified from yyyy-mm-ddThh:mm:ssZ+ > -t,--incl-mod-upto DATE Include files modified up to yyyy-mm-ddThh:mm:ssZ > > Available commands: > compute Computes file hashes and saves them to known hashes file@@ -40,16 +43,19 @@ . 1. create known hashes of files contained in a root directory (traversed recursively) .- > user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ > user@host:~$ haskdeep compute -c md5 -r myimportantfiles/ -k knownhashes.txt -e "tmp|\.log" . 2. verify a copy of the same files comparing them with known hashes .- > user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -i "tmp|\.log"+ > user@host:~$ haskdeep audit -c md5 -r copyofmyimportantfiles/ -k knownhashes.txt -e "tmp|\.log" . Heavily inspired by @hashdeep@: <http://md5deep.sourceforge.net/> category: Console, Cryptography, System extra-source-files: README.md+ CHANGELOG.md+homepage: https://github.com/maurotrb/haskdeep+bug-reports: https://github.com/maurotrb/haskdeep/issues executable haskdeep hs-source-dirs: src@@ -65,24 +71,32 @@ HaskDeep.KnownHash.Writer build-depends: attoparsec ==0.10.*- , attoparsec-conduit ==0.5.*- , base ==4.5.*+ , attoparsec-conduit ==1.0.*+ , base ==4.*+ , base16-bytestring ==0.1.* , bytestring ==0.9.* , cereal ==0.3.*- , conduit ==0.5.*+ , conduit ==1.0.* , containers ==0.4.*- , cryptohash ==0.7.*- , crypto-api ==0.10.*- , crypto-conduit ==0.4.*- , filesystem-conduit ==0.5.*- , optparse-applicative ==0.4.*+ , cryptohash ==0.10.*+ , cryptohash-cryptoapi ==0.1.*+ , crypto-api ==0.12.*+ , crypto-conduit ==0.5.*+ , filesystem-conduit ==1.0.*+ , old-locale ==1.0.*+ , optparse-applicative ==0.5.* , system-fileio ==0.3.* , system-filepath ==0.4.* , text ==0.11.*+ , time ==1.4.* , regex-tdfa ==1.1.* , regex-tdfa-text ==1.0.* ghc-options: -Wall source-repository head type: git- location: git://github.com/maurotrb/haskdeep.git+ location: https://github.com/maurotrb/haskdeep.git++source-repository head+ type: git+ location: https://maurotrb@bitbucket.org/maurotrb/haskdeep.git
src/HaskDeep/Computation.hs view
@@ -32,6 +32,7 @@ import qualified Data.Conduit.Filesystem as CF import qualified Data.Conduit.List as CL import qualified Data.Text as T+import Data.Time (UTCTime) import qualified Filesystem as FS import Filesystem.Path.CurrentOS (FilePath) import qualified Filesystem.Path.CurrentOS as FSC@@ -50,12 +51,15 @@ compute conf cm = CF.traverse False root $$ CL.foldM insert_hash empty_with_symbol where root = rootDirectory conf- rule = ignoreRule conf+ regex = excludeRegex conf+ mod_from = includeModFrom conf+ mod_upto = includeModUpTo conf empty_with_symbol = HS.setSymbol (symbol cm) HS.empty insert_hash :: HashSet -> FilePath -> IO HashSet insert_hash hs fp = do let fpt = fpToText $ relativize root fp- if ignore fpt rule+ fmt <- FS.getModified fp+ if (excRegex fpt regex) || (excMod fmt mod_from mod_upto) then return hs else do s <- FS.getSize fp h <- liftM (runComputation cm) $ C.runResourceT@@ -68,6 +72,12 @@ fpToText :: FilePath -> T.Text fpToText = either id id . FSC.toText -ignore :: T.Text -> Maybe T.Text -> Bool-ignore fpt (Just rule) = fpt =~ rule-ignore _ Nothing = False+excRegex :: T.Text -> Maybe T.Text -> Bool+excRegex fpt (Just rule) = fpt =~ rule+excRegex _ Nothing = False++excMod :: UTCTime -> Maybe UTCTime -> Maybe UTCTime -> Bool+excMod fmt (Just modFrom) (Just modUpTo) = (fmt < modFrom) || (fmt > modUpTo)+excMod fmt (Just modFrom) Nothing = fmt < modFrom+excMod fmt Nothing (Just modUpTo) = fmt > modUpTo+excMod _ Nothing Nothing = False
src/HaskDeep/ComputationMode.hs view
@@ -24,14 +24,9 @@ ) where -import Data.Bits ((.&.), shiftR)--import Crypto.Hash.MD5 (MD5)-import Crypto.Hash.SHA1 (SHA1)-import Crypto.Hash.SHA256 (SHA256)-import Crypto.Hash.Skein512 (Skein512)+import Crypto.Hash.CryptoAPI (MD5, SHA1, SHA256, Skein512_512) import Data.ByteString (ByteString)-import qualified Data.ByteString as BS+import qualified Data.ByteString.Base16 as BB16 import qualified Data.Serialize as S import qualified Data.Text as T @@ -43,41 +38,16 @@ -- | MD5 computation. md5hash :: ComputationMode MD5-md5hash = ComputationMode "md5" (toHex . S.encode)+md5hash = ComputationMode "md5" (BB16.encode . S.encode) -- | SHA1 computation. sha1hash :: ComputationMode SHA1-sha1hash = ComputationMode "sha1" (toHex . S.encode)+sha1hash = ComputationMode "sha1" (BB16.encode . S.encode) -- | SHA256 computation. sha256hash :: ComputationMode SHA256-sha256hash = ComputationMode "sha256" (toHex . S.encode)+sha256hash = ComputationMode "sha256" (BB16.encode . S.encode) -- | Skein512 computation.-skein512hash :: ComputationMode Skein512-skein512hash = ComputationMode "skein512" (toHex . S.encode)---- Only with ByteString 0.10---toHex :: ByteString -> ByteString---toHex = BS.concat . BL.toChunks . BB.toLazyByteString . BA.byteStringHexFixed---- Taken from--- http://stackoverflow.com/questions/10099921/efficiently-turn-a-bytestring-into-a-hex-representation-toHex :: ByteString -> ByteString-toHex bs0 =- fst $ BS.unfoldrN (BS.length bs0 * 2) go (Left bs0)- where- go (Left bs) =- case BS.uncons bs of- Nothing -> Nothing- Just (w, bs') ->- let w1 = w `shiftR` 4- w2 = w .&. 15- c1 = toC w1- c2 = toC w2- in Just (c1, Right (c2, bs'))- go (Right (c, bs)) = Just (c, Left bs)-- toC w- | w < 10 = w + 48- | otherwise = w + 87+skein512hash :: ComputationMode Skein512_512+skein512hash = ComputationMode "skein512" (BB16.encode . S.encode)
src/HaskDeep/Configuration.hs view
@@ -23,14 +23,17 @@ import Prelude hiding (FilePath) import Data.Text (Text)+import Data.Time (UTCTime) import Filesystem.Path (FilePath) import qualified Filesystem.Path.CurrentOS as FSC -- | HaskDeep configuration. data HaskDeepConfiguration = HaskDeepConfiguration- { rootDirectory :: FilePath -- ^ Root directory- , knownHashes :: FilePath -- ^ Known hashes file- , ignoreRule :: Maybe Text -- ^ Ignore rule (Regex)+ { rootDirectory :: FilePath -- ^ Root directory+ , knownHashes :: FilePath -- ^ Known hashes file+ , excludeRegex :: Maybe Text -- ^ Exclude rule (Regex)+ , includeModFrom :: Maybe UTCTime -- ^ Include file modified from+ , includeModUpTo :: Maybe UTCTime -- ^ Include file modified up to } -- | HaskDeep default configuration.@@ -38,5 +41,7 @@ defaultHaskDeepConfiguration = HaskDeepConfiguration { rootDirectory = FSC.decodeString "." , knownHashes = FSC.decodeString "known.haskdeep"- , ignoreRule = Nothing+ , excludeRegex = Nothing+ , includeModFrom = Nothing+ , includeModUpTo = Nothing }
src/HaskDeep/KnownHash/Writer.hs view
@@ -29,10 +29,12 @@ import qualified Data.Conduit as C import qualified Data.Conduit.Filesystem as CF import qualified Data.Conduit.List as CL+import qualified Data.Time as DT import Data.Text () import qualified Data.Text.Encoding as TE import Filesystem.Path () import qualified Filesystem.Path.CurrentOS as FSC+import qualified System.Locale as SL import HaskDeep.Configuration import HaskDeep.HashSet (HashSet)@@ -44,24 +46,32 @@ -> IO () writeHashes conf hs = C.runResourceT $ CL.sourceList bs_known $$ CF.sinkFile (knownHashes conf) where- newline = "\n" :: ByteString- root = TE.encodeUtf8 $ either id id $ FSC.toText $ rootDirectory conf- ignore = case ignoreRule conf of- (Just rule) -> TE.encodeUtf8 rule- Nothing -> BS.empty- files_count = B8.pack $ show $ HS.filesCount hs- size_sum = B8.pack $ show $ HS.sizeSum hs- known_header1 = "%%%% HASHDEEP-1.0"- `BS.append` newline- known_header2 = "%%%% size,"- `BS.append` TE.encodeUtf8 (HS.compSymbol hs)- `BS.append` ",filename"- `BS.append` newline- bs_comments = map (flip BS.append newline . BS.append "## ")- [ "Root directory: " `BS.append` root- , "Ignore rule : " `BS.append` ignore- , "Files count : " `BS.append` files_count- , "Files size : " `BS.append` size_sum- , "" ]- bs_hash_set = map (flip BS.append newline . HS.toByteString) $ HS.toAscList hs- bs_known = [known_header1, known_header2] ++ bs_comments ++ bs_hash_set+ newline = "\n" :: ByteString+ root = TE.encodeUtf8 $ either id id $ FSC.toText $ rootDirectory conf+ exclude_regex = case excludeRegex conf of+ (Just regex) -> TE.encodeUtf8 regex+ Nothing -> BS.empty+ include_mod_from = case includeModFrom conf of+ (Just modFrom) -> B8.pack $ DT.formatTime SL.defaultTimeLocale "%FT%TZ" modFrom+ Nothing -> BS.empty+ include_mod_upto = case includeModUpTo conf of+ (Just modUpTo) -> B8.pack $ DT.formatTime SL.defaultTimeLocale "%FT%TZ" modUpTo+ Nothing -> BS.empty+ files_count = B8.pack $ show $ HS.filesCount hs+ size_sum = B8.pack $ show $ HS.sizeSum hs+ known_header1 = "%%%% HASHDEEP-1.0"+ `BS.append` newline+ known_header2 = "%%%% size,"+ `BS.append` TE.encodeUtf8 (HS.compSymbol hs)+ `BS.append` ",filename"+ `BS.append` newline+ bs_comments = map (flip BS.append newline . BS.append "## ")+ [ "Root directory : " `BS.append` root+ , "Exclude regex : " `BS.append` exclude_regex+ , "Include mod. from : " `BS.append` include_mod_from+ , "Include mod. upto : " `BS.append` include_mod_upto+ , "Files count : " `BS.append` files_count+ , "Files size : " `BS.append` size_sum+ , "" ]+ bs_hash_set = map (flip BS.append newline . HS.toByteString) $ HS.toAscList hs+ bs_known = [known_header1, known_header2] ++ bs_comments ++ bs_hash_set
src/Main.hs view
@@ -25,9 +25,10 @@ import Options main :: IO ()-main = OA.execParser optionsPI >>= haskdeep+main = OA.customExecParser (OA.prefs OA.showHelpOnError) optionsPI >>= haskdeep haskdeep :: Options -> IO ()+haskdeep Version = putStrLn haskdeepVersion haskdeep (Options OptComputation OptMD5 conf) = execComputation conf md5hash haskdeep (Options OptComputation OptSHA1 conf) = execComputation conf sha1hash haskdeep (Options OptComputation OptSHA256 conf) = execComputation conf sha256hash
src/Options.hs view
@@ -16,6 +16,7 @@ ,OptExecution(..) ,OptCompMode(..) ,optionsPI+ ,haskdeepVersion ) where @@ -23,14 +24,21 @@ import Data.Text (Text) import qualified Data.Text as T+import Data.Time (UTCTime)+import qualified Data.Time as DT import Filesystem.Path.CurrentOS (FilePath) import qualified Filesystem.Path.CurrentOS as FSC import Options.Applicative+import qualified System.Locale as SL import HaskDeep -data Options = Options OptExecution OptCompMode HaskDeepConfiguration+haskdeepVersion :: String+haskdeepVersion = "haskdeep 0.2.0.0 - file hashing and audit" +data Options = Version+ | Options OptExecution OptCompMode HaskDeepConfiguration+ data OptExecution = OptComputation | OptAudit @@ -40,62 +48,91 @@ | OptSkein512 optionsPI :: ParserInfo Options-optionsPI = info (optionsP <**> helper)+optionsPI = info (helper <*> optionsP) ( fullDesc- & progDesc "Computes hashes and audit a set of files"- & header "haskdeep - file hashing and audit" )+ <> progDesc "Computes hashes and audit a set of files"+ <> header haskdeepVersion) configurationP :: Parser HaskDeepConfiguration configurationP = HaskDeepConfiguration <$> nullOption ( long "root"- & short 'r'- & metavar "DIRNAME"- & help "Root directory - default current directory"- & reader fpReader- & value (rootDirectory defaultHaskDeepConfiguration))+ <> short 'r'+ <> metavar "DIRNAME"+ <> help "Root directory - default current directory"+ <> reader fpReader+ <> value (rootDirectory defaultHaskDeepConfiguration)) <*> nullOption ( long "known"- & short 'k'- & metavar "FILENAME"- & help "Known hashes file - default known.haskdeep"- & reader fpReader- & value (knownHashes defaultHaskDeepConfiguration))+ <> short 'k'+ <> metavar "FILENAME"+ <> help "Known hashes file - default known.haskdeep"+ <> reader fpReader+ <> value (knownHashes defaultHaskDeepConfiguration)) <*> nullOption- ( long "ignore"- & short 'i'- & metavar "RULE"- & help "Regex to ignore files or directories"- & reader ignReader- & value (ignoreRule defaultHaskDeepConfiguration))+ ( long "excl-regex"+ <> short 'e'+ <> metavar "REGEX"+ <> help "Exclude files or directories based on regex"+ <> reader regexReader+ <> value (excludeRegex defaultHaskDeepConfiguration)+ <> hidden )+ <*> nullOption+ ( long "incl-mod-from"+ <> short 'f'+ <> metavar "DATE"+ <> help "Include files modified from yyyy-mm-ddThh:mm:ssZ"+ <> reader timeReader+ <> value (includeModFrom defaultHaskDeepConfiguration)+ <> hidden )+ <*> nullOption+ ( long "incl-mod-upto"+ <> short 't'+ <> metavar "DATE"+ <> help "Include files modified up to yyyy-mm-ddThh:mm:ssZ"+ <> reader timeReader+ <> value (includeModUpTo defaultHaskDeepConfiguration)+ <> hidden ) optionsP :: Parser Options-optionsP = Options- <$> subparser- ( command "compute"- (info (pure OptComputation)- (progDesc "Computes file hashes and saves them to known hashes file"))- & command "audit"- (info (pure OptAudit)- (progDesc "Audit files comparing them to known hashes")))- <*> nullOption- ( long "computation"- & short 'c'- & metavar "MODE"- & help "md5 | sha1 | sha256 | skein512 - default md5"- & reader compReader- & value OptMD5)- <*> configurationP+optionsP = flag' Version+ ( long "version"+ <> short 'v'+ <> help "Show version information"+ <> hidden )+ <|> ( Options+ <$> subparser+ ( command "compute"+ (info (pure OptComputation)+ (progDesc "Computes file hashes and saves them to known hashes file"))+ <> command "audit"+ (info (pure OptAudit)+ (progDesc "Audit files comparing them to known hashes")))+ <*> nullOption+ ( long "computation"+ <> short 'c'+ <> metavar "MODE"+ <> help "md5 | sha1 | sha256 | skein512 - default md5"+ <> reader compReader+ <> value OptMD5)+ <*> configurationP ) -fpReader :: String -> Maybe FilePath-fpReader fp = Just $ FSC.decodeString fp+compReader :: String -> Either ParseError OptCompMode+compReader "md5" = Right OptMD5+compReader "sha1" = Right OptSHA1+compReader "sha256" = Right OptSHA256+compReader "skein512" = Right OptSkein512+compReader _ = Left ShowHelpText -ignReader :: String -> Maybe (Maybe Text)-ignReader = Just . Just . T.pack+fpReader :: String -> Either ParseError FilePath+fpReader fp = Right $ FSC.decodeString fp -compReader :: String -> Maybe OptCompMode-compReader "md5" = Just OptMD5-compReader "sha1" = Just OptSHA1-compReader "sha256" = Just OptSHA256-compReader "skein512" = Just OptSkein512-compReader _ = Nothing+regexReader :: String -> Either ParseError (Maybe Text)+regexReader = Right . Just . T.pack++timeReader :: String -> Either ParseError (Maybe UTCTime)+timeReader dt = case parsedTime of+ (Just _) -> Right parsedTime+ Nothing -> Left ShowHelpText+ where+ parsedTime = DT.parseTime SL.defaultTimeLocale "%FT%TZ" dt