1/* 2 * Copyright (c) 2012 Chris Howey 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17package gopass 18 19import ( 20 "errors" 21 "fmt" 22 "io" 23 "os" 24) 25 26type FdReader interface { 27 io.Reader 28 Fd() uintptr 29} 30 31var defaultGetCh = func(r io.Reader) (byte, error) { 32 buf := make([]byte, 1) 33 if n, err := r.Read(buf); n == 0 || err != nil { 34 if err != nil { 35 return 0, err 36 } 37 return 0, io.EOF 38 } 39 return buf[0], nil 40} 41 42var ( 43 maxLength = 512 44 ErrInterrupted = errors.New("interrupted") 45 ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength) 46 47 // Provide variable so that tests can provide a mock implementation. 48 getch = defaultGetCh 49) 50 51// getPasswd returns the input read from terminal. 52// If prompt is not empty, it will be output as a prompt to the user 53// If masked is true, typing will be matched by asterisks on the screen. 54// Otherwise, typing will echo nothing. 55func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) { 56 var err error 57 var pass, bs, mask []byte 58 if masked { 59 bs = []byte("\b \b") 60 mask = []byte("*") 61 } 62 63 if isTerminal(r.Fd()) { 64 if oldState, err := makeRaw(r.Fd()); err != nil { 65 return pass, err 66 } else { 67 defer func() { 68 restore(r.Fd(), oldState) 69 fmt.Fprintln(w) 70 }() 71 } 72 } 73 74 if prompt != "" { 75 fmt.Fprint(w, prompt) 76 } 77 78 // Track total bytes read, not just bytes in the password. This ensures any 79 // errors that might flood the console with nil or -1 bytes infinitely are 80 // capped. 81 var counter int 82 for counter = 0; counter <= maxLength; counter++ { 83 if v, e := getch(r); e != nil { 84 err = e 85 break 86 } else if v == 127 || v == 8 { 87 if l := len(pass); l > 0 { 88 pass = pass[:l-1] 89 fmt.Fprint(w, string(bs)) 90 } 91 } else if v == 13 || v == 10 { 92 break 93 } else if v == 3 { 94 err = ErrInterrupted 95 break 96 } else if v != 0 { 97 pass = append(pass, v) 98 fmt.Fprint(w, string(mask)) 99 } 100 } 101 102 if counter > maxLength { 103 err = ErrMaxLengthExceeded 104 } 105 106 return pass, err 107} 108 109// GetPasswd returns the password read from the terminal without echoing input. 110// The returned byte array does not include end-of-line characters. 111func GetPasswd() ([]byte, error) { 112 return getPasswd("", false, os.Stdin, os.Stdout) 113} 114 115// GetPasswdMasked returns the password read from the terminal, echoing asterisks. 116// The returned byte array does not include end-of-line characters. 117func GetPasswdMasked() ([]byte, error) { 118 return getPasswd("", true, os.Stdin, os.Stdout) 119} 120 121// GetPasswdPrompt prompts the user and returns the password read from the terminal. 122// If mask is true, then asterisks are echoed. 123// The returned byte array does not include end-of-line characters. 124func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) { 125 return getPasswd(prompt, mask, r, w) 126} 127