{- Copyright 2016 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} module CmdLine where import Types import Types.Storage import Types.Server (HostName) import Types.Cost (Seconds(..)) import Tunables import qualified Gpg import Options.Applicative import qualified Data.ByteString.UTF8 as BU8 import qualified Data.Text as T import System.Directory import Network.Wai.Handler.Warp (Port) data CmdLine = CmdLine { mode :: Maybe Mode , secretkeysource :: Maybe SecretKeySource , localstorage :: Bool , localstoragedirectory :: Maybe LocalStorageDirectory , gui :: Bool , testMode :: Bool , customShareParams :: Maybe ShareParams , name :: Maybe Name , othername :: Maybe Name , serverConfig :: ServerConfig , chaffMaxDelay :: Maybe Seconds } data Mode = Backup | Restore | UploadQueued | AutoStart | Server | BackupServer FilePath | RestoreServer FilePath | Chaff HostName | CheckServers | Benchmark | Test deriving (Show) data ServerConfig = ServerConfig { serverPort :: Port , serverAddress :: String , monthsToFillHalfDisk :: Integer , serverMotd :: Maybe T.Text } parse :: Parser CmdLine parse = CmdLine <$> optional parseMode <*> optional (gpgswitch <|> fileswitch) <*> localstorageswitch <*> optional localstoragedirectoryopt <*> guiswitch <*> testmodeswitch <*> optional parseShareParams <*> optional nameopt <*> optional othernameopt <*> parseServerConfig <*> optional chaffmaxdelayopt where gpgswitch = GpgKey . KeyId . T.pack <$> strOption ( long "gpgkeyid" <> metavar "KEYID" <> help "Specify keyid of gpg key to back up or restore. (When this option is used to back up a key, it must also be used at restore time.)" ) fileswitch = KeyFile <$> strOption ( long "keyfile" <> metavar "FILE" <> help "Specify secret key file to back up or restore. (The same filename must be used to restore a key as was used to back it up.)" ) localstorageswitch = switch ( long "store-local" <> help "Store data locally. (The default is to store data in the cloud.)" ) localstoragedirectoryopt = LocalStorageDirectory <$> option str ( long "store-directory" <> metavar "DIR" <> help "Where to store data locally. (default: ~/.keysafe/objects/)" ) testmodeswitch = switch ( long "testmode" <> help "Avoid using expensive cryptographic operations to secure data. Use for testing only, not with real secret keys." ) guiswitch = switch ( long "gui" <> help "Use GUI interface for interaction. Default is to use readline interface when run in a terminal, and GUI otherwise." ) nameopt = option nameOption ( long "name" <> metavar "N" <> help "Specify name used for key backup/restore, avoiding the usual prompt." ) othernameopt = option nameOption ( long "othername" <> metavar "N" <> help "Specify other name used for key backup/restore, avoiding the usual prompt." ) chaffmaxdelayopt = option secondsOption ( long "chaff-max-delay" <> metavar "SECONDS" <> help "Specify a delay between chaff uploads. Will delay a random amount between 0 and this many seconds." ) parseMode :: Parser Mode parseMode = flag' Backup ( long "backup" <> help "Store a secret key in keysafe." ) <|> flag' Restore ( long "restore" <> help "Retrieve a secret key from keysafe." ) <|> flag' UploadQueued ( long "uploadqueued" <> help "Upload any data to servers that was queued by a previous --backup run." ) <|> flag' AutoStart ( long "autostart" <> help "Run automatically on login by desktop autostart file." ) <|> flag' Server ( long "server" <> help "Run as a keysafe server, accepting objects and storing them to ~/.keysafe/objects/local/" ) <|> BackupServer <$> strOption ( long "backup-server" <> metavar "BACKUPDIR" <> help "Run on a server, populates the directory with a gpg encrypted backup of all objects stored in the --store-directory. This is designed to be rsynced offsite (with --delete) to back up the a keysafe server with minimal information leakage." ) <|> RestoreServer <$> strOption ( long "restore-server" <> metavar "BACKUPDIR" <> help "Restore all objects present in the gpg-encrypted backups in the specified directory." ) <|> Chaff <$> strOption ( long "chaff" <> metavar "HOSTNAME" <> help "Upload random data to a keysafe server." ) <|> flag' CheckServers ( long "check-servers" <> help "Tries to connect to each server in the server list. Displays the server's MOTD, and the amount of data stored on it. Prints message to stderr and exits nonzero if any of the servers are not accessible." ) <|> flag' Benchmark ( long "benchmark" <> help "Benchmark speed of keysafe's cryptographic primitives." ) <|> flag' Test ( long "test" <> help "Run test suite." ) parseShareParams :: Parser ShareParams parseShareParams = ShareParams <$> totalobjects <*> neededobjects where totalobjects = option auto ( long "totalshares" <> metavar "M" <> help ("Configure the number of shares to split encrypted secret key into. " ++ showdefault totalObjects ++ neededboth) ) neededobjects = option auto ( long "neededshares" <> metavar "N" <> help ("Configure the number of shares needed to restore. " ++ showdefault neededObjects ++ neededboth) ) showdefault f = "(default: " ++ show (f (shareParams defaultTunables)) ++ ")" neededboth = " (When this option is used to back up a key, it must also be provided at restore time.)" parseServerConfig :: Parser ServerConfig parseServerConfig = ServerConfig <$> option auto ( long "port" <> metavar "P" <> value 80 <> showDefault <> help "Port for server to listen on." ) <*> option str ( long "address" <> metavar "A" <> value "127.0.0.1" <> showDefault <> help "Address for server to bind to. (Use \"*\" to bind to all addresses.)" ) <*> option auto ( long "months-to-fill-half-disk" <> metavar "N" <> value 12 <> showDefault <> help "Server rate-limits requests and requires proof of work, to avoid too many objects being stored. This is an lower bound on how long it could possibly take for half of the current disk space to be filled." ) <*> optional (T.pack <$> strOption ( long "motd" <> metavar "MESSAGE" <> help "The server's Message Of The Day." )) get :: IO CmdLine get = execParser opts where opts = info (helper <*> parse) ( fullDesc <> header "keysafe - securely back up secret keys" ) -- | When a mode is not specified on the command line, -- default to backing up if a secret key exists, and otherwise restoring. selectMode :: CmdLine -> IO Mode selectMode cmdline = case mode cmdline of Just m -> return m Nothing -> case secretkeysource cmdline of Just (KeyFile f) -> present <$> doesFileExist f _ -> present . not . null <$> Gpg.listSecretKeys where present True = Backup present False = Restore customizeShareParams :: CmdLine -> Tunables -> Tunables customizeShareParams cmdline t = case customShareParams cmdline of Nothing -> t Just ps -> t { shareParams = ps } secondsOption :: ReadM Seconds secondsOption = Seconds . toRational <$> (auto :: ReadM Double) nameOption :: ReadM Name nameOption = Name . BU8.fromString <$> auto