forked from howeyc/gopass
-
Notifications
You must be signed in to change notification settings - Fork 2
/
pass.go
152 lines (133 loc) · 3.63 KB
/
pass.go
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package gopass
import (
"errors"
"fmt"
"io"
"os"
"golang.org/x/term"
)
// echoMode encodes various types of echoing behavior.
type echoMode uint8
const (
// echoModeNone performs no echoing.
echoModeNone echoMode = iota
// echoModeMask performs asterisk echoing.
echoModeMask
// echoModeEcho performs complete echoing.
echoModeEcho
)
// String implements Stringer for echoMode.
func (m echoMode) String() string {
if m == echoModeNone {
return "none"
} else if m == echoModeMask {
return "mask"
} else if m == echoModeEcho {
return "echo"
}
return "unknown"
}
type FdReader interface {
io.Reader
Fd() uintptr
}
var defaultGetCh = func(r io.Reader) (byte, error) {
buf := make([]byte, 1)
if n, err := r.Read(buf); n == 0 || err != nil {
if err != nil {
return 0, err
}
return 0, io.EOF
}
return buf[0], nil
}
var (
maxLength = 512
ErrInterrupted = errors.New("interrupted")
ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength)
// Provide variable so that tests can provide a mock implementation.
getch = defaultGetCh
)
// getPasswd returns the input read from terminal. If prompt is not empty, it
// will be output as a prompt to the user. This function echos according to the
// mode specified.
func getPasswd(prompt string, mode echoMode, r FdReader, w io.Writer) ([]byte, error) {
var err error
var pass, bs, mask []byte
if mode == echoModeMask || mode == echoModeEcho {
bs = []byte("\b \b")
}
if mode == echoModeMask {
mask = []byte("*")
}
rfd := int(r.Fd())
if term.IsTerminal(rfd) {
if oldState, err := term.MakeRaw(rfd); err != nil {
return pass, err
} else {
defer func() {
term.Restore(rfd, oldState)
fmt.Fprintln(w)
}()
}
}
if prompt != "" {
fmt.Fprint(w, prompt)
}
// Track total bytes read, not just bytes in the password. This ensures any
// errors that might flood the console with nil or -1 bytes infinitely are
// capped.
var counter int
for counter = 0; counter <= maxLength; counter++ {
if v, e := getch(r); e != nil {
err = e
break
} else if v == 127 || v == 8 {
if l := len(pass); l > 0 {
pass = pass[:l-1]
fmt.Fprint(w, string(bs))
}
} else if v == 13 || v == 10 {
break
} else if v == 3 {
err = ErrInterrupted
break
} else if v != 0 {
pass = append(pass, v)
if mode == echoModeMask {
fmt.Fprint(w, string(mask))
} else if mode == echoModeEcho {
fmt.Fprint(w, string(v))
}
}
}
if counter > maxLength {
err = ErrMaxLengthExceeded
}
return pass, err
}
// GetPasswd returns the password read from the terminal without echoing input.
// The returned byte array does not include end-of-line characters.
func GetPasswd() ([]byte, error) {
return getPasswd("", echoModeNone, os.Stdin, os.Stdout)
}
// GetPasswdMasked returns the password read from the terminal, echoing asterisks.
// The returned byte array does not include end-of-line characters.
func GetPasswdMasked() ([]byte, error) {
return getPasswd("", echoModeMask, os.Stdin, os.Stdout)
}
// GetPasswdEchoed returns the password read from the terminal, echoing input.
// The returned byte array does not include end-of-line characters.
func GetPasswdEchoed() ([]byte, error) {
return getPasswd("", echoModeEcho, os.Stdin, os.Stdout)
}
// GetPasswdPrompt prompts the user and returns the password read from the terminal.
// If mask is true, then asterisks are echoed.
// The returned byte array does not include end-of-line characters.
func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) {
mode := echoModeNone
if mask {
mode = echoModeMask
}
return getPasswd(prompt, mode, r, w)
}