| // 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 aix darwin dragonfly freebsd linux netbsd openbsd solaris zos |
| |
| package speakeasy |
| |
| import ( |
| "fmt" |
| "os" |
| "os/exec" |
| "os/signal" |
| "strings" |
| "syscall" |
| ) |
| |
| const sttyBin = "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) { |
| path, err := exec.LookPath(sttyBin) |
| if err != nil { |
| return 0, fmt.Errorf("%s binary not found:\n\t%s", sttyBin, err) |
| } |
| pid, err := syscall.ForkExec(path, 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. |
| path, err := exec.LookPath(sttyBin) |
| if err != nil { |
| return |
| } |
| pid, e := syscall.ForkExec(path, 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: |
| } |
| } |