Version 18 of Echo-free password entry

Updated 2021-11-23 18:40:40 by nat418

"What can I do so the characters can't be seen when a user types a password?" is a frequent question. Tk applications have a complete and simple answer: use entry's -show option. For a usage example, see A little login dialog.

nat-418 Here is a solution that works with Tcl 8.6+ in 2021:

package require term::ansi::ctrl::unix

proc prompt {message {mode normal}} {
    global tcl_platform

    set os [lindex $tcl_platform(platform) 0]

    puts  -nonewline "$message "

    if {$os ne "unix"} {
        flush stdout
        return [gets stdin]
    }

    proc loop accumulator {
        ::term::ansi::ctrl::unix::raw
        flush stdout
        puts -nonewline "*"

        set character [read stdin 1]
        
        append accumulator $character

        if {$character eq "\n" || $character eq "\r"} {
            puts ""
            ::term::ansi::ctrl::unix::cooked
            return $accumulator
        }

        loop $accumulator
    }

    loop ""
}

set input [prompt "Password:" -silent]

A pure-Tcl solution is slightly subtler. In a Unix context, the formula is

        exec stty -echo / echo

[elaborate, including signal hygiene ...--which Expect handles by itself]

"stty -echo ..." is an error-prone subject. Don Libes and marsd describe, for example, a long-standing problem that continues to plague Linux 2.4.12 [L1 ].

The Unix Terminal Extension provides the ability to disable echoing, as well as the ability to read one character at a time from stdin. See terminal:password:get for an example.

If you don't mind using the Tcl Windows API extension (which only works on Win NT platforms, not Win 98!), the following should do the trick

   package require twapi
   set console_handle [twapi::GetStdHandle -10]
   set oldmode [twapi::GetConsoleMode $console_handle]
   set newmode [expr {$oldmode & ~4}] ;# Turn off the echo bit
   twapi::SetConsoleMode $console_handle $newmode
   gets stdin password ;#...or do whatever...
   twapi::SetConsoleMode $console_handle $oldmode ;# Restore echo mode

Or a slightly simpler version using the higher level console API in TWAPI 0.7:

   puts -nonewline "Enter password: "
   flush stdout
   set oldmode [twapi::modify_console_input_mode stdin -echoinput false -lineinput true]
   gets stdin password
   # Restore original input mode
   twapi::set_console_input_mode stdin {*}$oldmode

The TWAPI extension is available from http://twapi.sf.net


drh FWIW, here is the code I am currently using to read echo-free passwords on unix:

  # Read a single line of input from the terminal without echoing to the
  # screen.  If Control-C is pressed, exit immediately.
  #
  proc tty_gets_no_echo {{prompt {}}} {
    if {$prompt!=""} {
      puts -nonewline $prompt
    }
    flush stdout
    global _tty_input _tty_wait tcl_platform
    if {$tcl_platform(platform)!="unix"} {
      # FIXME:  This routine only works on unix.  On other platforms, the
      # password is still echoed to the screen as it is typed.
      return [gets stdin]
    }
    set _tty_input {}
    set _tty_wait 0
    fileevent stdin readable _tty_read_one_character
    exec /bin/stty raw -echo <@stdin
    vwait ::_tty_wait
    fileevent stdin readable {}
    return $_tty_input
  }
  proc _tty_read_one_character {} {
    set c [read stdin 1]
    if {$c=="\n" || $c=="\003"} {
      exec /bin/stty -raw echo <@stdin
      puts ""
      if {$c=="\003"} exit
      incr ::_tty_wait
    } else {
      append ::_tty_input $c
    }
  }

See also the slightly related app Password Gorilla