{- 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 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 } data Mode = Backup | Restore | UploadQueued | AutoStart | Server | BackupServer FilePath | RestoreServer FilePath | Chaff HostName | Benchmark | Test deriving (Show) data ServerConfig = ServerConfig { serverPort :: Port , serverAddress :: String , monthsToFillHalfDisk :: Integer } parse :: Parser CmdLine parse = CmdLine <$> optional parseMode <*> optional (gpgswitch <|> fileswitch) <*> localstorageswitch <*> localstoragedirectoryopt <*> guiswitch <*> testmodeswitch <*> optional (ShareParams <$> totalobjects <*> neededobjects) <*> nameopt <*> othernameopt <*> parseServerConfig 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 = optional $ 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." ) totalobjects = option auto ( long "totalshares" <> metavar "M" <> help ("Configure the number of shares to split encrypted secret key into. (default: " ++ show (totalObjects (shareParams defaultTunables)) ++ ") (When this option is used to back up a key, it must also be provided at restore time.)") ) neededobjects = option auto ( long "neededshares" <> metavar "N" <> help ("Configure the number of shares needed to restore. (default: " ++ show (neededObjects (shareParams defaultTunables)) ++ ") (When this option is used to back up a key, it must also be provided at restore time.)") ) nameopt = optional $ Name . BU8.fromString <$> strOption ( long "name" <> metavar "N" <> help "Specify name used for key backup/restore, avoiding the usual prompt." ) othernameopt = optional $ Name . BU8.fromString <$> strOption ( long "othername" <> metavar "N" <> help "Specify other name used for key backup/restore, avoiding the usual prompt." ) 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' Benchmark ( long "benchmark" <> help "Benchmark speed of keysafe's cryptographic primitives." ) <|> flag' Test ( long "test" <> help "Run test suite." ) 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." ) 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 }