liuxiaolong
2022-06-28 37714b1093c04061e636e5b1d27179652e671c0a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 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:
    }
}