94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
// based on https://code.google.com/p/gopass
|
|
// Author: johnsiilver@gmail.com (John Doak)
|
|
//
|
|
// Original code is based on code by RogerV in the golang-nuts thread:
|
|
// https://groups.google.com/group/golang-nuts/browse_thread/thread/40cc41e9d9fc9247
|
|
|
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
|
|
package speakeasy
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
const sttyArg0 = "/bin/stty"
|
|
|
|
var (
|
|
sttyArgvEOff = []string{"stty", "-echo"}
|
|
sttyArgvEOn = []string{"stty", "echo"}
|
|
)
|
|
|
|
// getPassword gets input hidden from the terminal from a user. This is
|
|
// accomplished by turning off terminal echo, reading input from the user and
|
|
// finally turning on terminal echo.
|
|
func getPassword() (password string, err error) {
|
|
sig := make(chan os.Signal, 10)
|
|
brk := make(chan bool)
|
|
|
|
// File descriptors for stdin, stdout, and stderr.
|
|
fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
|
|
|
|
// Setup notifications of termination signals to channel sig, create a process to
|
|
// watch for these signals so we can turn back on echo if need be.
|
|
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
|
|
syscall.SIGTERM)
|
|
go catchSignal(fd, sig, brk)
|
|
|
|
// Turn off the terminal echo.
|
|
pid, err := echoOff(fd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Turn on the terminal echo and stop listening for signals.
|
|
defer signal.Stop(sig)
|
|
defer close(brk)
|
|
defer echoOn(fd)
|
|
|
|
syscall.Wait4(pid, nil, 0, nil)
|
|
|
|
line, err := readline()
|
|
if err == nil {
|
|
password = strings.TrimSpace(line)
|
|
} else {
|
|
err = fmt.Errorf("failed during password entry: %s", err)
|
|
}
|
|
|
|
return password, err
|
|
}
|
|
|
|
// echoOff turns off the terminal echo.
|
|
func echoOff(fd []uintptr) (int, error) {
|
|
pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: "", Files: fd})
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed turning off console echo for password entry:\n\t%s", err)
|
|
}
|
|
return pid, nil
|
|
}
|
|
|
|
// echoOn turns back on the terminal echo.
|
|
func echoOn(fd []uintptr) {
|
|
// Turn on the terminal echo.
|
|
pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: "", Files: fd})
|
|
if e == nil {
|
|
syscall.Wait4(pid, nil, 0, nil)
|
|
}
|
|
}
|
|
|
|
// catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn
|
|
// terminal echo back on before the program ends. Otherwise the user is left
|
|
// with echo off on their terminal.
|
|
func catchSignal(fd []uintptr, sig chan os.Signal, brk chan bool) {
|
|
select {
|
|
case <-sig:
|
|
echoOn(fd)
|
|
os.Exit(-1)
|
|
case <-brk:
|
|
}
|
|
}
|