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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
// Package readpassword reads a password from the terminal of from stdin.
package readpassword
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
"golang.org/x/term"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
const (
// 2kB limit like EncFS
maxPasswordLen = 2048
)
// Once tries to get a password from the user, either from the terminal, extpass, passfile
// or stdin. Leave "prompt" empty to use the default "Password: " prompt.
func Once(extpass []string, passfile []string, prompt string) ([]byte, error) {
if len(passfile) != 0 {
return readPassFileConcatenate(passfile)
}
if len(extpass) != 0 {
return readPasswordExtpass(extpass)
}
if prompt == "" {
prompt = "Password"
}
if !term.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordStdin(prompt)
}
return readPasswordTerminal(prompt + ": ")
}
// Twice is the same as Once but will prompt twice if we get the password from
// the terminal.
func Twice(extpass []string, passfile []string) ([]byte, error) {
if len(passfile) != 0 {
return readPassFileConcatenate(passfile)
}
if len(extpass) != 0 {
return readPasswordExtpass(extpass)
}
if !term.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordStdin("Password")
}
p1, err := readPasswordTerminal("Password: ")
if err != nil {
return nil, err
}
p2, err := readPasswordTerminal("Repeat: ")
if err != nil {
return nil, err
}
if !bytes.Equal(p1, p2) {
return nil, fmt.Errorf("passwords do not match")
}
// Wipe the password duplicate from memory
for i := range p2 {
p2[i] = 0
}
return p1, nil
}
// readPasswordTerminal reads a line from the terminal.
// Exits on read error or empty result.
func readPasswordTerminal(prompt string) ([]byte, error) {
fd := int(os.Stdin.Fd())
fmt.Fprint(os.Stderr, prompt)
// term.ReadPassword removes the trailing newline
p, err := term.ReadPassword(fd)
if err != nil {
return nil, fmt.Errorf("could not read password from terminal: %v", err)
}
fmt.Fprintf(os.Stderr, "\n")
if len(p) == 0 {
return nil, fmt.Errorf("password is empty")
}
return p, nil
}
// readPasswordStdin reads a line from stdin.
// It exits with a fatal error on read error or empty result.
func readPasswordStdin(prompt string) ([]byte, error) {
// This should make debugging situations like
// https://github.com/rfjakob/gocryptfs/issues/852
// easier. Only works on Linux, otherwise shows "?".
target, err := os.Readlink("/proc/self/fd/0")
if err != nil {
target = "?"
}
tlog.Info.Printf("Reading %s from stdin (connected to %q)", prompt, target)
p, err := readLineUnbuffered(os.Stdin)
if err != nil {
return nil, err
}
if len(p) == 0 {
return nil, fmt.Errorf("got empty %s from stdin", prompt)
}
return p, nil
}
// readPasswordExtpass executes the "extpass" program and returns the first line
// of the output.
// Exits on read error or empty result.
func readPasswordExtpass(extpass []string) ([]byte, error) {
var parts []string
if len(extpass) == 1 {
parts = strings.Split(extpass[0], " ")
} else {
parts = extpass
}
tlog.Info.Printf("Reading password from extpass program %q, arguments: %q\n", parts[0], parts[1:])
cmd := exec.Command(parts[0], parts[1:]...)
cmd.Stderr = os.Stderr
pipe, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("extpass pipe setup failed: %v", err)
}
err = cmd.Start()
if err != nil {
return nil, fmt.Errorf("extpass cmd start failed: %v", err)
}
p, err := readLineUnbuffered(pipe)
if err != nil {
return nil, err
}
pipe.Close()
err = cmd.Wait()
if err != nil {
return nil, fmt.Errorf("extpass program returned an error: %v", err)
}
if len(p) == 0 {
return nil, fmt.Errorf("extpass: password is empty")
}
return p, nil
}
// readLineUnbuffered reads single bytes from "r" util it gets "\n" or EOF.
// The returned string does NOT contain the trailing "\n".
func readLineUnbuffered(r io.Reader) (l []byte, err error) {
b := make([]byte, 1)
for {
if len(l) > maxPasswordLen {
return nil, fmt.Errorf("fatal: maximum password length of %d bytes exceeded", maxPasswordLen)
}
n, err := r.Read(b)
if err == io.EOF {
return l, nil
}
if err != nil {
return nil, fmt.Errorf("readLineUnbuffered: %v", err)
}
if n == 0 {
continue
}
if b[0] == '\n' {
return l, nil
}
l = append(l, b...)
}
}
|