speakeasy_unix.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. // based on https://code.google.com/p/gopass
  2. // Author: johnsiilver@gmail.com (John Doak)
  3. //
  4. // Original code is based on code by RogerV in the golang-nuts thread:
  5. // https://groups.google.com/group/golang-nuts/browse_thread/thread/40cc41e9d9fc9247
  6. // +build darwin freebsd linux netbsd openbsd solaris
  7. package speakeasy
  8. import (
  9. "fmt"
  10. "os"
  11. "os/signal"
  12. "strings"
  13. "syscall"
  14. )
  15. const sttyArg0 = "/bin/stty"
  16. var (
  17. sttyArgvEOff = []string{"stty", "-echo"}
  18. sttyArgvEOn = []string{"stty", "echo"}
  19. )
  20. // getPassword gets input hidden from the terminal from a user. This is
  21. // accomplished by turning off terminal echo, reading input from the user and
  22. // finally turning on terminal echo.
  23. func getPassword() (password string, err error) {
  24. sig := make(chan os.Signal, 10)
  25. brk := make(chan bool)
  26. // File descriptors for stdin, stdout, and stderr.
  27. fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
  28. // Setup notifications of termination signals to channel sig, create a process to
  29. // watch for these signals so we can turn back on echo if need be.
  30. signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
  31. syscall.SIGTERM)
  32. go catchSignal(fd, sig, brk)
  33. // Turn off the terminal echo.
  34. pid, err := echoOff(fd)
  35. if err != nil {
  36. return "", err
  37. }
  38. // Turn on the terminal echo and stop listening for signals.
  39. defer signal.Stop(sig)
  40. defer close(brk)
  41. defer echoOn(fd)
  42. syscall.Wait4(pid, nil, 0, nil)
  43. line, err := readline()
  44. if err == nil {
  45. password = strings.TrimSpace(line)
  46. } else {
  47. err = fmt.Errorf("failed during password entry: %s", err)
  48. }
  49. return password, err
  50. }
  51. // echoOff turns off the terminal echo.
  52. func echoOff(fd []uintptr) (int, error) {
  53. pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: "", Files: fd})
  54. if err != nil {
  55. return 0, fmt.Errorf("failed turning off console echo for password entry:\n\t%s", err)
  56. }
  57. return pid, nil
  58. }
  59. // echoOn turns back on the terminal echo.
  60. func echoOn(fd []uintptr) {
  61. // Turn on the terminal echo.
  62. pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: "", Files: fd})
  63. if e == nil {
  64. syscall.Wait4(pid, nil, 0, nil)
  65. }
  66. }
  67. // catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn
  68. // terminal echo back on before the program ends. Otherwise the user is left
  69. // with echo off on their terminal.
  70. func catchSignal(fd []uintptr, sig chan os.Signal, brk chan bool) {
  71. select {
  72. case <-sig:
  73. echoOn(fd)
  74. os.Exit(-1)
  75. case <-brk:
  76. }
  77. }