-------------------------------------------------------------------------------- -- rofi-pinentry - a simply pinentry proxy for bitwarden -- -- Rather than prompt the user like all the other pinentry programs, call the -- bitwarden deamon and prompt for a password there module Main where import Bitwarden.Internal import qualified Data.Text.IO as TI import Data.Yaml import RIO import qualified RIO.List as L import qualified RIO.Text as T import System.Directory import System.Environment import System.FilePath.Posix import System.Posix.Process main :: IO () main = do hSetBuffering stdout LineBuffering TI.putStrLn "OK Pleased to meet you" pinentryLoop =<< readPinConf newtype PinConf = PinConf {pcBwName :: String} deriving (Eq, Show) instance FromJSON PinConf where parseJSON (Object o) = PinConf <$> o .:? "bitwarden-name" .!= "gnupg" parseJSON _ = fail "pinentry yaml parse error" readPinConf :: IO PinConf readPinConf = do c <- decodeFileEither =<< pinConfDir case c of Left e -> TI.putStrLn (T.pack $ show e) >> exitWith (ExitFailure 1) Right r -> return r pinConfDir :: IO FilePath pinConfDir = maybe defHome (return . ( confname)) =<< lookupEnv "GNUPGHOME" where defHome = ( ".gnupg" confname) <$> getHomeDirectory confname = "pinentry-rofi.yml" pinentryLoop :: PinConf -> IO () pinentryLoop p = do processLine p . T.words =<< TI.getLine pinentryLoop p processLine :: PinConf -> [T.Text] -> IO () processLine _ [] = noop processLine _ ["BYE"] = exitSuccess processLine p ["GETPIN"] = getPin p processLine _ ["GETINFO", o] = processGetInfo o -- TODO this might be important processLine _ ["OPTION", o] = processOption o -- these should all do nothing processLine _ ("SETDESC" : _) = noop processLine _ ("SETOK" : _) = noop processLine _ ("SETNOTOK" : _) = noop processLine _ ("SETCANCEL" : _) = noop processLine _ ("SETPROMPT" : _) = noop processLine _ ("SETERROR" : _) = noop -- CONFIRM can take a flag processLine _ ["CONFIRM"] = noop processLine _ ["CONFIRM", "--one-button", _] = noop processLine _ ss = unknownCommand $ T.unwords ss unknownCommand :: T.Text -> IO () unknownCommand c = TI.putStrLn $ T.append "ERR 275 Unknown command " c getPin :: PinConf -> IO () getPin p = do its <- getItems let w = (fmap T.pack . password . login) =<< L.find (\i -> pcBwName p == name i) its maybe err send w where err = TI.putStrLn "ERR 83886179 Operation canceled " -- these are the only supported options for GETINFO; anything else is an error processGetInfo :: T.Text -> IO () processGetInfo "pid" = send . T.pack . show =<< getProcessID processGetInfo "version" = noop processGetInfo "flavor" = noop processGetInfo "ttyinfo" = noop processGetInfo _ = TI.putStrLn "ERR 83886360 IPC parameter error " processOption :: T.Text -> IO () processOption _ = noop send :: T.Text -> IO () send s = TI.putStrLn (T.append "D " s) >> ok noop :: IO () noop = ok ok :: IO () ok = TI.putStrLn "OK"