-- Copyright (C) 2017 Matthew Harm Bekkema
--
-- This file is part of passman-cli.
--
-- passman-cli is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- passman-cli is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see .
import Data.Semigroup ((<>))
import Control.Applicative ((<**>), optional)
import Options.Applicative (execParser, option, strOption, infoOption,
switch, argument, long, short, metavar,
help, hidden, auto, maybeReader,
hsubparser, command, info, progDesc,
fullDesc, header, helper)
import Data.Conduit ((.|), runConduitRes, yield)
import Text.Read (readMaybe)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Numeric.Natural (Natural)
import Control.Exception (evaluate)
import Control.Concurrent (threadDelay)
import System.Timeout (timeout)
import Data.Version (showVersion)
import Paths_passman_cli (version)
import qualified Passman.Core.Version as Passman.Core
import Passman.Core.Config (Config (Config),
masterPasswordHash, passlistPath)
import Passman.Core.Mode (Mode, readMode)
import Passman.Core.Info (Info (Info))
import Passman.Core.Entry (Entry (Entry))
import Passman.Core.Hash (generatePassword)
import qualified Passman.Core.Entry as Entry
import Passman.CLI.MasterPassword
import Passman.CLI.Interaction
import Passman.CLI.Entry
import Passman.CLI.Config
import qualified Passman.CLI.Clipboard as Clip
data Command = ListEntries
| AddEntry (Maybe Info) (Maybe Mode) (Maybe Natural)
| Generate Int
| Reconfigure Bool Bool
deriving Show
main :: IO ()
main = execParser opts >>= run
where
opts = info (parser <**> helper <**> versioner)
( fullDesc
<> header "passman-cli - deterministic password generator" )
parser = hsubparser $ mconcat
[ command "list" $ info listParser $
progDesc "List entries from the passlist"
, command "add" $ info addParser $
progDesc "Add an entry to the passlist"
, command "generate" $ info generateParser $
progDesc "Generate password"
, command "reconfigure" $ info reconfigureParser $
progDesc "Change some or all fields of configuration"
]
listParser = pure ListEntries
addParser = AddEntry
<$> optional (strOption
( long "info"
<> short 'i'
<> metavar "INFO"
<> help "Info string for new entry" ))
<*> optional (option (maybeReader readMode)
( long "mode"
<> short 'M'
<> metavar "MODE"
<> help "Mode for new entry" ))
<*> optional (option auto
( long "max-length"
<> short 'x'
<> metavar "LENGTH"
<> help "Maximum length for new entry (0 for no maximum)" ))
generateParser = Generate
<$> argument auto
( metavar "ENTRY"
<> help "Entry to generate password for"
)
reconfigureParser = Reconfigure
<$> switch
( long "master-password"
<> help "Reconfigure master password" )
<*> switch
( long "passlist-path"
<> help "Reconfigure passlist path" )
versioner = infoOption versionString
( long "version"
<> short 'V'
<> help "Print version information"
<> hidden )
versionString = "passman-cli version " <> showVersion version
<> "\npassman-core version " <> showVersion Passman.Core.version
<> "\n"
<> "\nCopyright 2017 Matthew Harm Bekkema"
<> "\nLicense GPLv3+: GNU GPL version 3 or later "
run :: Command -> IO ()
run ListEntries =
T.putStr . renderList =<< loadList . passlistPath =<< getConfig
run (AddEntry i' m' l') = do
config <- getConfig
putStrLn "New entry"
putStrLn "========="
i <- maybe (Info . T.pack <$> askString "info: ") pure i'
m <- maybe (askString "mode: " `withValidation` readMode) pure m'
l <- maybe (askString "maximum length (0 for no maximum): " `withValidation`
readMaybe) pure l'
runConduitRes $ yield (Entry i l m) .| Entry.append (passlistPath config)
run (Generate i) = do
config <- getConfig
entry <- loadEntry i $ passlistPath config
T.putStrLn $ renderEntry entry
mp <- askMasterPassword $ masterPasswordHash config
p <- evaluate $ generatePassword entry mp
Clip.withInitialSetup $ \s -> do
putStrLn "Password now in clipboard..."
_ <- timeout 5000000 $ Clip.setClipboardString s p >>
threadDelay 5000000
putStrLn "Clearing clipboard..."
Clip.clearClipboard s
run (Reconfigure False False) = run $ Reconfigure True True
run (Reconfigure mph' plp') = do
mConfig <- tryLoadConfig
case mConfig of
Left e -> do
putStrLn e
configure Nothing Nothing
Right (Config mph plp) -> configure (Just (mph', mph))
(Just (plp', plp))