github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/cf/terminal/ui_unix.go (about) 1 // Copied from https://code.google.com/p/gopass/ 2 3 // +build !windows 4 5 package terminal 6 7 import ( 8 "bufio" 9 "fmt" 10 "os" 11 "os/signal" 12 "strings" 13 "syscall" 14 15 . "github.com/liamawhite/cli-with-i18n/cf/i18n" 16 ) 17 18 const ( 19 sttyArg0 = "/bin/stty" 20 execCWDir = "" 21 ) 22 23 // Tells the terminal to turn echo off. 24 var sttyArgvEOff = []string{"stty", "-echo"} 25 26 // Tells the terminal to turn echo on. 27 var sttyArgvEOn = []string{"stty", "echo"} 28 29 var ws syscall.WaitStatus 30 31 func (ui terminalUI) AskForPassword(prompt string) (passwd string) { 32 sig := make(chan os.Signal, 10) 33 34 // Display the prompt. 35 fmt.Printf("\n%s%s ", prompt, PromptColor(">")) 36 37 // File descriptors for stdin, stdout, and stderr. 38 fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()} 39 40 // Setup notifications of termination signals to channel sig, create a process to 41 // watch for these signals so we can turn back on echo if need be. 42 signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT, 43 syscall.SIGTERM) 44 defer signal.Stop(sig) 45 46 go catchSignal(fd, sig) 47 48 pid, err := echoOff(fd) 49 defer echoOn(fd) 50 if err != nil { 51 return 52 } 53 54 passwd = readPassword(pid) 55 56 // Carriage return after the user input. 57 fmt.Println("") 58 59 return 60 } 61 62 func readPassword(pid int) string { 63 rd := bufio.NewReader(os.Stdin) 64 _, _ = syscall.Wait4(pid, &ws, 0, nil) 65 66 line, err := rd.ReadString('\n') 67 if err == nil { 68 return strings.TrimSpace(line) 69 } 70 return "" 71 } 72 73 func echoOff(fd []uintptr) (int, error) { 74 pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: execCWDir, Files: fd}) 75 76 if err != nil { 77 return 0, fmt.Errorf(T("failed turning off console echo for password entry:\n{{.ErrorDescription}}", map[string]interface{}{"ErrorDescription": err})) 78 } 79 80 return pid, nil 81 } 82 83 // echoOn turns back on the terminal echo. 84 func echoOn(fd []uintptr) { 85 // Turn on the terminal echo. 86 pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: execCWDir, Files: fd}) 87 88 if e == nil { 89 _, _ = syscall.Wait4(pid, &ws, 0, nil) 90 } 91 } 92 93 // catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn terminal 94 // echo back on before the program ends. Otherwise the user is left with echo off on 95 // their terminal. 96 func catchSignal(fd []uintptr, sig chan os.Signal) { 97 select { 98 case <-sig: 99 echoOn(fd) 100 os.Exit(2) 101 } 102 }