// SPDX-License-Identifier: MIT //go:build !windows package main import ( "bufio" "errors" "fmt" "io" "os" "os/exec" "strings" ) // readPassword asks on the terminal, with typing hidden. It reads the // terminal itself rather than standard input, so a command whose input is a // pipe still asks the person at the keyboard. func readPassword(prompt string) (string, error) { tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return "", errNoTerminal } defer tty.Close() saved, err := stty(tty, "-g") if err != nil { return "", errNoTerminal } if _, err := stty(tty, "-echo"); err != nil { return "", errNoTerminal } restore := func() { _, _ = stty(tty, strings.TrimSpace(saved)) } stop := onInterrupt(func() { restore() fmt.Fprintln(tty) }) defer stop() defer restore() fmt.Fprint(tty, prompt) line, err := bufio.NewReader(tty).ReadString('\n') fmt.Fprintln(tty) if err != nil && !(errors.Is(err, io.EOF) && line != "") { return "", fmt.Errorf("could not read the password: %w", err) } return strings.TrimRight(line, "\r\n"), nil } // stty runs the system's stty on the terminal. It is named by its path: every // system this tool is built for has it there. func stty(tty *os.File, args ...string) (string, error) { cmd := exec.Command("/bin/stty", args...) cmd.Stdin = tty out, err := cmd.Output() return string(out), err }