gen-passwd-1.0.0.0: src/Main.hs
module Main where
import Control.Monad
import qualified Data.ByteString.Char8 as BS
import Data.Char
import qualified Data.Vector as Vec
import Options.Applicative
import System.Random (newStdGen, randoms, next)
import System.Exit (exitFailure)
import Wordlists
data Arguments = Arguments
{ argsShort :: Bool
, argsSpecial :: Bool
, argsCount :: Int
, argsDelimiter :: String
, argsAcrostic :: String
}
args :: Parser Arguments
args = Arguments
<$> switch (mconcat
[ long "short"
, help "Use the EFF list of short words"
])
<*> switch (mconcat
[ long "special"
, help "Use the EFF list of special words"
])
<*> option auto (mconcat
[ long "count"
, help "The number of passwords"
, showDefault
, value 1
, metavar "INT"
])
<*> strOption (mconcat
[ long "delimiter"
, help "The delimiter to use"
, showDefault
, value " "
])
<*> strOption (mconcat
[ long "acrostic"
, help "The first letters of the words form this word."
, showDefault
, value ""
])
opts :: ParserInfo Arguments
opts = info (args <**> helper) $ mconcat
[ fullDesc
, progDesc "Print a wordlist-based password"
, header "gen-passwd - a password generator"
]
-- | Basically `unsafeIndex (concat vec)`, only faster
concatIndex :: Vec.Vector (Vec.Vector a) -> Int -> a
concatIndex vec n = if n > firstElemLen
then concatIndex (Vec.tail vec) (n - firstElemLen)
else Vec.unsafeIndex (Vec.head vec) n
where
firstElemLen = Vec.length (Vec.head vec)
normalPassword :: Int -> Wordlist -> BS.ByteString -> IO ()
normalPassword len wordlist delim = do
gen <- newStdGen
let ws = map (concatIndex wordlist . flip mod len) $ take 6 $ randoms gen
BS.putStrLn $ BS.intercalate delim ws
acrosticPassword :: Int -> Wordlist -> BS.ByteString -> String -> IO ()
acrosticPassword len wordlist delim acrostic = do
gen <- newStdGen
ws <- go gen acrostic
BS.putStrLn $ BS.intercalate delim ws
where
go _ [] = pure []
go gen (x:xs) = do
let (rand, nextgen) = next gen
let letter = (subtract (ord 'a')) . ord . toLower $ x
when (letter > 25 || letter < 0) $ do
putStrLn $ "The character '" ++ x:[] ++ "' is not part of the wordlist."
exitFailure
let list = Vec.unsafeIndex wordlist letter
let word = Vec.unsafeIndex list (rand `mod` Vec.length list)
rest <- go nextgen xs
pure (word : rest)
main :: IO ()
main = do
args <- execParser opts
(len, wordlist) <- case (argsShort args, argsSpecial args) of
(False, False) -> pure (7776, longWords)
(True , False) -> pure (1296, shortWords)
(False, True ) -> pure (1296, specialWords)
(True , True ) -> do
putStrLn "Sorry, but you can't use the short and special list at the same time."
exitFailure
replicateM_ (argsCount args) $ case argsAcrostic args of
"" -> normalPassword len wordlist (BS.pack $ argsDelimiter args)
acrostic -> acrosticPassword len wordlist (BS.pack $ argsDelimiter args) acrostic