Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prompting for a password in Haskell command line application

The following Haskell program prompts the user for a password in the terminal and continues if he has entered the correct one:

main = do
    putStrLn "Password:"
    password <- getLine

    case hash password `member` database of
        False -> putStrLn "Unauthorized use!"
        True  -> do
                 ...

Unfortunately, the password will appear on the screen as the user types it, which I want to avoid.

How can I read a sequence of characters that the users types without having the show up on the screen? What is the equivalent of getLine for this purpose?

I'm on MacOS X, but I would like this to work on Windows and Linux, too.

like image 866
Heinrich Apfelmus Avatar asked Oct 31 '10 18:10

Heinrich Apfelmus


4 Answers

Do this:

module Main
where

import System.IO
import Control.Exception

main :: IO ()
main = getPassword >>= putStrLn . ("Entered: " ++)

getPassword :: IO String
getPassword = do
  putStr "Password: "
  hFlush stdout
  pass <- withEcho False getLine
  putChar '\n'
  return pass

withEcho :: Bool -> IO a -> IO a
withEcho echo action = do
  old <- hGetEcho stdin
  bracket_ (hSetEcho stdin echo) (hSetEcho stdin old) action
like image 170
Yuras Avatar answered Oct 19 '22 01:10

Yuras


There is a getPassword in System.Console.Haskeline. Probably it's an overkill for your case but someone may find it useful.

An example:

> runInputT defaultSettings $ do {p <- getPassword (Just '*') "pass:"; outputStrLn $ fromJust p}
pass:***
asd
like image 23
Daniel Avatar answered Oct 19 '22 01:10

Daniel


It is possible to disable echoing in the terminal with the System.Posix.Terminal module. However, this requires POSIX support, so may not work on Windows (I didn't check).

import System.Posix.Terminal 
import System.Posix.IO (stdInput)

getPassword :: IO String
getPassword = do
    tc <- getTerminalAttributes stdInput
    setTerminalAttributes stdInput (withoutMode tc EnableEcho) Immediately
    password <- getLine
    setTerminalAttributes stdInput tc Immediately
    return password

main = do
    putStrLn "Password:"
    password <- getPassword
    putStrLn "Name:"
    name <- getLine
    putStrLn $ "Your password is " ++ password ++ " and your name is " ++ name

Note that the stdin is line-buffered, so if you use putStr "Password:" instead of putStrLn, you need to flush the buffer first, otherwise the prompt will be inhibited also.

like image 9
kennytm Avatar answered Oct 19 '22 02:10

kennytm


withEcho can be written with a little less noise:

withEcho :: Bool -> IO a -> IO a
withEcho echo action =
    bracket (hGetEcho stdin)
            (hSetEcho stdin)
            (const $ hSetEcho stdin echo >> action)
like image 7
1chb Avatar answered Oct 19 '22 00:10

1chb