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